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
| Event | Description |
|---|---|
agent.started | Any agent starts execution |
agent.completed | Any agent completes successfully |
agent.errored | Any agent throws an error |
session.started | New session begins |
session.completed | Session ends |
thread.created | New thread created |
thread.destroyed | Thread 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-level setup and shutdown are still useful for agent-local config exposed through ctx.config. For shared app-wide dependencies, the current stable path is module-scoped initialization plus registerShutdownHook(), not new examples built around ctx.app.
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.
- Module setup: Shared resources like database pools, Redis clients, or service singletons
- Agent setup: Agent-local caches, preloaded models, or isolated clients exposed through
ctx.config
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,
});
});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
| Aspect | Events | Evals |
|---|---|---|
| Purpose | Monitoring, logging | Quality assessment |
| Timing | During execution | After completion |
| Blocking | Synchronous | Background (waitUntil) |
| Output | Logs, metrics | Pass/fail, scores |
Use events for observability. Use evaluations for output quality checks.
Next Steps
- Evaluations: Automated quality testing for agent outputs
- State Management: Thread and session state patterns
- Calling Other Agents: Multi-agent coordination
- Logging: Structured logging for debugging events
- Tracing: Track timing across event handlers