Agent loop API
Model calls, tools, saved state and event handling through one context.
An agent loop chooses what to do with each message or background observation. Brain supplies its context, executes requested calls and saves explicit writes. Start with Write an agent loop for a complete example and build commands.
The context
Export turn = defineAgentloop(handler) from @aexhq/brain/agentloop. The handler receives one
ctx object with parsed values and scoped services. Return an optional JSON value as the run's
result; return without a value when no separate result is needed.
| Field | Value |
|---|---|
ctx.input | { message, media? }, or undefined for a background activation |
ctx.transcript | Saved model messages |
ctx.events | A finite batch of pending events, in sequence order |
ctx.configuration | Options supplied through the loop factory |
ctx.system | The session's system prompt |
ctx.tools | Tool definitions and authorized environments |
ctx.runtime | Logical time and a deterministic seed |
There is one activation at a time per session. Local variables and mutations of these input values do not persist across activations. Save conversation and KV changes explicitly.
Call Brain services
| Operation | Use |
|---|---|
await ctx.model(request) | Call the session model with your chosen messages |
await ctx.callTool(call) | Invoke one tool and receive its available observations |
await ctx.callTools(calls) | Invoke a batch together; returns stay in call order |
await ctx.readEvents(after) | Read a finite event page; continue from next_cursor |
await ctx.emit(kind, data) | Append an application-visible event |
await ctx.setTranscript(messages) | Save the selected conversation |
await ctx.kv.get(key) | Read a JSON value, or undefined if absent |
await ctx.kv.set(key, value) | Save one JSON value |
await ctx.kv.delete(key) | Remove a key; absence is a no-op |
await ctx.environments.list() | List visible environments; other operations use the same scoped service |
await ctx.telemetry(record) | Publish best-effort diagnostics |
Requests and responses are ordinary objects. The adapter handles JSON encoding and the
Component binding's sequence conversions. Model and tool shapes come from Brain's generated
contracts. KV stores JSON values; stored null is distinct from absence.
Model calls
Model calls record their request and response without editing the saved conversation. Append
selected replies to your messages and call ctx.setTranscript(messages). Auxiliary summaries
or classifications can remain separate.
Keep native model blocks unchanged when retaining context. Images and PDFs need their media blocks, not merely URL text. Brain does not host or refresh those files. See models for format and provider compatibility.
Tool calls
Each call supplies call_id, name, environment and input. Preserve the model's call ID
when executing its request, and use names and placements from ctx.tools. Dynamically created
environments also require their current environment_sequence.
A return contains call_id, the original call sequence, ordered events, and finished.
Read tool_result_emitted.data.result for a result and tool_call_ended.data.outcome for
completion. There may be several results or none. A successful empty finish is a completion
boundary, not a null result. Inspect failures and uncertainty before presenting success.
A tool may continue after releasing its caller. Later results can activate the loop without user input. The journal also contains observations already received through a tool call; the loop can remember what it presented in KV. Model-facing tool definitions do not grant execution authority. With several placements, your loop chooses one.
Event completion and saved state
Successful return completes the delivered event batch, including deliberately ignored events. Brain records that boundary automatically. Contiguous pages read during the activation join that batch. It never advances across an unread gap or to the journal's current head merely because the handler returned. Events that arrived without being delivered remain pending.
Failure or cancellation leaves the batch incomplete. Earlier transcript and KV writes still persist, so a later activation may see observations it has previously encountered. Brain does not automatically retry the failed activation or its effects.
The loop owns any finer progress tracking. Save pending work, remembered observations or
skip decisions with ctx.kv.set. There is no public acknowledgement, partial checkpoint or
retry-policy API. Historical events remain readable after completion.
Each write commits independently before resolving. Separate writes are not a transaction. A model call, a local mutation or returning a result does not save conversation changes.
Environments and failures
ctx.environments exposes the operations in
environment control, under the loop's grants.
Tools and environment handlers use the same service vocabulary.
Brain records requested effects before dispatch and observations before returning them. It
sends each effect once. Propagate service errors unless the loop has a deliberate handling
policy. Cancellation or a reached budget makes service calls fail with cancelled or
decision_limit; cancellation does not undo external actions. Background tools retain their
original deadlines and budgets.
Test a second message, background activation, failed tool and cancellation as well as the first answer. The Rust reference loop shows a complete model-and-tool policy.
Runtime adapters and other languages
The JavaScript Component adapter is defineAgentloop. An HTTP runtime adapter can construct
the same author context with createAgentloopContext from @aexhq/brain/runtime, supplying
an invocation-scoped callback transport. See the
HTTP example.
Rust and Python build requirements are in the
language examples. Runtime implementers use
@aexhq/brain/contracts/agentloop.wit (brain:agentloop@0.2.0) and the JSON payload schemas in
@aexhq/brain/contracts/session.json. The raw Component imports belong to that adapter boundary.