Event System — Agentuity Documentation

Event System

Lifecycle hooks for monitoring agent, session, and thread events

The runtime exposes three event layers: agent-scoped listeners attached to a specific agent, global listeners registered with the top-level event bus, and instance listeners on ctx.session and ctx.thread. Use agent listeners for local instrumentation, global listeners for app-wide analytics or cleanup, and session or thread listeners when you want to react to the lifecycle of a specific run.

Agent Events

Agents emit started, completed, and errored events during execution.

Available Events:

agent.addEventListener('started', (eventName, agent, ctx) => {
  // Agent execution has started
});
 
agent.addEventListener('completed', (eventName, agent, ctx) => {
  // Agent execution completed successfully
});
 
agent.addEventListener('errored', (eventName, agent, ctx, error) => {
  // Agent execution failed
});

Example: Logging Agent Lifecycle

import { createAgent } from '@agentuity/runtime';
import { s } from '@agentuity/schema';
 
const agent = createAgent('TaskRunner', {
  schema: {
    input: s.object({ task: s.string() }),
    output: s.object({ result: s.string() }),
  },
  handler: async (ctx, input) => {
    return { result: `Completed: ${input.task}` };
  },
});
 
// Log when agent starts
agent.addEventListener('started', (eventName, agent, ctx) => {
  ctx.logger.info('Agent started', {
    agentName: agent.metadata.name,
    sessionId: ctx.sessionId,
  });
});
 
// Log when the agent completes
agent.addEventListener('completed', (eventName, agent, ctx) => {
  ctx.logger.info('Agent completed', {
    agentName: agent.metadata.name,
    sessionId: ctx.sessionId,
  });
});
 
// Log when agent errors
agent.addEventListener('errored', (eventName, agent, ctx, error) => {
  ctx.logger.error('Agent errored', {
    agentName: agent.metadata.name,
    sessionId: ctx.sessionId,
    error: error.message,
  });
});

App Events

Register app-wide listeners with the top-level addEventListener() function. These listeners are global, so register them at module scope during startup rather than inside individual requests.

Available App Events:

import { addEventListener } from '@agentuity/runtime';
 
// Agent lifecycle events
addEventListener('agent.started', (eventName, agent, ctx) => {});
addEventListener('agent.completed', (eventName, agent, ctx) => {});
addEventListener('agent.errored', (eventName, agent, ctx, error) => {});
 
// Session lifecycle events
addEventListener('session.started', (eventName, session) => {});
addEventListener('session.completed', (eventName, session) => {});
 
// Thread lifecycle events
addEventListener('thread.created', (eventName, thread) => {});
addEventListener('thread.destroyed', (eventName, thread) => {});

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

Example: App-Wide Metrics

import { addEventListener } from '@agentuity/runtime';
 
// Track agent executions
let agentExecutions = 0;
let completedSessions = 0;
let activeThreads = 0;
 
addEventListener('agent.started', (eventName, agent, ctx) => {
  agentExecutions++;
  ctx.logger.info(`Total agent executions: ${agentExecutions}`);
});
 
// Track errors for monitoring
addEventListener('agent.errored', (eventName, agent, ctx, error) => {
  ctx.logger.error('Agent error detected', {
    agentName: agent.metadata.name,
    error: error.message,
    sessionId: ctx.sessionId,
  });
});
 
addEventListener('session.completed', (eventName, session) => {
  completedSessions++;
});
 
addEventListener('thread.created', (eventName, thread) => {
  activeThreads++;
});
 
addEventListener('thread.destroyed', (eventName, thread) => {
  activeThreads = Math.max(0, activeThreads - 1);
});

Event Handlers

Both agent-scoped and global handlers can be added and removed dynamically. Keep a reference to the callback you register so you can remove it later.

Adding Event Listeners:

import {
  addEventListener,
  createAgent,
  removeEventListener,
} from '@agentuity/runtime';
import { s } from '@agentuity/schema';
import type { Agent, AgentContext } from '@agentuity/runtime';
 
const agent = createAgent('ActionHandler', {
  schema: {
    input: s.object({ action: s.string() }),
    output: s.object({ success: s.boolean() }),
  },
  handler: async () => ({ success: true }),
});
 
const handler = (
  _eventName: 'started' | 'completed',
  _agent: Agent,
  ctx: AgentContext
): void => {
  ctx.logger.info('Agent started');
};
 
agent.addEventListener('started', handler);
 
const completedHandler = (
  eventName: 'agent.completed',
  agent: Agent,
  ctx: AgentContext
): void => {
  ctx.logger.info(`${agent.metadata.name} completed`);
};
 
addEventListener('agent.completed', completedHandler);

Removing Event Listeners:

agent.removeEventListener('started', handler);
removeEventListener('agent.completed', completedHandler);

Complete Example with Cleanup:

import {
  addEventListener,
  createAgent,
  removeEventListener,
} from '@agentuity/runtime';
import { s } from '@agentuity/schema';
import type { Agent, AgentContext } from '@agentuity/runtime';
 
const agent = createAgent('ActionHandler', {
  schema: {
    input: s.object({ action: s.string() }),
    output: s.object({ success: s.boolean() }),
  },
  handler: async () => ({ success: true }),
});
 
// Create handlers with references for cleanup
const startedHandler = (
  _eventName: 'started' | 'completed',
  _agent: Agent,
  ctx: AgentContext
): void => {
  ctx.logger.info('Started processing');
};
 
const completedHandler = (
  _eventName: 'started' | 'completed',
  _agent: Agent,
  ctx: AgentContext
): void => {
  ctx.logger.info('Completed processing');
};
 
const erroredHandler = (
  _eventName: 'errored',
  _agent: Agent,
  ctx: AgentContext,
  error: Error
): void => {
  ctx.logger.error('Error occurred', { error });
};
 
const globalCompletedHandler = (
  _eventName: 'agent.completed',
  completedAgent: Agent,
  ctx: AgentContext
): void => {
  ctx.logger.info('Observed completion', {
    agentName: completedAgent.metadata.name,
  });
};
 
// Add listeners
agent.addEventListener('started', startedHandler);
agent.addEventListener('completed', completedHandler);
agent.addEventListener('errored', erroredHandler);
addEventListener('agent.completed', globalCompletedHandler);
 
// Later, if needed, remove listeners
// agent.removeEventListener('started', startedHandler);
// agent.removeEventListener('completed', completedHandler);
// agent.removeEventListener('errored', erroredHandler);
// removeEventListener('agent.completed', globalCompletedHandler);

For output-quality patterns, see LLM as a Judge.