# Quickstart (/brain/docs/quickstart)

This guide walks you through running Brain on your machine and sending your first message. You start the server with the example host config, create a session that uses the Pi harness and an OpenAI model, and stream the reply with the TypeScript or Python SDK.

| Concept | In this guide |
| --- | --- |
| **Host config** | `examples/local.json`: one API token for the `local` tenant, the `pi` harness, the OpenAI provider, and no tools. |
| **Session** | One conversation that uses the `pi` harness and the `openai/gpt-4.1-mini` model. |
| **Turn** | The work Brain runs for your message: model calls until Pi finishes its reply. |
| **Event** | Records such as `assistant.message` and `session.status` that the example reads from the session's stream. |

## Prerequisites [#prerequisites]

- **Go 1.26 or newer** to run the server.
- **Node.js 24 or newer** to run the Pi runner and JavaScript/TypeScript client.
- **Python 3.11 or newer**, only if you use the Python SDK or run the full test suite.
- An **OpenAI API key**. To use another provider, see [Models & providers](/brain/docs/reference/models).

The SDKs install from npm and PyPI. This guide builds the server and Pi runner locally; [platform examples](/brain/docs/guides/platforms) use the published container image.

<Steps>
<Step>
### Get the code and build the Pi runner [#get-the-code-and-build-the-pi-runner]

Clone the repository and build the Pi runner. Brain starts shared runner hosts when sessions begin their first turn.

```sh
git clone https://github.com/aexhq/brain.git
cd brain
npm --prefix runners/pi ci --ignore-scripts
npm --prefix runners/pi run build
```
</Step>

<Step>
### Set your keys and start Brain [#set-your-keys-and-start-brain]

Brain reads two secrets from the environment:

- `BRAIN_API_KEY` is the token your application sends to Brain. It must be at least 24 bytes. Use a long random value anywhere other than your own machine.
- `OPENAI_API_KEY` is the model provider key that Brain passes to the Pi runner.

```sh tab="macOS / Linux"
export BRAIN_API_KEY="replace-with-a-random-token-of-at-least-24-bytes"
export OPENAI_API_KEY="your-provider-key"
go run ./cmd/brain -config examples/local.json
```

```powershell tab="Windows"
$env:BRAIN_API_KEY = "replace-with-a-random-token-of-at-least-24-bytes"
$env:OPENAI_API_KEY = "your-provider-key"
go run ./cmd/brain -config examples/local.json
```

When the server is ready, it writes a JSON log line with `"msg":"brain listening"` to stderr. With `examples/local.json`, Brain listens on `http://127.0.0.1:8080`, writes local state to `.brain/`, grants `BRAIN_API_KEY` the `local` tenant with permission to manage sessions, registers the `pi` harness, and enables no tools.

<Callout type="info" title="Customize the config">
Copy `examples/local.json` to `brain.json` to change settings without editing tracked files. `brain.json` is gitignored, and Brain loads it when you omit `-config`. Add `-check` to validate a config, including the environment variables it names, and exit without starting the server.
</Callout>
</Step>

<Step>
### Open a second terminal [#open-a-second-terminal]

Leave the server running. Open a second terminal in the repository root and set the same `BRAIN_API_KEY`. Install the Python package if you use the Python example.

```sh tab="macOS / Linux"
export BRAIN_API_KEY="replace-with-a-random-token-of-at-least-24-bytes"
python -m pip install aex-brain==2.0.0
```

```powershell tab="Windows"
$env:BRAIN_API_KEY = "replace-with-a-random-token-of-at-least-24-bytes"
python -m pip install aex-brain==2.0.0
```
</Step>

<Step>
### Run a session [#run-a-session]

Run the example script for your language. Install the published SDK for your language. If your system has no `python` command, use `python3`.

```sh tab="TypeScript"
npm install --no-save --package-lock=false @aexhq/brain@2.0.0
node examples/session.mjs "Explain what makes a good agent tool."
```

```sh tab="Python"
python examples/session.py "Explain what makes a good agent tool."
```

The script prints the session ID and the assistant's reply, then exits when the turn ends. Example output:

```text
Session: 7d1c9e04b3a24f6e8c5a0b2d9e3f4a61
A good agent tool does one clearly named job, ...
```
</Step>
</Steps>

## How the example works [#how-the-example-works]

Both scripts create a session, send one message, and read events until the turn for that message ends. Here are the same steps with comments:

