Skip to content

Session

The AgentSession class provides multi-turn agent conversations with hooks, built-in tools, and extensibility.

import { createAgentSession } from '@philschmid/agent';
const session = createAgentSession({
model: 'gemini-3-flash-preview',
tools: ['read', 'write', 'bash'],
systemInstruction: 'You are a helpful coding assistant.',
});
session.send('List files in src/');
for await (const event of session.stream()) {
if (event.type === 'text.delta') {
process.stdout.write(event.delta);
}
}

Use createAgentSession() or new AgentSession():

import { createAgentSession, AgentSession } from '@philschmid/agent';
// Factory function (recommended)
const session = createAgentSession({
model: 'gemini-3-flash-preview',
tools: ['read', 'bash'],
});
// Or direct instantiation
const session = new AgentSession({
model: 'gemini-3-flash-preview',
tools: ['read', 'bash'],
});
type AgentSessionOptions = {
model: string; // Required: model ID
tools?: ToolName[]; // Built-in tool names (default: from config)
systemInstruction?: string; // System prompt
maxInjectionLoops?: number; // Max onAgentEnd loops (default: 3)
cwd?: string; // Working directory for file tools (default: process.cwd())
};

Set cwd to specify the working directory for file-based tools. This is useful when the agent operates on a project that isn’t in the current process directory.

const session = createAgentSession({
model: 'gemini-3-flash-preview',
cwd: '/path/to/project', // All file tools use this as base directory
tools: ['read', 'write', 'bash'],
});
// cwd is immutable after creation
console.log(session.cwd); // '/path/to/project'

The cwd option affects all file-based tools: read_file, list_directory, write_file, apply_patch, bash, and grep. Relative paths in tool arguments are resolved from this directory.


The session uses a pull-based design:

MethodWhat it does
send(input)Queues a message: no API calls happen
stream()Processes the queue: runs the agent loop

Calling send() without stream() does nothing. The message sits in a queue. All work (API calls, tool execution, hooks) happens when you iterate stream().

// Queue message
session.send('Analyze this code');
// Process and stream events
for await (const event of session.stream()) {
switch (event.type) {
case 'text.delta':
process.stdout.write(event.delta);
break;
case 'tool.start':
console.log(`Tool: ${event.name}`);
break;
}
}
// First turn
session.send('List files');
for await (const event of session.stream()) { /* ... */ }
// Second turn - maintains context
session.send('Now read package.json');
for await (const event of session.stream()) { /* ... */ }

Specify tools by name:

const session = createAgentSession({
tools: ['read', 'write', 'bash', 'grep'],
});
NameTool(s)Description
readread_file, list_directoryRead files and list directories
writewrite_file, apply_patchCreate new files and patch existing files
bashbashExecute shell commands
grepgrepSearch file contents with regex patterns
sleepsleepPause execution for up to 60 seconds
planupdate_planTrack task progress with step statuses
web_searchweb_searchSearch the web
web_fetchweb_fetchFetch and convert web pages to markdown
skillsskillsLoad skills from .agent/skills/
subagentsubagentDelegate to subagents from .agent/subagents/

Register hooks with .on():

const session = createAgentSession({
tools: ['read', 'bash'],
});
// Log all tool calls
session.on('beforeToolExecute', (event) => {
console.log(`[TOOL] ${event.toolName}(${JSON.stringify(event.arguments)})`);
return { allow: true };
});
// Block destructive commands
session.on('beforeToolExecute', (event) => {
if (event.toolName === 'bash') {
const cmd = event.arguments.command as string;
if (cmd.includes('rm -rf')) {
return { allow: false, reason: 'Blocked' };
}
}
return { allow: true };
});
// Inject follow-up after completion
session.on('onAgentEnd', (event) => {
if (event.interactionCount < 2) {
return { input: 'Now verify your changes.' };
}
return {};
});

Hooks are chainable:

session
.on('beforeToolExecute', logHandler)
.on('beforeToolExecute', approvalHandler)
.on('onAgentEnd', verifyHandler);

Include the skills tool to load skill artifacts:

const session = createAgentSession({
tools: ['read', 'bash', 'skills'],
systemInstruction: 'You have access to skills for specialized tasks.',
});

Skills are loaded from .agent/skills/ directories. Each skill has a SKILL.md with instructions.

session.send('Use the code-review skill to analyze src/index.ts');

Include the subagent tool to delegate tasks:

const session = createAgentSession({
tools: ['read', 'bash', 'subagent'],
});

Subagents are loaded from .agent/subagents/ files. Each subagent has specialized capabilities.

session.send('Delegate the documentation task to the docs-writer subagent');

The session emits events during streaming:

EventDescription
agent.startAgent loop begins
interaction.startLLM call begins
text.deltaStreaming text chunk
tool.startTool execution begins
tool.endTool execution completes
interaction.endLLM call completes
agent.endAgent loop finishes
for await (const event of session.stream()) {
switch (event.type) {
case 'text.delta':
process.stdout.write(event.delta);
break;
case 'tool.start':
console.log(`Starting: ${event.name}`);
break;
case 'tool.end':
console.log(`Completed: ${event.name}`);
break;
case 'agent.end':
console.log('Done!', event.result);
break;
}
}

import { createAgentSession } from '@philschmid/agent';
const session = createAgentSession({
model: 'gemini-3-flash-preview',
tools: ['read', 'write', 'bash', 'skills'],
systemInstruction: 'You are a senior developer. Write clean, tested code.',
});
// Add safety hooks
session.on('beforeToolExecute', (event) => {
console.log(`[${event.toolName}] ${JSON.stringify(event.arguments)}`);
// Block dangerous operations
if (event.toolName === 'bash') {
const cmd = event.arguments.command as string;
if (cmd.includes('rm -rf /') || cmd.includes('sudo')) {
return { allow: false, reason: 'Blocked for safety' };
}
}
return { allow: true };
});
// Run the session
session.send('Create a simple HTTP server in src/server.ts');
for await (const event of session.stream()) {
if (event.type === 'text.delta') {
process.stdout.write(event.delta);
}
}