Skip to main content

Overview

The ExecutionContext 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

input
string
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

data
TData
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

state
ExecutionState
Internal runtime state containing execution history, messages, tool calls, and token usage.WARNING: This is read-only. Do not mutate directly.Example:
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

sessionId
string
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
MemoryProvider | null
Memory provider instance for this execution, or null if no memory is configured.Example:
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
Example:
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 via agent.on().
emit(event: string, payload?: unknown): void
event
string
required
Custom event name (can be any string)
payload
unknown
Event payload data
Example:
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

Build docs developers (and LLMs) love