Skip to main content
Attach a durable sink to the event bus so every lifecycle event survives restart and reads back per session, in order.
Nothing is persisted by default — a bare Agent() attaches no sink and the zero-overhead in-memory path stays untouched. You opt in by attaching a sink.

Quick Start

1

Attach a sink and publish

2

Read the timeline after a restart

3

Walk the timeline with a cursor


How It Works

The log persists the same Event / EventType shapes as the in-memory bus — one row per event, keyed by session_id with a monotonic per-session seq. Writes are best-effort (a broken sink never breaks the turn), on-disk databases use WAL, and prune keeps the file bounded. Sinks resolve the session from event.data["session_id"], then event.metadata["session_id"], then event.source. With none present, the event is stored under an empty session id.

Configuration Options


API

Bus wiring:

Common Patterns

Timeline export:
Periodic prune:
Custom sink — implement EventLogProtocol to send events to Postgres, Kafka, or JSONL:

Best Practices

attach_sink is idempotent per instance, but re-instantiating SqliteEventLog on every publish is wasteful. Build it once and attach it.
Sinks resolve the session via event.data["session_id"], then event.metadata["session_id"], then event.source. When emitting your own events, put session_id in data for clarity.
log.prune(older_than_days=30, max_rows=1_000_000) trims by age and row count. Run it from your housekeeping loop.
A slow or broken sink must never block a turn, so failures are swallowed at DEBUG. Check application logs under praisonaiagents.bus.event_log when events seem absent.

Event Bus

Pub/sub side of the same events

Run Ledger

Sibling durable store for run status