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 sameEvent / 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:EventLogProtocol to send events to Postgres, Kafka, or JSONL:
Best Practices
Pass session_id in data
Pass session_id in data
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.Prune to keep the file bounded
Prune to keep the file bounded
log.prune(older_than_days=30, max_rows=1_000_000) trims by age and row count. Run it from your housekeeping loop.Watch the log if events look missing
Watch the log if events look missing
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.Related
Event Bus
Pub/sub side of the same events
Run Ledger
Sibling durable store for run status

