The Agent Loop
ra's core loop is simple: send messages to the model, stream the response, execute any tool calls, repeat. Every step fires a middleware hook you can intercept. The loop handles iteration, token tracking, and tool execution — you control everything else through system prompts, skills, and middleware.
┌─────────────────────────────────────────────────┐
│ beforeLoopBegin │
└──────────────────────┬──────────────────────────┘
▼
┌─── beforeModelCall ◄────────────┐
│ │
▼ │
Stream response │
(onStreamChunk) │
│ │
▼ │
afterModelResponse │
│ │
├── No tool calls? ──► afterLoopComplete
│
▼
beforeToolExecution
│
▼
Execute tools
│
├── ask_user? ──► suspend (loop exits without afterLoopComplete)
│
▼
afterToolExecution
│
▼
afterLoopIteration ────────────────────►┘How it works
Start —
beforeLoopBeginfires once. Your middleware can set up logging, validate config, or inject initial context.Model call —
beforeModelCallfires with the full request (messages, tools, model). The model streams its response, firingonStreamChunkfor every token. When the response is complete,afterModelResponsefires.Tool execution — If the model requested tool calls,
beforeToolExecutionfires for each one, then the tool runs, thenafterToolExecutionfires with the result.Iterate or complete — If tools were called,
afterLoopIterationfires and the loop goes back to step 2 with the tool results appended to the conversation. If no tools were called,afterLoopCompletefires and the loop ends.Suspend — The
AskUserQuestiontool is special: it suspends the loop and returns control to the caller without firingafterLoopComplete. The session is saved so you can resume later.
Loop controls
The loop tracks token usage per iteration, enforces maxIterations, and supports an AbortController. Any middleware can call ctx.stop() to halt the loop cleanly.
maxIterations: 50 # default — prevents runaway loops
toolTimeout: 30000 # per-tool timeout in ms// middleware/budget.ts — stop if we've used too many tokens
export default async (ctx) => {
if (ctx.loop.usage.inputTokens + ctx.loop.usage.outputTokens > 100_000) {
ctx.stop()
}
}See also
- Middleware — all available hooks and their context shapes
- Built-in Tools — tools available to the agent
- Context Control — how ra manages what the model sees
- Configuration —
maxIterationsandtoolTimeoutsettings