> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.rightbrain.ai/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.rightbrain.ai/_mcp/server.

# Build your first agent

> Create a Rightbrain agent via the API with a task as a tool, run it over Server-Sent Events, follow the event stream, and fetch the recorded run afterward.

An **agent** is the top-level unit in Rightbrain. It reasons over an input and calls **tools** to do real work. This quickstart creates an agent, makes the task you built available to it as a tool, runs it over the API, and streams every step. It reuses the `RB_TOKEN`, `RB_ORG`, `RB_PROJECT`, `RB_TASK`, and `RB_MODEL` variables from the previous pages.

> **Note**
>
> Need the task first? Follow [Create a task](/docs/quickstart/create-a-task), then come back with its `id` in `RB_TASK`.

## Create, run and observe

Follow the steps beside the code. Paste each shell file’s commands in order into the same terminal; `events.txt` shows sample output.

#### Create the agent

The `instruction` is the agent's standing brief. `task_tools` attaches your task as a callable tool. With `revision_strategy: "follow_active"`, it uses the task's active revision.

The `201` response includes the agent's `id`. Save it as `RB_AGENT` for the next request. These shell commands require `jq` and run in the same terminal.

A task tool can also use `is_output_formatter` to produce structured final output, or `action_mode: "require_approval"` for [human sign-off](/docs/production/approvals).

**`create-agent.sh (lines 1-13)`**

```bash create-agent.sh (lines 1-13)
curl --fail-with-body -X POST https://app.rightbrain.ai/api/v1/org/$RB_ORG/project/$RB_PROJECT/task-agent \
  -H "Authorization: Bearer $RB_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Review Triage Agent",
    "instruction": "You triage inbound customer reviews. For each review, use the sentiment tool to classify it, then summarize what the customer is unhappy about and whether it needs escalation.",
    "llm_model_id": "'"$RB_MODEL"'",
    "max_turns": 10,
    "task_tools": [
      { "task_id": "'"$RB_TASK"'", "revision_strategy": "follow_active" }
    ]
  }' > agent.json &&
export RB_AGENT="$(jq -er .id agent.json)"
```

#### Run it and stream the events

Send a review in `message`. An agent takes turns and calls tools, so the response is Server-Sent Events rather than a single JSON body. `-N` makes cURL show events as they arrive.

Run this command once to start work. An HTTP success does not guarantee a successful run: inspect the events for `done`, `error`, or `approval_required`.

**`run-agent.sh (lines 1-5)`**

```bash run-agent.sh (lines 1-5)
curl --fail-with-body -N -X POST "https://app.rightbrain.ai/api/v1/org/$RB_ORG/project/$RB_PROJECT/task-agent/$RB_AGENT/run" \
  -H "Authorization: Bearer $RB_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: text/event-stream" \
  -d '{"message": "Triage this review: My toaster exploded during breakfast and set the bread on fire."}'
```

#### Follow the stream

This is a representative event sequence; tool names and results depend on your task. Each `data:` frame contains JSON with an `event_type` and ends with a blank line.

Save `metadata.run_id` from `done` for the next step. Save `session_id` to continue the conversation by sending it with a subsequent run. An `approval_required` event pauses work; an `error` event means the run failed.

**`events.txt (lines 1-9)`**

```text events.txt (lines 1-9)
data: {"event_type": "session_id", "metadata": {"session_id": "01909843-3596-da54-4756-28af46917e74"}}

data: {"event_type": "tool_call", "tool_name": "sentiment_analyzer", "tool_display_name": "Sentiment Analyzer", "tool_args": {"customer_review": "My toaster exploded..."}, "tool_call_id": "call_01"}

data: {"event_type": "tool_result", "tool_name": "sentiment_analyzer", "tool_outcome": "success", "tool_call_id": "call_01", "tool_result": {"sentiment": "negative"}}

data: {"event_type": "text", "content": "This review is negative and reports a safety hazard; it should be escalated."}

data: {"event_type": "done", "metadata": {"run_id": "0195d207-32bb-d03d-cfdc-f4516e9222c8", "session_id": "01909843-3596-da54-4756-28af46917e74", "duration_ms": 8421, "total_tokens": 5120}}
```

