Aex Brain
Concepts

Agent loop

The deterministic policy that owns context and decides what happens next.

An agent loop receives what just happened and returns one durable action. Its registration shape is the same as every other extension:

import { agentloop } from "@aexhq/brain";
import { z } from "zod";

export const simple = agentloop((author) => {
  const memory = author.state(z.object({ messages: z.array(z.unknown()) }), () => ({ messages: [] }));

  author.on.message((message, turn) => {
    memory.messages.push({ role: "user", content: [{ type: "text", text: String(message.content) }] });
    return turn.model({ messages: memory.messages });
  });

  author.on.model((completed, turn) => {
    memory.messages.push(completed.response.message);
    const text = completed.response.message.content
      .filter((block) => block.type === "text")
      .map((block) => block.text)
      .join("");
    return turn.reply(text);
  });
});

Messages are provider-neutral: a role and an array of content blocks (text, tool_use, tool_result). Brain renders them into the bound provider's wire format, so the same extension runs unchanged against every supported provider.

Handlers are synchronous. Model calls, Tool calls, emitted output, and cancellation complete as a later observation, so Brain can journal every effect before performing it and recover without a hidden JavaScript continuation.

author.state() is durable extension state. The messages passed to turn.model() are the context for that one model request. An extension can summarize or drop old messages without rewriting the append-only journal.

The compiled extension runs as a WebAssembly component with no filesystem, network, process, ambient clock, environment variables, or secrets. Pi and Codex use the same public API and runtime.