> For clean Markdown of any page, append .md to the page URL. > For a complete documentation index, see https://docs.rightbrain.ai/v-1/docs/quickstart/first-agent/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. > Build reliable AI agents that run inside your existing tools and workflows. Rightbrain developer documentation.