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.