#### Fetch the run afterward

Replace `{run_id}` with the recorded run ID. Fetch the run for its outcome, then fetch its events for the transcript.

If the stream disconnects before a terminal event, inspect the run before retrying. When no run ID arrived, use the [run history endpoints](/docs/api/run-agents) to find it; starting a new run creates new work.

**`inspect-run.sh (lines 1-6)`**

```bash inspect-run.sh (lines 1-6)
export RB_RUN="{run_id}"
curl --fail-with-body "https://app.rightbrain.ai/api/v1/org/$RB_ORG/project/$RB_PROJECT/task-agent/$RB_AGENT/run/$RB_RUN" \
  -H "Authorization: Bearer $RB_TOKEN"

curl --fail-with-body "https://app.rightbrain.ai/api/v1/org/$RB_ORG/project/$RB_PROJECT/task-agent/$RB_AGENT/run/$RB_RUN/events?include_history=true" \
  -H "Authorization: Bearer $RB_TOKEN"
```

#### Complete files

**`create-agent.sh`**

```bash title="create-agent.sh"
curl --fail-with-body -X POST https://app.rightbrain.ai/api/v1/org/$RB_ORG/project/$RB_PROJECT/task-agent \
  -H "Authorization: Bearer $RB_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Review Triage Agent",
    "instruction": "You triage inbound customer reviews. For each review, use the sentiment tool to classify it, then summarize what the customer is unhappy about and whether it needs escalation.",
    "llm_model_id": "'"$RB_MODEL"'",
    "max_turns": 10,
    "task_tools": [
      { "task_id": "'"$RB_TASK"'", "revision_strategy": "follow_active" }
    ]
  }' > agent.json &&
export RB_AGENT="$(jq -er .id agent.json)"
```

**`run-agent.sh`**

```bash title="run-agent.sh"
curl --fail-with-body -N -X POST "https://app.rightbrain.ai/api/v1/org/$RB_ORG/project/$RB_PROJECT/task-agent/$RB_AGENT/run" \
  -H "Authorization: Bearer $RB_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: text/event-stream" \
  -d '{"message": "Triage this review: My toaster exploded during breakfast and set the bread on fire."}'
```

**`events.txt`**

```text title="events.txt"
data: {"event_type": "session_id", "metadata": {"session_id": "01909843-3596-da54-4756-28af46917e74"}}

data: {"event_type": "tool_call", "tool_name": "sentiment_analyzer", "tool_display_name": "Sentiment Analyzer", "tool_args": {"customer_review": "My toaster exploded..."}, "tool_call_id": "call_01"}

data: {"event_type": "tool_result", "tool_name": "sentiment_analyzer", "tool_outcome": "success", "tool_call_id": "call_01", "tool_result": {"sentiment": "negative"}}

data: {"event_type": "text", "content": "This review is negative and reports a safety hazard; it should be escalated."}

data: {"event_type": "done", "metadata": {"run_id": "0195d207-32bb-d03d-cfdc-f4516e9222c8", "session_id": "01909843-3596-da54-4756-28af46917e74", "duration_ms": 8421, "total_tokens": 5120}}
```

**`inspect-run.sh`**

```bash title="inspect-run.sh"
export RB_RUN="{run_id}"
curl --fail-with-body "https://app.rightbrain.ai/api/v1/org/$RB_ORG/project/$RB_PROJECT/task-agent/$RB_AGENT/run/$RB_RUN" \
  -H "Authorization: Bearer $RB_TOKEN"

curl --fail-with-body "https://app.rightbrain.ai/api/v1/org/$RB_ORG/project/$RB_PROJECT/task-agent/$RB_AGENT/run/$RB_RUN/events?include_history=true" \
  -H "Authorization: Bearer $RB_TOKEN"
```

## Python and TypeScript

