Skip to content

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.

An agent is not about a single LLM call. It’s about the loop:

Yes

No

User Input

Call LLM

Tool Calls?

Execute Tools

Submit Results

Return Response

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.


The agentLoop function emits events throughout execution:

ToolModelAgentUserToolModelAgentUseragent.startinteraction.starttool.starttool.endinteraction.endinteraction.startinteraction.endagent.endagentLoop(input, config)input + toolsthought.signaturefunction_callexecute(args)resulttool resulttext.delta (streaming)text.deltaStream complete


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);
}
OptionTypeDescription
modelstringModel ID (e.g., 'gemini-3-flash-preview')
toolsAgentTool[]Available tools
systemInstructionstringSystem prompt
previousInteractionIdstringResume from previous interaction
transformContextfunctionModify context before each LLM call

Each iteration of the loop is an interaction — a single LLM call that may produce text, tool calls, or both.

  1. Start: Request sent to Gemini API
  2. Streaming: Receiving text.delta events
  3. Tool Calls: Model requests function execution
  4. 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.


When the model returns a function_call, the agent loop:

  1. Looks up the tool by name
  2. Calls tool.execute(id, args, signal, onUpdate)
  3. Captures the result (or error)
  4. 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) };
}
};

The agent loop returns an AsyncGenerator that yields events:

const stream = agentLoop(input, config);
// Collect all events
const events: AgentEvent[] = [];
for await (const event of stream) {
events.push(event);
}
// Get final result
const lastEvent = events.at(-1);
if (lastEvent?.type === 'agent.end') {
console.log('Turns:', lastEvent.interactions.length);
}

For quick prototyping, use the built-in printer:

import { printStream } from '@philschmid/agents-core';
const result = await printStream(stream, { verbosity: 'verbose' });

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.