Skip to content

Events

personaforge/events gives every agent and workflow a first-class typed pub/sub bus. Everything that happens — agent started, tool called, LLM delta, run finished, workflow suspended — is an observable, typed event.

ts
import { eventBus, AGENT_EVENT } from 'personaforge/events';

Quick start

ts
import { eventBus, AGENT_EVENT } from 'personaforge/events';
import { agent } from 'personaforge';

// A bus pre-wired with the core event vocabulary.
const bus = eventBus({ replayBufferSize: 100 });

bus.on(AGENT_EVENT.runFinished, (e) => {
  console.log('run finished', e.agentId, e.result);
});
bus.on('*', (type, payload) => {
  console.log('any event →', type);
});

// Emit from your own hooks:
const bot = agent({
  instructions: 'You are helpful.',
  hooks: {
    afterRun: async (result) => {
      await bus.emit(AGENT_EVENT.runFinished, { agentId: 'bot', sessionId: 's1', result });
    },
  },
});

Core event vocabulary

AGENT_EVENT contains the canonical event names — use these to avoid typos across the framework:

ConstantEvent namePayload
AGENT_EVENT.agentStartedagent:started{ agentId?, sessionId?, prompt? }
AGENT_EVENT.agentOutputagent:output{ agentId?, sessionId?, text? }
AGENT_EVENT.agentFinishedagent:finished{ agentId?, steps, tokensUsed?, costUsd? }
AGENT_EVENT.toolCalledtool:called{ agentId?, sessionId?, name, input }
AGENT_EVENT.toolResulttool:result{ agentId?, sessionId?, name, success, output?, durationMs? }
AGENT_EVENT.llmDeltallm:delta{ agentId?, sessionId?, delta }
AGENT_EVENT.stepFinishedstep:finished{ agentId?, sessionId?, step }
AGENT_EVENT.runFinishedrun:finished{ agentId?, sessionId?, result? }
AGENT_EVENT.workflowSuspendedworkflow:suspended{ workflowId?, awaiting, token?, message? }
AGENT_EVENT.workflowCompletedworkflow:completed{ workflowId?, results? }
AGENT_EVENT.errorerror{ agentId?, message, error? }

CoreEventMap is the TypeScript type for these payloads — your handlers are fully typed.


Generic event bus

For a custom EventMap, use createAgentEventBus:

ts
import { createAgentEventBus } from 'personaforge/events';

interface MyEvents {
  'ping': { at: number };
  'pong': { at: number };
}
const bus = createAgentEventBus<MyEvents>({ replayBufferSize: 64 });

bus.on('ping', (p) => console.log('ping at', p.at));
await bus.emit('ping', { at: Date.now() });

Replay buffer

With replayBufferSize > 0, late subscribers receive buffered events after subscribing — useful for audit / dashboard views:

ts
const bus = eventBus({ replayBufferSize: 100 });
await bus.emit(AGENT_EVENT.toolCalled, { name: 'search', input: {...} });

// Later subscriber still sees the buffered event:
bus.on(AGENT_EVENT.toolCalled, (e) => console.log('replay >', e.name));

Handler failures

Handler errors surface as an AggregateError after all handlers run, so one failing handler doesn't break the others:

ts
try {
  await bus.emit(AGENT_EVENT.error, { message: 'x' });
} catch (err) {
  // AggregateError of handler failures
}

Released under the MIT License.