Skip to navigation

AgentEventBus

Signature
class AgentEventBus {
on<K extends keyof AgentEventMap>(event: K, handler: (payload: AgentEventMap[K]) => void): this;
once<K extends keyof AgentEventMap>(event: K, handler: (payload: AgentEventMap[K]) => void): this;
off<K extends keyof AgentEventMap>(event: K, handler: (payload: AgentEventMap[K]) => void): this;
emit<K extends keyof AgentEventMap>(event: K, payload: AgentEventMap[K]): boolean;
removeAllListeners(event?: keyof AgentEventMap): this;
}

A typed event emitter for agent output. Pass one bus to any runner as eventBus. Events from sub-agents (for example, each worker in a pentest swarm) are forwarded to the same bus, tagged with subagentId and sessionId, so one subscriber sees the whole run.

Handlers run synchronously while the agent streams. Keep them fast, and hand slow work (database writes, network calls) off to a queue.

Import
import { AgentEventBus } from "@pensar/apex/core/eventBus";

Events

Model output

text-delta
{ text: string; subagentId?: string; sessionId?: string; messageId?: string; partId?: string }

A chunk of the model’s response text.

reasoning-delta
{ text: string; subagentId?: string; sessionId?: string; messageId?: string }

A chunk of extended-thinking output.

reasoning-start
{ subagentId?: string; sessionId?: string; messageId?: string }

A reasoning block opened.

reasoning-end
{ subagentId?: string; sessionId?: string; messageId?: string }

A reasoning block closed.

step-finish
{ messages: unknown[]; subagentId?: string; sessionId?: string; messageId?: string }

An agent step finished. messages holds the conversation so far.

Tools

tool-call-start
{ toolCallId: string; toolName: string; subagentId?: string; sessionId?: string }

The model started generating a tool call.

tool-call-delta
{ toolCallId: string; argsTextDelta: string; subagentId?: string; sessionId?: string }

A chunk of the tool call’s JSON arguments.

tool-call-complete
{ toolCallId: string; toolName: string; args: unknown; subagentId?: string; sessionId?: string }

The tool call is fully formed and about to execute.

tool-result
{ toolCallId: string; toolName: string; result: unknown; subagentId?: string; sessionId?: string }

The tool finished. Match it to its call with toolCallId.

command-output
{ data: string; subagentId?: string }

Live output from a running shell command.

Sub-agents and workflow

subagent-spawn
{ subagentId: string; sessionId?: string; name?: string; input: unknown; parentSubagentId?: string; parentSessionId?: string }

A sub-agent started. parentSubagentId is unset for top-level agents.

subagent-complete
{ subagentId: string; sessionId?: string; status: 'completed' | 'failed'; parentSubagentId?: string; parentSessionId?: string }

A sub-agent finished.

workflow-phase-start
{ phase: 'discovery' | 'pentesting' | 'reporting'; label: string; metadata?: Record<string, unknown> }

A runPentestAgent phase started.

workflow-phase-complete
{ phase: 'discovery' | 'pentesting' | 'reporting'; summary: Record<string, unknown> }

A phase finished. For reporting, summary includes reportPath, findingsCount, and findingsBySeverity.

app-analysis-progress
{ totalApps: number; completedApps: number; appName?: string }

Whitebox discovery progress across the apps in a repository.

Errors and tracing

error
{ error: unknown; subagentId?: string }

An agent or sub-agent hit an error. See Errors and cancellation.

trace-record
{ record: TraceRecord; subagentId?: string }

A structured step-level trace record (reasoning, tool calls, token usage), suitable for exporting to a tracing backend.

Example

Per-agent progress for a pentest swarm
import { AgentEventBus } from "@pensar/apex/core/eventBus";
const eventBus = new AgentEventBus();
const names = new Map<string, string>();
eventBus
.on("workflow-phase-start", ({ label }) => console.log(`\n== ${label}`))
.on("subagent-spawn", ({ subagentId, name }) => {
names.set(subagentId, name ?? subagentId);
console.log(`+ ${names.get(subagentId)}`);
})
.on("tool-call-complete", ({ toolName, subagentId }) => {
const who = subagentId ? names.get(subagentId) : "root";
console.log(` [${who}] → ${toolName}`);
})
.on("subagent-complete", ({ subagentId, status }) =>
console.log(`${status === "completed" ? "✓" : "✗"} ${names.get(subagentId)}`),
)
.on("error", ({ error }) => console.error(error));
// await runPentestAgent({ ..., eventBus });