```ts tab="TypeScript"
import { Brain } from "@aexhq/brain";

const token = process.env.BRAIN_API_KEY;
if (!token) throw new Error("Set BRAIN_API_KEY before running this example");

// Connect with the token that the host config grants to the "local" tenant.
const brain = new Brain("http://127.0.0.1:8080", token);

// Create a session with the harness, model, and tools the agent should use.
const session = await brain.create({ harness: "pi", config: { model: "openai/gpt-4.1-mini", tools: [] } });
console.log(`Session: ${session.id}`);

// Send a message. The random key makes the request safe to retry.
const input = await brain.send(session.id, { type: "user.message", text: "Say hello.", key: crypto.randomUUID() });

// Read events until the turn for this message ends.
for await (const event of brain.observe(session.id)) {
  const data = event.data as { text?: string; message?: string; status?: string };
  if (event.type === "assistant.message") console.log(data.text);
  if (event.input_id !== input.id) continue;
  if (event.type === "session.error") throw new Error(data.message);
  if (event.type === "session.status" && data.status === "idle") break;
}
```

```python tab="Python"
import os
import uuid

from brain import Brain

# Connect with the token that the host config grants to the "local" tenant.
brain = Brain("http://127.0.0.1:8080", os.environ["BRAIN_API_KEY"])

# Create a session with the harness, model, and tools the agent should use.
session = brain.create({"harness": "pi", "config": {"model": "openai/gpt-4.1-mini", "tools": []}})
print("Session:", session["id"])

# Send a message. The random key makes the request safe to retry.
message = brain.send(session["id"], {"type": "user.message", "text": "Say hello.", "key": str(uuid.uuid4())})

# Read events until the turn for this message ends.
stream = brain.observe(session["id"])
try:
    for event in stream:
        if event["type"] == "assistant.message":
            print(event["data"]["text"])
        if event.get("input_id") != message["id"]:
            continue
        if event["type"] == "session.error":
            raise RuntimeError(event["data"]["message"])
        if event["type"] == "session.status" and event["data"]["status"] == "idle":
            break
finally:
    stream.close()
```

The scripts in `examples/` take the prompt from their arguments. `examples/session.mjs` also reads a comma-separated list of tool names from `BRAIN_TOOLS`, and `examples/session.py` reads `BRAIN_URL` and `BRAIN_MODEL`. Ending the loop doesn't close the session; it stays available for more messages.

## Call the HTTP API with cURL [#call-the-http-api-with-curl]

The SDKs wrap Brain's HTTP API. You can make the same calls with cURL from a macOS or Linux shell.

Create a session. Brain returns `201 Created` with the session; save its `id`.

```sh
curl http://127.0.0.1:8080/sessions \
  -H "Authorization: Bearer $BRAIN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"harness":"pi","config":{"model":"openai/gpt-4.1-mini","tools":[]}}'
```

Send a message using the returned session `id`:

```sh
curl http://127.0.0.1:8080/sessions/SESSION_ID/events \
  -H "Authorization: Bearer $BRAIN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type":"user.message","text":"Hello","key":"first-message"}'
```

`POST /sessions/{id}/events` returns `202 Accepted` with the saved `user.message` event once the message is stored and queued. The event's `id` is the `input_id` on the events of the turn it starts.

Stream the session's events:

```sh
curl -N http://127.0.0.1:8080/sessions/SESSION_ID/events \
  -H "Authorization: Bearer $BRAIN_API_KEY" \
  -H "Accept: text/event-stream"
```

The stream replays the session's retained events, then follows new events as they happen. Without `Accept: text/event-stream`, the same URL returns a JSON page of retained events. See [Events & streaming](/brain/docs/reference/events) for every event type.

## What happened [#what-happened]

1. **Brain loaded the host config.** It checked that `BRAIN_API_KEY` and `OPENAI_API_KEY` are set, registered the `pi` harness, and opened local state in `.brain/`.
2. **The script created a session.** Brain stored it with status `idle` and returned `201 Created`.
3. **The script sent a message.** Brain saved a `user.message` event, queued it, and returned `202 Accepted`.
4. **Brain ran a turn.** It emitted `session.status` with `running`, started the Pi runner, and streamed `assistant.delta` events while the model wrote. The complete reply arrived as `assistant.message`.
5. **The session went idle.** A `session.status` event with `idle` and the message's `input_id` ended the turn. The session stays open for more messages until you close it.

## Next steps [#next-steps]

<Cards>
  <Card icon={<Wrench />} title="Add a tool" href="/docs/tools/local-tools">Run the local tool server with `echo`, `inspect_session`, and `ask_approval`.</Card>
  <Card icon={<MessagesSquare />} title="Session lifecycle" href="/docs/guides/sessions">Queue messages, interrupt turns, resume, and close sessions.</Card>
  <Card icon={<Braces />} title="TypeScript SDK" href="/docs/sdks/typescript">Call Brain from a Node.js or TypeScript backend.</Card>
  <Card icon={<Code />} title="Python SDK" href="/docs/sdks/python">Call Brain from a Python backend.</Card>
  <Card icon={<Database />} title="Persistence & recovery" href="/docs/guides/persistence">Back up checkpoints and restore sessions on another server.</Card>
  <Card icon={<BookOpen />} title="HTTP API" href="/docs/api">Every route, request body, and response.</Card>
</Cards>
