Aex Brain

Quickstart

Run an agent that looks up an order and tells you whether it has shipped.

Give an AI model a function from your app and watch it answer a real question. You need Docker, Node.js 22 or newer and an OpenAI API key; OpenAI bills model calls to your key. Rather not run a server? Follow the Aex quickstart instead.

1. Start Brain

docker run --rm -p 127.0.0.1:8080:8080 \
  -e BRAIN_LISTEN=0.0.0.0:8080 -e BRAIN_API_TOKEN=quickstart \
  -e BRAIN_AGENTLOOPS='{"pi":{"command":"brain-agentloop-pi"}}' \
  -v brain-data:/var/lib/brain ghcr.io/aexhq/brain-agentloops:latest

Leave this terminal running. The volume keeps saved sessions across restarts. The token is for local use only; read Operate Brain before exposing a server. Commands use Bash. In PowerShell, join multiline commands onto one line.

2. Install the client

In another terminal:

mkdir brain-example
cd brain-example
npm init -y
npm pkg set type=module
npm install @aexhq/brain@0.41.0 zod@4.4.3

Add the compiler and runner:

npm install --save-dev typescript@5.9.2 tsx@4 @types/node@22

Set your model key in the same terminal:

export OPENAI_API_KEY="your-key"

In PowerShell, use $env:OPENAI_API_KEY = "your-key". Never commit keys to source control.

3. Add a tool and send a message

Save this as order.ts. It's the complete order example, and it runs unchanged as JavaScript. The Zod input schema gives the tool its argument types.

import { Brain, tool } from "@aexhq/brain";
import { z } from "zod";

const apiKey = process.env.OPENAI_API_KEY;
if (!apiKey) throw new Error("OPENAI_API_KEY is required");

const lookupOrder = tool({
  name: "lookup_order",
  description: "Look up an order by id.",
  input: z.object({ id: z.string() }),
  run: ({ id }, ctx) => ctx.finish({ id, status: "shipped" }),
});

const brain = new Brain({ baseUrl: "http://127.0.0.1:8080", token: "quickstart" });
brain.sessions.create({
  agentloop: { name: "pi" },
  model: { provider: "openai", name: "gpt-5-mini", apiKey },
  tools: [lookupOrder()],
}).then(async session => {
  const after = session.state.lastSequence;
  await session.send("Look up order A-1001. Has it shipped?");
  for await (const event of session.events(after)) {
    if (event.type === "turn_failed") throw new Error(JSON.stringify(event.data));
  }
  console.log(JSON.stringify(await session.transcript(), null, 2));
  console.log("Session:", session.id);
}).catch(error => {
  console.error(error);
  process.exitCode = 1;
});

4. Run it

npx tsc --noEmit --strict --module NodeNext --target ES2023 order.ts
npx tsx order.ts

You'll see a lookup_order result in the transcript, followed by an answer saying order A-1001 has shipped. Exact wording varies by model. Now swap the sample return value for a real lookup in your backend.

What just happened

  • The agentloop ran on Brain. Startup registered Pi; the session selected it by name. Pi owns the agent loop and compaction; Brain executes and journals its model and tool calls.
  • The tool ran in your app. lookup_order executed in this Node.js process, and ctx.finish(value) returned its result and completed the call.
  • The script exited cleanly. After five idle seconds the client released its tool connection. The session and its history are still on the server.

To keep chatting, add another send() in the callback. A later send through the same live client reconnects before it starts work.

If other callers or future autonomous events need these tools, pass connectionIdleTimeoutMs: 0 to new Brain(...). Event subscriptions stay open until you stop them. brain.close() permanently disposes of the client, session.end() finishes the conversation and session.delete() removes its history. See Sessions.

Next steps

On this page