Aex Brain
Customize and operate

Write a tool

Add a function the agent can call, then run it in your app or package it for Brain.

Start with a JavaScript or TypeScript function. The example below returns sample order data; replace its body with your database or API call.

TypeScript and JavaScript

Complete the quickstart first. Save this as order-tool.ts beside order.ts. The same code works as JavaScript:

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

export 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" }),
});

Replace the quickstart's inline declaration with import { lookupOrder } from "./order-tool.js". Run npx tsx order.ts; NodeNext compilation resolves the .js import to the TypeScript source.

Keep the session's tool placement:

tools: [lookupOrder()],

The transcript includes the lookup result and an answer about the order. The tool runs in your process and can use its packages, credentials and working directory. It has no separate session workspace. Keep that process connected until the tool finishes; choose HTTP or remote execution when it should run independently.

Publish a tool library

Write and export tool({ ..., run }) using your normal TypeScript or JavaScript build. The runnable package example contains a filesystem tool, a normal tsc build, and two package exports. Clone the repository and run these commands from examples/packaged-tools, not the repository root. brain.tools in its package.json selects the executable export and factory names. After compilation, brain-tools generates the public client entry and its types. Build and inspect the release with:

npm install
npm run build
npm pack

Consumers import the factory from the package and call readText({ env: workspace }). Its browser entry does not import the filesystem implementation. readText() loads the executable in the registering app when that runtime supports it. The selected Environment prepares ordinary npm dependencies and loads the exact package version; an unavailable package or incompatible placement fails explicitly. Neither the Tool author nor its consumer writes an execution descriptor.

Match the package's Brain SDK version to its consumers. Before publishing, follow the example's fresh consumer check to catch incompatible factory and environment types. readText() uses the application's current working directory and permissions; remote placement follows that provider's workspace rules.

For a service that loads packages itself, see Write an environment.

Supply useful model context

Finish with the full structured value and optional short text: ctx.finish(value, { content: summary }). ctx.emitResult accepts the same option for partial results. The Agentloop decides where to place that text and when to compact it.

A Tool can call ctx.model with its own messages. The document summarizer is a complete example. Its request returns to that Tool; the Agentloop still owns the shared conversation.

Language choices

Other languages can supply JSON Schema metadata directly through bindTool; no Zod schema reconstruction is needed. The Python package example exports metadata from Python and runs in a prepared Python Environment. Its JavaScript caller is exercised in the cross-language test.

LanguageWhere it runsBuild path
JavaScript / TypeScriptYour application (hostEnv)The example above
RustBrain (brainEnv)Build the Rust example below
PythonBrain (brainEnv)Package the Python example below
A language in your own runtimeYour environment serviceWrite an environment

Rust and Python produce a .wasm extension file. You only need that packaging step when you want Brain to run the tool independently of your app. These are tested examples, not a promise that every library in those languages runs there.

Rust

Clone Brain and start from tests/fixtures/diagnostic-tool. Its echo path finishes with a JSON result:

fn finish(value: serde_json::Value) -> Result<String, ToolError> {
    brain::tool::host::finish(Some(
        &serde_json::json!({"status": "ok", "value": value}).to_string(),
    ))?;
    Ok(value.to_string())
}

The linked project includes the input handler, bindings and Cargo.toml. Edit its run function, then build from the Brain checkout with Rust 1.97 or newer:

rustup target add wasm32-wasip2
cargo build --manifest-path tests/fixtures/diagnostic-tool/Cargo.toml --target wasm32-wasip2 --release

Copy tests/fixtures/diagnostic-tool/target/wasm32-wasip2/release/diagnostic_tool.wasm into your app as echo.wasm.

Python

The complete Python tool returns its input and records a progress event:

import json
from wit_world import WitWorld
from wit_world.imports import host

class WitWorld(WitWorld):
    def run(self, input):
        value = json.loads(input.input_json)
        sequence = host.emit("python_tool_ran", json.dumps(value))
        result = {"echo": value, "sequence": sequence}
        host.finish(json.dumps({"status": "ok", "value": result}))
        return json.dumps(result)

Edit tests/fixtures/python/tool.py in a Brain checkout. With Python 3, venv and Bash available (Linux or WSL), build it with:

bash tools/build-python-fixtures.sh "$PWD/build/python"

The script installs the pinned builder and generates the imports. Copy build/python/python-tool.wasm into your app as echo.wasm. This example supports Python computation and Brain callbacks, not Python filesystem or network APIs. The server needs BRAIN_MAX_CORE_INSTANCES=1024; add that environment variable to the quickstart's Docker command. This requires a Brain server you control. For hosted runtimes, see the Aex docs. Use a Python environment service for OS packages and ordinary file/network access.

Use the built tool

Replace the quickstart's tool declaration and placement with:

import { component } from "@aexhq/brain";

const echo = tool({
  name: "echo",
  description: "Echo a message.",
  input: z.object({ message: z.string() }),
  implementation: component(new URL("./echo.wasm", import.meta.url)),
});

// In sessions.create:
// tools: [echo({ env: brainEnv({ name: "brain" }) })],

Send "Call echo with the message hello." and run the app. The transcript should contain hello in the tool result. The SDK uploads the file when it creates the session.

Return values and outcomes

Use ctx.finish(value) for ordinary successful output. Throw an error when the function cannot complete. Tools that continue in the background use ctx.emitResult(value) and eventually ctx.finish(). Returning alone leaves the tool open. Observe ctx.signal to stop on cancellation.

See the tool contract for structured errors, reserved status values, deadlines, background results and host reconnection.

On this page