Skip to content

Streaming Guide

The agent loop returns an async generator that yields events as they happen. Use these to build responsive UIs.

EventDescription
agent.startAgent loop begins
interaction.startLLM call begins
thought.summaryModel reasoning (if available)
text.deltaStreaming text chunk
tool.startTool execution begins
tool.deltaTool progress update
tool.endTool execution completes
interaction.endLLM call completes
agent.endAgent loop finishes with result

import { agentLoop } from '@philschmid/agents-core';
const stream = agentLoop(input, config);
for await (const event of stream) {
switch (event.type) {
case 'text.delta':
process.stdout.write(event.delta);
break;
case 'tool.start':
console.log(`\nCalling: ${event.name}`);
break;
case 'agent.end':
console.log('\nDone!');
break;
}
}

Streaming text from the model:

case 'text.delta':
// event.delta is a string chunk
process.stdout.write(event.delta);
break;

Tool execution begins:

case 'tool.start':
// event.name - tool name
// event.id - unique tool call ID
// event.arguments - parsed args
console.log(`Tool: ${event.name}(${JSON.stringify(event.arguments)})`);
break;

Tool execution completes:

case 'tool.end':
// event.name - tool name
// event.result - AgentToolResult
const preview = event.result.result.slice(0, 100);
console.log(`Result: ${preview}`);
break;

Agent loop finishes:

case 'agent.end':
// event.interactions - all conversation turns
// event.interactionId - final interaction ID
// event.usage - token usage statistics
console.log(`Done! ${event.interactions.length} turns`);
break;

For quick prototyping, use the built-in printer:

import { printStream } from '@philschmid/agents-core';
const result = await printStream(stream, { verbosity: 'verbose' });
LevelShows
'minimal'Text only
'normal'Text + tool names
'verbose'Text + full tool details

Collect all events for later processing:

const events: AgentEvent[] = [];
for await (const event of stream) {
events.push(event);
}
// Find all tool calls
const toolCalls = events.filter(e => e.type === 'tool.start');
console.log(`Made ${toolCalls.length} tool calls`);
// Get final result
const endEvent = events.find(e => e.type === 'agent.end');
console.log('Result:', endEvent?.result);

function AgentOutput({ stream }) {
const [text, setText] = useState('');
const [tools, setTools] = useState<string[]>([]);
useEffect(() => {
(async () => {
for await (const event of stream) {
switch (event.type) {
case 'text.delta':
setText(prev => prev + event.delta);
break;
case 'tool.start':
setTools(prev => [...prev, event.name]);
break;
}
}
})();
}, [stream]);
return (
<div>
<div className="tools">
{tools.map((tool, i) => <span key={i}>🔧 {tool}</span>)}
</div>
<div className="text">{text}</div>
</div>
);
}
for await (const event of stream) {
switch (event.type) {
case 'tool.start':
process.stdout.write(`\r${event.name}...`);
break;
case 'tool.end':
process.stdout.write(`\r${event.name}\n`);
break;
case 'text.delta':
process.stdout.write(event.delta);
break;
}
}

Abort a running agent:

const controller = new AbortController();
// Start agent
const stream = agentLoop(input, { ...config, signal: controller.signal });
// Cancel after 10 seconds
setTimeout(() => controller.abort(), 10000);
try {
for await (const event of stream) {
// Handle events
}
} catch (error) {
if (error.name === 'AbortError') {
console.log('Agent cancelled');
}
}

When cancelled, tool executions in progress will receive the abort signal. Make sure your tools handle this gracefully.