Agent Loop
The agent loop is the core execution cycle that powers AI agents. It’s a simple but powerful pattern: call the model, execute any requested tools, send results back, repeat.
The Loop Pattern
Section titled “The Loop Pattern”An agent is not about a single LLM call. It’s about the loop:
The loop continues until the model produces a response without tool calls — indicating it has completed the task or needs more input from the user.
Event Flow
Section titled “Event Flow”The agentLoop function emits events throughout execution:
Basic Execution
Section titled “Basic Execution”import { agentLoop } from '@philschmid/agents-core';
const stream = agentLoop( [{ type: 'text', text: 'What is 25 * 17?' }], { model: 'gemini-3-flash-preview', tools: [calculateTool], });
for await (const event of stream) { console.log(event.type, event);}Configuration Options
Section titled “Configuration Options”| Option | Type | Description |
|---|---|---|
model | string | Model ID (e.g., 'gemini-3-flash-preview') |
tools | AgentTool[] | Available tools |
systemInstruction | string | System prompt |
previousInteractionId | string | Resume from previous interaction |
transformContext | function | Modify context before each LLM call |
Interaction Lifecycle
Section titled “Interaction Lifecycle”Each iteration of the loop is an interaction — a single LLM call that may produce text, tool calls, or both.
Interaction States
Section titled “Interaction States”- Start: Request sent to Gemini API
- Streaming: Receiving
text.deltaevents - Tool Calls: Model requests function execution
- End: Response complete
The Interactions API maintains state server-side. You don’t need to resend the full conversation history — just pass previous_interaction_id.
Tool Execution
Section titled “Tool Execution”When the model returns a function_call, the agent loop:
- Looks up the tool by name
- Calls
tool.execute(id, args, signal, onUpdate) - Captures the result (or error)
- Submits the result back to the model
const calculateTool: AgentTool = { name: 'calculate', label: 'Calculate', description: 'Perform math calculations', parameters: { type: 'object', properties: { expression: { type: 'string' } }, required: ['expression'] }, execute: async (_id, args) => { const result = eval(args.expression as string); return { result: String(result) }; }};Stream Consumption
Section titled “Stream Consumption”The agent loop returns an AsyncGenerator that yields events:
const stream = agentLoop(input, config);
// Collect all eventsconst events: AgentEvent[] = [];for await (const event of stream) { events.push(event);}
// Get final resultconst lastEvent = events.at(-1);if (lastEvent?.type === 'agent.end') { console.log('Turns:', lastEvent.interactions.length);}Helper: printStream
Section titled “Helper: printStream”For quick prototyping, use the built-in printer:
import { printStream } from '@philschmid/agents-core';
const result = await printStream(stream, { verbosity: 'verbose' });Error Handling
Section titled “Error Handling”Tool errors are captured and returned to the model:
execute: async (_id, args) => { try { const data = await fetchData(args.url); return { result: JSON.stringify(data) }; } catch (error) { return { result: `Error: ${error.message}`, isError: true }; }}The model can then decide how to proceed — retry, use a different tool, or report the error to the user.
Next Steps
Section titled “Next Steps”- Defining Tools: Tool interface and best practices
- Hooks System: Intercept the loop at each stage
- Streaming Guide: Handle events in your UI