Events & Lifecycle — Agentuity Documentation

Events & Lifecycle

Lifecycle hooks for monitoring and extending agent behavior

Events give you lifecycle hooks for observability and lightweight coordination. Use them for logging, metrics, analytics, and cleanup that should stay close to the runtime.

Agent Events

Track individual agent execution with started, completed, and errored events:

import { createAgent } from '@agentuity/runtime';
import { s } from '@agentuity/schema';
 
const agent = createAgent('TaskProcessor', {
  schema: {
    input: s.object({ task: s.string() }),
    output: s.object({ result: s.string() }),
  },
  handler: async (ctx, input) => {
    ctx.logger.info('Processing task', { task: input.task });
    return { result: `Completed: ${input.task}` };
  },
});
 
// Track execution timing
agent.addEventListener('started', (event, agent, ctx) => {
  ctx.state.set('startTime', Date.now());
  ctx.logger.info('Agent started', { agent: agent.metadata.name });
});
 
agent.addEventListener('completed', (event, agent, ctx) => {
  const startTime = ctx.state.get('startTime') as number;
  const duration = Date.now() - startTime;
 
  ctx.logger.info('Agent completed', {
    agent: agent.metadata.name,
    durationMs: duration,
  });
 
  // Warn on slow executions
  if (duration > 1000) {
    ctx.logger.warn('Slow execution detected', { duration, threshold: 1000 });
  }
});
 
agent.addEventListener('errored', (event, agent, ctx, error) => {
  const startTime = ctx.state.get('startTime') as number;
  const duration = Date.now() - startTime;
 
  ctx.logger.error('Agent failed', {
    agent: agent.metadata.name,
    error: error.message,
    durationMs: duration,
  });
});
 
export default agent;

Agent listeners receive the event name, agent instance, context, and for errored, the thrown error.

App-Level Events

Monitor all agents, sessions, and threads globally with the top-level addEventListener() function. Register these listeners at module scope during startup.

import { addEventListener } from '@agentuity/runtime';
 
// Track all agent executions
addEventListener('agent.started', (event, agent, ctx) => {
  ctx.logger.info('Agent execution started', {
    agent: agent.metadata.name,
    sessionId: ctx.sessionId,
  });
});
 
addEventListener('agent.completed', (event, agent, ctx) => {
  ctx.logger.info('Agent execution completed', {
    agent: agent.metadata.name,
    sessionId: ctx.sessionId,
  });
});
 
addEventListener('agent.errored', (event, agent, ctx, error) => {
  ctx.logger.error('Agent execution failed', {
    agent: agent.metadata.name,
    error: error.message,
    sessionId: ctx.sessionId,
  });
});

Agent events receive (eventName, agent, ctx) or (eventName, agent, ctx, error). Session and thread events receive (eventName, session) or (eventName, thread).

Available App Events

EventDescription
agent.startedAny agent starts execution
agent.completedAny agent completes successfully
agent.erroredAny agent throws an error
session.startedNew session begins
session.completedSession ends
thread.createdNew thread created
thread.destroyedThread is explicitly destroyed or removed by the active provider

App Startup and Shutdown

For shared resources, prefer module initialization plus registerShutdownHook():

import { createApp, registerShutdownHook } from '@agentuity/runtime';
import api from './src/api/index';
import agents from './src/agent';
import { db } from './db';
 
registerShutdownHook(async () => {
  await db.close();
});
 
export default await createApp({
  router: { path: '/api', router: api },
  agents,
});

Agent Lifecycle Hooks

Individual agents can also define setup and shutdown functions for agent-specific initialization:

import { createAgent } from '@agentuity/runtime';
import { s } from '@agentuity/schema';
 
const agent = createAgent('CachedLookup', {
  schema: {
    input: s.object({ key: s.string() }),
    output: s.object({ value: s.string() }),
  },
  // Called once when the app starts. Return value becomes ctx.config.
  setup: async () => {
    const cache = new Map<string, string>();
    return { cache };
  },
  // Called when the app shuts down.
  shutdown: async (_app, config) => {
    config.cache.clear();
  },
  handler: async (ctx, input) => {
    // ctx.config is typed from setup's return value
    const cached = ctx.config.cache.get(input.key);
    if (cached) {
      return { value: cached };
    }
 
    // Fetch and cache
    const value = await fetchValue(input.key);
    ctx.config.cache.set(input.key, value);
    return { value };
  },
});
 
export default agent;

setup returns agent-specific config, exposed via ctx.config. Use it for agent-local caches, clients, or precomputed data that should stay scoped to that agent.

Shared State

Event handlers on the same request can share data through ctx.state:

agent.addEventListener('started', (event, agent, ctx) => {
  ctx.state.set('startTime', Date.now());
  ctx.state.set('metadata', { userId: '123', source: 'api' });
});
 
agent.addEventListener('completed', (event, agent, ctx) => {
  const startTime = ctx.state.get('startTime') as number;
  const metadata = ctx.state.get('metadata') as Record<string, string>;
 
  ctx.logger.info('Execution complete', {
    duration: Date.now() - startTime,
    ...metadata,
  });
});

Events vs Evals

AspectEventsEvals
PurposeMonitoring, loggingQuality assessment
TimingDuring executionAfter completion
BlockingSynchronousBackground (waitUntil)
OutputLogs, metricsPass/fail, scores

Use events for observability. Use evaluations for output quality checks.

Next Steps