Build/Agents

Events & Lifecycle

Lifecycle hooks for monitoring and extending agent behavior

Events provide lifecycle hooks for monitoring agent execution. Use them for logging, metrics, analytics, and error tracking.

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;

Event listeners receive: event name, agent instance, context, and (for errored) the error object.

App-Level Events

Monitor all agents globally by registering listeners in app.ts:

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

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 expired or destroyed

App Lifecycle Hooks

Use setup and shutdown hooks to initialize resources and manage typed app state:

import { createApp } from '@agentuity/runtime';
 
const { server, logger } = await createApp({
  setup: async () => {
    // Initialize resources when the app starts
    const dbClient = await connectToDatabase();
 
    // Return typed app state - available in all agents via ctx.app
    return {
      db: dbClient,
      startedAt: new Date(),
    };
  },
  shutdown: async (state) => {
    // Cleanup when the app shuts down
    // state is typed from setup's return value
    await state.db.close();
    logger.info('App ran for:', Date.now() - state.startedAt.getTime(), 'ms');
  },
});

Access app state in any agent handler:

const agent = createAgent('DataFetcher', {
  handler: async (ctx, input) => {
    // ctx.app is fully typed from setup's return value
    const results = await ctx.app.db.query('SELECT * FROM users');
    return { users: results };
  },
});

Use Cases

Common uses for app lifecycle hooks:

  • Database connection pools
  • Cache initialization
  • External service clients
  • Metrics/telemetry setup

Shared State

Event handlers share state via 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,
  });
});

Background Work

Use ctx.waitUntil() in event handlers for non-blocking operations like sending metrics to external services:

agent.addEventListener('completed', (event, agent, ctx) => {
  ctx.waitUntil(async () => {
    await sendMetricsToExternalService({ agent: agent.metadata.name });
  });
});

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

Need Help?

Join our DiscordCommunity for assistance or just to hang with other humans building agents.

Send us an email at hi@agentuity.com if you'd like to get in touch.

Please Follow us on

If you haven't already, please Signup for your free account now and start building your first agent!