> ## Documentation Index
> Fetch the complete documentation index at: https://docs.paradime.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Build a custom bot on the agent API

> Run a Streamlit chat app backed by a DinoAI programmable agent, so stakeholders get answers with charts, data, and SQL from an open-source starter.

Run a chat app where business stakeholders ask questions about their data, with a DinoAI programmable agent as the backend. The agent finds the right dbt™ models, queries the warehouse, and writes the answer. Your app shows it with charts. You start from the open-source [dinoai-custom-bot-starter](https://github.com/paradime-io/dinoai-custom-bot-starter) repository, a Streamlit app that you can run as it is or use as a pattern for your own bot.

![The starter app answers a question with a bar chart that has Chart, Data, and SQL tabs](https://mintcdn.com/paradime-docs/141i9Nbh-DY4Ka13/images/custom-bot-starter-answer-with-charts.png?fit=max\&auto=format\&n=141i9Nbh-DY4Ka13\&q=85\&s=15182ca7f6cfac709edfc17e845a21aa)

> [!NOTE]
>
> **Prerequisites**
>
> * A Paradime workspace with a warehouse connection and a dbt™ project. The agent works best with documented mart models.
> * The Workspace Admin role, to create API keys and agent environment variables.
> * Python 3.11 or later, and git.
> * Optional, for **Report a problem**: a Linear workspace, the key of the team that gets the reports, and a Linear personal API key.
>
> Estimated time: 30 minutes.

## How it works

* **One conversation is one agent session.** The app starts a run with `triggerDinoaiAgentRun`, sends follow-ups to the same session with `sendDinoaiAgentMessage`, and reads the run with `dinoaiAgentRun` every second. The agent keeps the context between questions.
* **The app sends a response contract.** At the start of each session, the app tells the agent to put each chart in a fenced `viz` block that holds one JSON object with the rows and the SQL. The app parses the blocks and draws the charts.
* **The app shows live progress.** It reads the run's `steps` to list each tool call while the agent works, such as a catalog search or a SQL query.
* **The app needs no warehouse credentials.** Paradime runs the agent, its tools, and the warehouse connection. The app only needs an API key.

## Steps

**1. Run the demo**

Clone the repository and start the app in demo mode. Demo mode replays recorded answers and needs no credentials.

```bash theme={"system"}
git clone https://github.com/paradime-io/dinoai-custom-bot-starter.git
cd dinoai-custom-bot-starter
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements-dev.txt
pytest -q
ANALYST_MODE=demo streamlit run app.py
```

Select a starter question. If the tests pass and an answer with charts shows, your Python setup is correct.

**2. Create an API key**

1. Go to **Settings** > **API Keys** and select **Generate API Key**.
2. Enter a name, for example `ask-your-data app`.
3. Select the **DinoAI agent API** capability. The app needs nothing else.
4. Select the workspace that the app uses, then generate the key.
5. Select **Copy credentials**. This copies the key, the API endpoint, and the workspace UID.

> [!WARNING]
>
> Paradime shows the key only once. Store it in your password manager before you leave the screen.

Copy the endpoint, don't type it: it depends on the region of your account. See [API keys](https://docs.paradime.io/developers/api-keys.md).

**3. Create the agents**

The repository has two agent definitions in `agent/`:

| Agent | What it does |
| - | - |
| `stakeholder-analyst` | Answers questions: finds the dbt™ models, runs SQL, and writes the answer. Read-only. |
| `analytics-feedback` | Files a reader's report as a Linear issue. Only the analyst starts it. Optional. |

Create each agent one of two ways:

* **In the Agent UI**: open **DinoAI Agent** > **Agents** > **New agent** > **Start from scratch**, and copy the role, goal, backstory, and tool allowlist from the YAML file. Create `analytics-feedback` first, so that you can select it in the analyst's **Squad**. Select **Deploy** > **Just here**. Copy each agent's slug from the page address (`.../agent/builder/edit/<slug>?uuid=...`). An agent created in the UI gets a generated slug, such as `stakeholder-5029ee`. See [Build an agent in the UI](https://docs.paradime.io/guides/programmable-agents/build-in-the-agent-ui.md).
* **As code**: copy both files to `.dinoai/agents/` in your dbt™ repository and merge them into the default branch. The slug is the file name. See [YAML configuration](https://docs.paradime.io/products/dino-ai/programmable-agents/yaml-configuration.md).

> [!WARNING]
>
> Keep the `tools` allowlist. `run_sql_query` runs the SQL that the agent writes, and stakeholders can type anything. An agent without a `tools` block gets every tool.

**4. Connect Linear for the feedback agent (optional)**

Skip this step if you don't use **Report a problem**.

1. In Linear, create a personal API key as a user that can create issues in the target team.
2. In Paradime, go to **Settings** > **Workspace Settings** > **Environment Variables**, open the **Agent** tab, and add `LINEAR_API_KEY` with the Linear key as its value.

The feedback agent gets its Linear tools only when its session can see this variable. See [DinoAI background agent environment variables](https://docs.paradime.io/products/settings/environment-variables/dinoai-background-agent-environment-variables.md).

**5. Give the analyst a read-only warehouse role**

1. In your warehouse, create a role or service user that can only read the mart schemas.
2. In Paradime, go to **Settings** > **Environments**, select **Agent**, and create an environment with that connection.
3. Select the environment in the analyst's **Environment** field, or set `environment: <environment slug>` in its YAML file.

> [!NOTE]
>
> This step is optional for a test, but do it before real users get access.

**6. Configure the app**

Copy the settings template:

```bash theme={"system"}
cp .streamlit/secrets.toml.example .streamlit/secrets.toml
```

Fill in the values from the steps above:

```toml theme={"system"}
PARADIME_API_ENDPOINT = "https://api.paradime.io/api/v1/<company_token>/graphql"
PARADIME_API_SECRET = "prdm_cmp_..."
PARADIME_WORKSPACE_UID = "<workspace uid>"
DINOAI_AGENT = "<stakeholder-analyst slug>"
ANALYST_FEEDBACK_AGENT = "<analytics-feedback slug>"
ANALYST_FEEDBACK_TEAM = "DNA"
```

Then describe your data in `ANALYST_CONTEXT` (the key marts, the metrics and their definitions, the fiscal calendar) and set `ANALYST_STARTER_QUESTIONS`. Good context makes answers faster, because the agent spends less time on discovery.

`.streamlit/secrets.toml` is in `.gitignore`. Never commit it.

**7. Test and run the app**

```bash theme={"system"}
python scripts/check_setup.py          # checks the settings and access, starts nothing
python scripts/smoke_test.py           # asks one live question, 1 to 2 minutes
streamlit run app.py
```

`python scripts/smoke_test.py --report` tests **Report a problem** end to end. It creates a real Linear issue.

> [!TIP]
>
> Ask a question in the app. The status box lists the agent's steps, and after about a minute the answer shows with charts. Each chart has a **Data** tab with the rows and a **SQL** tab with the query. The sidebar shows the agent session ID: open the session in Paradime to see every tool call. If the run fails at start-up, check the agent slug and the environment slug.

## Report a problem with an agent squad

A reader can report a wrong answer. The app sends the report to the conversation's own session. The analyst hands it to the feedback agent with `invoke_agent` and its own session ID as `callback_session_id`. The feedback agent files a Linear issue, or comments on an open one about the same answer, then calls `notify_parent_session`. The callback wakes the analyst, which posts the issue link in the chat.

![The starter app shows that a reported problem was filed as a Linear issue](https://mintcdn.com/paradime-docs/141i9Nbh-DY4Ka13/images/custom-bot-starter-report-filed-in-linear.png?fit=max\&auto=format\&n=141i9Nbh-DY4Ka13\&q=85\&s=895223c0257c5eeefe51aa9dc4620326)

The link comes a few minutes after the report, without a question from the reader, so the app watches the session for new messages. See [Agent-to-agent delegation](https://docs.paradime.io/products/dino-ai/programmable-agents/agent-to-agent-delegation.md).

## Build your own bot

The same pattern works for other bots, such as a support assistant or a data-quality triage bot. Define the agent with only the tools it needs, then call the API with the Python SDK (`paradime-io` 6.8 or later):

```python theme={"system"}
import time
from paradime import Paradime

client = Paradime(
    api_endpoint="https://api.paradime.io/api/v1/<company_token>/graphql",
    api_secret="prdm_cmp_...",
    workspace_uid="<workspace uid>",
)
agents = client.dinoai_agents

started = agents.trigger_run(agent="support-triage", message="How many tickets did we close last week?")
session_id = started.agent_session_id

while True:
    run = agents.get_run(agent_session_id=session_id)
    if run.status.value in ("COMPLETED", "FAILED", "EXPIRED", "STOPPED"):
        break
    time.sleep(2)
answer = [m.content for m in run.messages if m.role == "agent"][-1]

# A follow-up goes to the same session, so the agent keeps the context.
agents.send_message(agent_session_id=session_id, message="And the week before?")
```

What to plan for:

* **Messages arrive complete.** The API has no streaming. Read the run in a loop, and use `steps` (with `steps_after`) to show progress.
* **Keep the output format out of the agent definition.** Put "who you are" and "which tools" in the agent, and the reply format in your first message. Then the format and its parser change together.
* **Track turns.** Before you send a follow-up, count the messages. Right after you send, the run can still report the previous turn's `COMPLETED`, so wait until it is active again or new messages stop.
* **Plan for an expired session.** The agent pod doesn't live forever. If Paradime refuses a follow-up, start a new session with a summary of the conversation.
* **Leave out local links.** Paradime's API firewall blocks a request body with a link to a local address, such as `http://localhost:8501/...`.

The repository's [build-your-own-bot guide](https://github.com/paradime-io/dinoai-custom-bot-starter/blob/main/docs/build-your-own-bot.md) covers the GraphQL calls, the response contract, and turn tracking in detail.

## Before real users

* **Sign-in.** The app has no authentication. Turn on Streamlit's OIDC sign-in (`st.login`) or put the app behind your SSO proxy. The API key stays on the server.
* **Read-only access.** Give the analyst a read-only agent environment.
* **Budget.** Each question is an agent turn that uses DinoAI credits. Set a cap for background agents, and optionally for each agent, in **Settings** > **Budget & Consumption** > **DinoAI Credits**.
* **Conversation storage.** Conversations are JSON files in `.threads/` on the app server. Keep that folder on a persistent disk, or use a database for more than one server.

## Next steps

> [card] [Run an agent from a webhook](https://docs.paradime.io/guides/programmable-agents/run-an-agent-from-a-webhook.md)
>
> Start an agent when another tool sends an event.

> [card] [Agent API reference](https://docs.paradime.io/products/dino-ai/programmable-agents/api-reference.md)
>
> The GraphQL mutations and queries, and the Python SDK.


## Related topics

- [Customize the Slack Agent](https://docs.paradime.io/products/dino-ai/slack-agent/customize-agent.md)
- [Jira intake agent](https://docs.paradime.io/guides/programmable-agents/jira-intake-agent.md)
- [Customize the DinoAI Slack agent](https://docs.paradime.io/changelog/2026-07-22/customize-slack-agent.md)
- [Linear intake agent](https://docs.paradime.io/guides/programmable-agents/linear-intake-agent.md)
- [Slack Agent setup](https://docs.paradime.io/products/dino-ai/slack-agent/setup.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.