Skip to content
brain.GitHub
Developer docs OPEN SOURCE

Quickstart

Start Brain on your machine, create a Pi session, and stream the reply with the TypeScript or Python SDK.

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.

ConceptIn this guide
Host configexamples/local.json: one API token for the local tenant, the pi harness, the OpenAI provider, and no tools.
SessionOne conversation that uses the pi harness and the openai/gpt-4.1-mini model.
TurnThe work Brain runs for your message: model calls until Pi finishes its reply.
EventRecords such as assistant.message and session.status that the example reads from the session's stream.

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.

The SDKs install from npm and PyPI. This guide builds the server and Pi runner locally; platform examples use the published container image.

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.

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

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.
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

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.

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.

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.

export BRAIN_API_KEY="replace-with-a-random-token-of-at-least-24-bytes"
python -m pip install aex-brain==2.0.0

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.

npm install --no-save --package-lock=false @aexhq/brain@2.0.0
node examples/session.mjs "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:

Session: 7d1c9e04b3a24f6e8c5a0b2d9e3f4a61
A good agent tool does one clearly named job, ...

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:

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;
}

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

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.

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:

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:

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 for every event type.

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

On this page