To create the same agent from an application, use either example below and save the returned ID as `RB_AGENT`.

**`Python`**

```python title="Python"
import os, requests

base = f"https://app.rightbrain.ai/api/v1/org/{os.environ['RB_ORG']}/project/{os.environ['RB_PROJECT']}"
headers = {"Authorization": f"Bearer {os.environ['RB_TOKEN']}"}

payload = {
    "name": "Review Triage Agent",
    "instruction": "You triage inbound customer reviews. For each review, use the sentiment tool to classify it, then summarize what the customer is unhappy about and whether it needs escalation.",
    "llm_model_id": os.environ["RB_MODEL"],
    "max_turns": 10,
    "task_tools": [
        {"task_id": os.environ["RB_TASK"], "revision_strategy": "follow_active"}
    ],
}

response = requests.post(f"{base}/task-agent", headers=headers, json=payload)
response.raise_for_status()
agent = response.json()
print(agent["id"])
```

**`TypeScript`**

```typescript title="TypeScript"
const base = `https://app.rightbrain.ai/api/v1/org/${process.env.RB_ORG}/project/${process.env.RB_PROJECT}`;

const response = await fetch(`${base}/task-agent`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.RB_TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    name: "Review Triage Agent",
    instruction:
      "You triage inbound customer reviews. For each review, use the sentiment tool to classify it, then summarize what the customer is unhappy about and whether it needs escalation.",
    llm_model_id: process.env.RB_MODEL,
    max_turns: 10,
    task_tools: [{ task_id: process.env.RB_TASK, revision_strategy: "follow_active" }],
  }),
});

if (!response.ok) throw new Error(`Create agent: HTTP ${response.status}`);
const agent = await response.json();
console.log(agent.id);
```

### Read the event stream in your application

These readers handle completion, approval pauses, failures and interrupted streams. For multi-turn sessions and file inputs, see [Run agents via the API](/docs/api/run-agents).

Set `RB_TOKEN`, `RB_ORG`, `RB_PROJECT`, and `RB_AGENT` to your credential and resource IDs. These examples send a review-triage message; change it to match your agent. Python requires `httpx`; TypeScript uses the built-in `fetch` in Node.js 22+.

**`cURL`**

```bash title="cURL"
curl --fail-with-body -N -X POST "https://app.rightbrain.ai/api/v1/org/$RB_ORG/project/$RB_PROJECT/task-agent/$RB_AGENT/run" \
  -H "Authorization: Bearer $RB_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: text/event-stream" \
  -d '{"message": "Triage this review: My toaster exploded during breakfast and set the bread on fire."}'
```

**`Python`**

```python title="Python"
import json
import os
import httpx


def read_agent_stream(response):
    response.raise_for_status()
    if "text/event-stream" not in response.headers.get("content-type", ""):
        raise RuntimeError("Expected a text/event-stream response")

    session_id = None
    data = []
    for line in response.iter_lines():
        if line.startswith("data:"):
            data.append(line[5:].removeprefix(" "))
        elif line == "" and data:
            event = json.loads("\n".join(data))
            data = []
            kind = event["event_type"]
            if kind == "session_id":
                session_id = event["metadata"]["session_id"]
            elif kind == "text":
                print(event.get("content") or "", end="", flush=True)
            elif kind == "formatted_output":
                print("\nStructured output:", event.get("content"))
            elif kind == "error":
                raise RuntimeError(f"Agent run failed: {event.get('error')}")
            elif kind in ("done", "approval_required"):
                return {
                    "status": "completed" if kind == "done" else "waiting_for_human",
                    "session_id": session_id,
                    "metadata": event.get("metadata"),
                }
            elif kind in ("tool_call", "tool_result"):
                print("\nTool event:", event)
            # Ignore unknown event types so new events remain compatible.

    raise RuntimeError("Stream ended without done or approval_required; inspect the run before retrying")


