Skip to main content
Subscribe to agent lifecycle and tool events with a typed event bus — zero cost when nothing is listening.
The user runs the agent; tool and lifecycle events publish to subscribers for logging, UI updates, or custom automation.

How It Works

Quick Start

1

Subscribe and publish

2

Global bus with an agent

Both publish() / publish_event() and publish_async() always record into history, whether or not subscribers are attached — get_history() returns every event you publish (capped at the 1000 most recent). The fast path still skips the subscriber walk when nothing is listening, so publishing to a bare bus stays cheap. Wrap expensive payload construction in a has_subscribers check, since that work is genuinely skipped when nothing is listening. Changed for sync in PR #4958; the async parity fix landed in PR #5258.

Event Types


Durable Persistence

Attach a durable sink to persist every published event to SQLite across processes. In-memory get_history() already records every event; a durable sink adds cross-process persistence on top. See Durable Event Log for the full story.
EventBus.attach_sink(sink) / detach_sink(sink) -> bool / has_sinks manage sinks; a sink is any object implementing EventLogProtocol.append(event).

Common Patterns

Async subscriber:
Guard expensive payloads:
Event history: Every published event is recorded, whether or not subscribers are registered — no need to subscribe first. History is capped at the 1000 most recent events.
For cross-process persistence, attach a durable sink as well (see Durable Event Log).

Best Practices

Building summaries or embeddings for events nobody listens to wastes CPU — guard with bus.has_subscribers.
Every published event lands in history regardless of subscribers, so get_history() is reliable for after-the-fact debugging on a bare bus. Since PR #4958 you no longer need a no-op subscriber to capture a record on the sync path; PR #5258 closed the same gap for publish_async(), so async lifecycle events fired before an observability subscriber attaches are no longer lost. For cross-process persistence, attach a durable sink (see Durable Event Log).
Pass event_types= to subscribe() so handlers only run for relevant events.
The shared default bus lets agents, memory, and hooks emit events without passing a bus instance everywhere.
Sync callbacks run inline; async handlers are awaited during publish_async only.

Durable Event Log

Persist events to SQLite and query per session

Hook Events

Hook lifecycle events alongside the bus

Spawn & Announce

Sub-agent events and coordination