Agent loops
An agent loop is a loop that extends Agent instead of Workflow. Instead of a single run() that executes once per trigger, agent loops handle chat turns — each message from the playground or an external channel calls onChatMessage() and streams a reply back.
Use agent loops for conversational experiences: health concierges, intake assistants, triage chatbots, and anything where the user sends multiple messages in a session.
Shape
export class HealthLoop extends Agent {
async onChatMessage({ messages, ctx }) {
const user = await ctx.USER.getUser({ id: "patient-123" });
const result = await ctx.OLLIE.chat({
thinking: "high",
system: "You're a health concierge.",
intent: ctx.intent,
messages,
user,
tools: ctx.tools(),
});
return result.toUIMessageStreamResponse();
}
}| Piece | Description |
|---|---|
messages | The chat transcript so far, in AI SDK UIMessage shape — flows unchanged from the web playground’s useChat. |
ctx.OLLIE.chat | Platform-managed chat completion. Returns a stream you hand back with toUIMessageStreamResponse(). |
ctx.intent | Optional intent string from the trigger or playground — use to branch behavior without changing the system prompt. |
ctx.body | The JSON body from the trigger, if any. |
ctx.USER | Same org-wide user store as workflow loops. See Users. |
this.env | Loop variables and secrets, same as workflow loops. See Variables and secrets. |
OLLIE.chat options
type OllieChatInput = {
thinking?: "low" | "medium" | "high";
system?: string;
intent?: string;
messages: UIMessage[];
user?: User;
tools?: LoopToolSpec[];
options?: {
/** Inject the user's most recent completed scan into the system context. */
useLatestScan?: boolean;
};
};| Option | Description |
|---|---|
thinking | Reasoning depth for the model. Higher = more thorough, slower. |
system | System prompt for this turn. Combine with the dashboard’s System prompt panel for a base prompt you extend in code. |
user | A User handle — required for useLatestScan and for threading identity across turns. |
tools | Tool specs from ctx.tools({ loops }) — see below. |
options.useLatestScan | When true, the platform injects the user’s latest completed facial scan (summary, measurements, intake) into context. |
Declare workflow and voice tools
Agent loops can trigger other loops mid-conversation. Declare them with ctx.workflow() or ctx.voice(), then pass them to ctx.tools():
async onChatMessage({ messages, ctx }) {
const intakeTool = ctx.workflow({
loopId: "intake-workflow-loop-id",
description: "Start a facial scan intake for the patient",
inputSchema: z.object({
patientId: z.string(),
phoneNumber: z.string(),
}),
});
const callTool = ctx.voice({
loopId: "intake-voice-loop-id",
description: "Start a voice intake call and return a join link",
inputSchema: z.object({ reason: z.string() }),
});
const result = await ctx.OLLIE.chat({
messages,
tools: ctx.tools({
loops: {
start_intake: intakeTool,
start_call: callTool,
},
}),
});
return result.toUIMessageStreamResponse();
}Workflow tools start a background run — the model receives { runId, workflowInstanceId, … }, not the child loop’s output. Voice tools return { runId, sessionId, joinUrl, callSid? } so the model can share the join link. Do not use workflow tools for synchronous API lookups where the model needs the response data inline (search, slots, booking). See Loop-as-tool for return shapes and when to call APIs directly instead.
Knowledge base
Upload documents under Storage → Knowledge base (PDF, TXT, or Markdown). At runtime, the platform exposes a searchKnowledgeBase tool to the model automatically — you don’t declare it in loop code. Use the knowledge base for clinic policies, product FAQs, dosing guides, or any reference material the agent should cite.
System prompt panel
Agent loops (and voice loops) have a System prompt panel on the /code route, separate from the loop’s TypeScript. The saved prompt is applied as the base system context for every turn. In code, pass an additional system string to OLLIE.chat to extend or override it for a specific turn.
Playground and sessions
Agent loops don’t produce runs in the workflow sense — they produce sessions (one per chat thread). Open the Playground tab to test turns interactively, and Sessions to browse past conversations.
To trigger an agent loop from the API, POST to the same loop trigger URL. The body becomes ctx.body; the response is a streaming chat reply rather than a runId.
Where to go next
- Loop-as-tool — declare other loops as tools, return shapes, and limitations.
- Users — carry memory and metadata across turns.
- Voice loops — voice tools and
ctx.startVoice. - Facial scan — pair with
useLatestScanso the agent speaks to scan results.