base = f"https://app.rightbrain.ai/api/v1/org/{os.environ['RB_ORG']}/project/{os.environ['RB_PROJECT']}"
url = f"{base}/task-agent/{os.environ['RB_AGENT']}/run"
headers = {
    "Authorization": f"Bearer {os.environ['RB_TOKEN']}",
    "Accept": "text/event-stream",
}
payload = {"message": "Triage this review: My toaster exploded during breakfast and set the bread on fire."}

with httpx.stream("POST", url, headers=headers, json=payload, timeout=None) as response:
    outcome = read_agent_stream(response)
    print("\nOutcome:", outcome)
```

**`TypeScript`**

```typescript title="TypeScript"
async function readAgentStream(response: Response) {
  if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
  if (!response.headers.get("content-type")?.includes("text/event-stream") || !response.body) {
    throw new Error("Expected a text/event-stream response");
  }

  const reader = response.body.getReader();
  const decoder = new TextDecoder();
  let buffer = "";
  let data: string[] = [];
  let sessionId: string | undefined;

  try {
    while (true) {
      const { value, done } = await reader.read();
      buffer += done ? decoder.decode() : decoder.decode(value, { stream: true });
      let newline: number;
      while ((newline = buffer.indexOf("\n")) !== -1) {
        const line = buffer.slice(0, newline).replace(/\r$/, "");
        buffer = buffer.slice(newline + 1);
        if (line.startsWith("data:")) {
          data.push(line.slice(5).replace(/^ /, ""));
        } else if (line === "" && data.length) {
          const event = JSON.parse(data.join("\n"));
          data = [];
          switch (event.event_type) {
            case "session_id":
              sessionId = event.metadata.session_id;
              break;
            case "text":
              process.stdout.write(event.content ?? "");
              break;
            case "formatted_output":
              console.log("\nStructured output:", event.content);
              break;
            case "error":
              throw new Error(`Agent run failed: ${event.error}`);
            case "done":
            case "approval_required":
              return {
                status: event.event_type === "done" ? "completed" : "waiting_for_human",
                sessionId,
                metadata: event.metadata,
              };
            case "tool_call":
            case "tool_result":
              console.log("\nTool event:", event);
              break;
            // Ignore unknown event types so new events remain compatible.
          }
        }
      }
      if (done) throw new Error("Stream ended without done or approval_required; inspect the run before retrying");
    }
  } finally {
    await reader.cancel().catch(() => {});
    reader.releaseLock();
  }
}

const base = `https://app.rightbrain.ai/api/v1/org/${process.env.RB_ORG}/project/${process.env.RB_PROJECT}`;
const response = await fetch(`${base}/task-agent/${process.env.RB_AGENT}/run`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.RB_TOKEN}`,
    "Content-Type": "application/json",
    Accept: "text/event-stream",
  },
  body: JSON.stringify({
    message: "Triage this review: My toaster exploded during breakfast and set the bread on fire.",
  }),
});
const outcome = await readAgentStream(response);
console.log("\nOutcome:", outcome);
```

HTTP success only means the stream opened. The cURL example prints events; its exit code does not tell you whether the agent completed. The Python and TypeScript readers return `completed` on `done`, return `waiting_for_human` on `approval_required`, and raise an error on a run failure or unexpected end of stream. `formatted_output.content` is a string; parse it separately if your formatter produces JSON.

## Where to go next

#### [Agents](/docs/concepts/agents)

How agents use Tasks, Skills, Connections, Collections, and built-in tools.

#### [Skills](/docs/concepts/skills)

Add reusable procedures and selectively loaded reference material.

#### [Connections](/docs/concepts/connections)

Give an agent controlled access to Integrations and MCP servers.

#### [Collections](/docs/concepts/collections)

Ground an agent through a Collection-backed Task tool.

#### [Approvals (HITL)](/docs/production/approvals)

Pause runs on sensitive tool calls and resume after review.

#### [Run agents via the API](/docs/api/run-agents)

Multi-turn sessions, file inputs, and the full event reference.

#### [Triggers and runs](/docs/concepts/triggers-and-runs)

Start agent runs from webhooks, schedules, and inbound email.