Overview
TheExecutionContext interface represents the runtime environment for a single agent execution. It provides access to input, custom state, execution history, memory, and control functions. This context is passed to tool execute functions and middleware.
Interface
interface ExecutionContext<TData = unknown> {
input: string
data: TData
state: ExecutionState
sessionId: string
memory: MemoryProvider | null
cancel(): void
emit(event: string, payload?: unknown): void
}
Properties
input
The user’s input prompt that initiated this execution.Example:
const tool = defineTool({
schema: { name: 'echoInput', description: 'Echo the user input', parameters: {} },
execute: async (args, ctx) => {
return `You asked: ${ctx.input}`
}
})
data
User-defined typed state data that persists throughout the execution. This is the merged result of the agent’s default data and run-specific data.Example:
interface MyData {
userId: string
sessionCount: number
}
const agent = createAgent<MyData>({
name: 'stateful',
data: { userId: 'default', sessionCount: 0 }
})
const tool = defineTool<MyData>({
schema: { name: 'incrementSession', description: 'Increment session counter', parameters: {} },
execute: async (args, ctx) => {
ctx.data.sessionCount++
return `Session count for user ${ctx.data.userId}: ${ctx.data.sessionCount}`
}
})
state
Internal runtime state containing execution history, messages, tool calls, and token usage.WARNING: This is read-only. Do not mutate directly.
Example:
Show ExecutionState Properties
Show ExecutionState Properties
Array of all reasoning steps (thoughts, plans, tool calls, reflections, responses)
Full message history sent to/from the LLM
Record of all tool invocations and their results
Token usage statistics:
promptTokens: Tokens in promptscompletionTokens: Tokens in completionstotalTokens: Total tokens used
Timestamp when execution started
Timestamp when execution completed (undefined while running)
const middleware = {
name: 'state-logger',
scope: 'run:after',
async run(mCtx, next) {
await next()
const { state } = mCtx.ctx
console.log(`Steps taken: ${state.steps.length}`)
console.log(`Tools called: ${state.toolCalls.length}`)
console.log(`Tokens used: ${state.usage.totalTokens}`)
console.log(`Duration: ${state.finishedAt!.getTime() - state.startedAt.getTime()}ms`)
}
}
sessionId
Unique identifier for this execution session. Used to scope memory reads/writes. Either provided in
run() options or auto-generated UUID.Example:const tool = defineTool({
schema: { name: 'logSession', description: 'Log session ID', parameters: {} },
execute: async (args, ctx) => {
console.log(`Current session: ${ctx.sessionId}`)
return ctx.sessionId
}
})
// Same session across multiple runs
await agent.run({ input: 'Hello', sessionId: 'user-123' })
await agent.run({ input: 'Follow up', sessionId: 'user-123' })
memory
Memory provider instance for this execution, or null if no memory is configured.
Example:
Show MemoryProvider Methods
Show MemoryProvider Methods
Load conversation history:
limit: Max messages to returnsessionId: Filter by sessionquery: Semantic search querytags: Filter by metadata tags
Persist messages to memory
Clear stored memory
Get raw memory entries
const tool = defineTool({
schema: { name: 'getHistory', description: 'Get conversation history', parameters: {} },
execute: async (args, ctx) => {
if (!ctx.memory) {
return 'No memory configured'
}
const history = await ctx.memory.read({
sessionId: ctx.sessionId,
limit: 10
})
return history.map(msg => `${msg.role}: ${msg.content}`).join('\n')
}
})
Methods
cancel()
Cancel the current execution.cancel(): void
const dangerousTool = defineTool({
schema: {
name: 'dangerousAction',
description: 'Perform a potentially destructive action',
parameters: {
type: 'object',
properties: {
confirm: { type: 'boolean', description: 'Must be true to proceed' }
},
required: ['confirm']
}
},
execute: async (args, ctx) => {
if (!args.confirm) {
ctx.cancel()
throw new Error('Action cancelled - confirmation required')
}
// Proceed with action
return 'Action completed'
}
})
emit()
Emit a custom event that listeners can subscribe to viaagent.on().
emit(event: string, payload?: unknown): void
Custom event name (can be any string)
Event payload data
const processingTool = defineTool({
schema: {
name: 'processItems',
description: 'Process multiple items',
parameters: {
type: 'object',
properties: {
items: { type: 'array', items: { type: 'string' } }
},
required: ['items']
}
},
execute: async (args, ctx) => {
const items = args.items as string[]
ctx.emit('processing:started', { count: items.length })
const results = []
for (let i = 0; i < items.length; i++) {
ctx.emit('processing:item', { index: i, item: items[i] })
// Process item
const result = await processItem(items[i])
results.push(result)
ctx.emit('processing:progress', {
completed: i + 1,
total: items.length,
percentage: ((i + 1) / items.length * 100).toFixed(0)
})
}
ctx.emit('processing:completed', { results })
return results
}
})
// Listen to custom events
agent
.on('processing:started', ({ count }) => {
console.log(`Starting to process ${count} items...`)
})
.on('processing:progress', ({ completed, total, percentage }) => {
console.log(`Progress: ${completed}/${total} (${percentage}%)`)
})
.on('processing:completed', ({ results }) => {
console.log(`Completed! Processed ${results.length} items`)
})
Complete Examples
Accessing State in Tools
interface AppContext {
userId: string
apiKey: string
retryCount: number
}
const apiTool = defineTool<AppContext>({
schema: {
name: 'callAPI',
description: 'Call external API',
parameters: {
type: 'object',
properties: {
endpoint: { type: 'string' }
},
required: ['endpoint']
}
},
execute: async (args, ctx) => {
const { endpoint } = args as { endpoint: string }
console.log(`User ${ctx.data.userId} calling ${endpoint}`)
for (let attempt = 0; attempt < ctx.data.retryCount; attempt++) {
try {
const response = await fetch(endpoint, {
headers: { 'Authorization': `Bearer ${ctx.data.apiKey}` }
})
if (!response.ok) throw new Error(`HTTP ${response.status}`)
return await response.json()
} catch (error) {
if (attempt === ctx.data.retryCount - 1) throw error
ctx.emit('api:retry', { attempt, endpoint, error })
await new Promise(resolve => setTimeout(resolve, 1000 * (attempt + 1)))
}
}
}
})
Middleware with Context
const analyticsMiddleware: Middleware<AppContext> = {
name: 'analytics',
scope: ['run:before', 'run:after', 'tool:after'],
async run(mCtx, next) {
const { scope, ctx } = mCtx
if (scope === 'run:before') {
console.log(`[Analytics] Run started for user ${ctx.data.userId}`)
console.log(`[Analytics] Input: ${ctx.input}`)
}
await next()
if (scope === 'run:after') {
console.log(`[Analytics] Run completed for session ${ctx.sessionId}`)
console.log(`[Analytics] Steps: ${ctx.state.steps.length}`)
console.log(`[Analytics] Tokens: ${ctx.state.usage.totalTokens}`)
// Send to analytics service
ctx.emit('analytics:run_complete', {
userId: ctx.data.userId,
sessionId: ctx.sessionId,
steps: ctx.state.steps.length,
tokens: ctx.state.usage.totalTokens,
duration: ctx.state.finishedAt!.getTime() - ctx.state.startedAt.getTime()
})
}
if (scope === 'tool:after' && mCtx.tool) {
console.log(`[Analytics] Tool ${mCtx.tool.name} called`)
}
}
}
Working with Memory
const summarizeTool = defineTool({
schema: {
name: 'summarizeConversation',
description: 'Summarize the conversation history',
parameters: { type: 'object', properties: {}, required: [] }
},
execute: async (args, ctx) => {
if (!ctx.memory) {
return 'No conversation history available'
}
// Get all messages from this session
const history = await ctx.memory.read({
sessionId: ctx.sessionId
})
if (history.length === 0) {
return 'No conversation history found'
}
const summary = [
`Session: ${ctx.sessionId}`,
`Messages: ${history.length}`,
`Started: ${ctx.state.startedAt.toISOString()}`,
'',
'Conversation:'
]
for (const msg of history) {
summary.push(`${msg.role}: ${msg.content.substring(0, 100)}...`)
}
return summary.join('\n')
}
})
Cancellation Example
const longRunningTool = defineTool({
schema: {
name: 'longTask',
description: 'Perform a long-running task',
parameters: {
type: 'object',
properties: {
iterations: { type: 'number', minimum: 1, maximum: 1000 }
},
required: ['iterations']
}
},
execute: async (args, ctx) => {
const iterations = args.iterations as number
// Set up cancellation handler
let cancelled = false
const cancelHandler = () => { cancelled = true }
// Note: You'd need to wire this through the event system
// This is conceptual - actual implementation would listen to cancel events
for (let i = 0; i < iterations; i++) {
if (cancelled) {
ctx.emit('task:cancelled', { completedIterations: i })
throw new Error('Task cancelled by user')
}
// Do work
await new Promise(resolve => setTimeout(resolve, 100))
ctx.emit('task:progress', { iteration: i + 1, total: iterations })
}
return { success: true, iterations }
}
})
Type Safety
The context is fully typed based on your custom data type:interface MyCustomData {
userId: string
settings: { theme: string; notifications: boolean }
apiKeys: Record<string, string>
}
const agent = createAgent<MyCustomData>({
name: 'typed-agent',
data: {
userId: 'user-123',
settings: { theme: 'dark', notifications: true },
apiKeys: { openai: 'sk-xxx' }
}
})
const tool = defineTool<MyCustomData>({
schema: { name: 'example', description: 'Example', parameters: {} },
execute: async (args, ctx) => {
// TypeScript knows the shape of ctx.data
const theme = ctx.data.settings.theme // ✓ Type-safe
const key = ctx.data.apiKeys.openai // ✓ Type-safe
// const invalid = ctx.data.invalid // ✗ Type error
return { theme, key }
}
})
See Also
- createAgent() - Create agents
- defineTool() - Define tools that receive context
- AgentInstance - Agent instance methods