Session & observability¶
Session is the event bus that fans observability events into
exporters and exposes the GraphSchema topology view. Six core
exporter classes ship under lazybridge.*; OTelExporter lives
under lazybridge.ext.otel (see Extension engines).
For narrative usage see Guides → Mid → Session, Guides → Full → Exporters, and Guides → Full → GraphSchema.
| Symbol | Role |
|---|---|
Session |
The event bus. Attached to an Agent via session= (or implicitly via verbose=True). |
EventLog |
SQLite-backed event store under Session.events. |
EventType |
StrEnum of emitted event kinds (AGENT_START, TOOL_CALL, AGENT_FINISH, …). |
GraphSchema |
Topology view of registered agents + tool edges; renderable via Session.graph.to_json(). |
EventExporter |
Base protocol — implement export(event) to add your own sink. |
ConsoleExporter |
Human-readable stdout exporter; used implicitly by verbose=True. |
CallbackExporter |
Routes events to a user-supplied callable. |
FilteredExporter |
Wrap any exporter to drop events that don't match a predicate. |
JsonFileExporter |
Newline-delimited JSON sink. |
StructuredLogExporter |
Logs events as structured records via the logging module. |
OTelExporter (ext) |
OpenTelemetry GenAI conventions; see Guides → Advanced → OpenTelemetry. |
Session¶
lazybridge.Session ¶
Session(*, db: str | None = None, exporters: list[Any] | None = None, redact: Callable[[dict[str, Any]], dict[str, Any]] | None | Any = _REDACT_UNSET, redact_on_error: Literal['fallback', 'strict'] = 'strict', unsafe_log_payloads: bool = False, console: bool = False, batched: bool = False, batch_size: int = 100, batch_interval: float = 1.0, max_queue_size: int = 10000, on_full: Literal['drop', 'block', 'hybrid'] = 'hybrid', critical_events: frozenset[str] | set[str] | None = None)
Container for observability config: exporters, redaction, EventLog.
Construct a Session.
Back-pressure policy¶
on_full selects what happens when the batched-writer queue
is saturated (batched=True):
"hybrid"(default) — block for audit-critical event types (AGENT_*,TOOL_*,HIL_DECISION; override viacritical_events=) and silently drop the cheap high-volume ones (LOOP_STEP/MODEL_REQUEST/MODEL_RESPONSE). A buggy slow exporter no longer makesAGENT_FINISHorTOOL_ERRORdisappear, while a steady-state telemetry firehose still doesn't add latency to the producer."block"— back-pressure unconditionally. Pick this when every event must persist (compliance) and producer latency can absorb the wait."drop"— never back-pressure. Saturation drops events silently (with a doubling-interval warning). Pick this when telemetry must not block production traffic and lossy traces are acceptable.
Redactor failure modes¶
redact_on_error governs what happens when a redact
callable either raises or returns a non-dict:
"strict"(default) — warn once, then drop the event entirely. No record in the EventLog, no export to exporters. Fail-closed: unredacted data can never leak via this session. This is the safe default for compliance workloads where a broken redactor is a bug to be fixed, not a reason to keep leaking."fallback"— warn once, then record and export the original, unredacted payload. Observability is preserved at the cost of potentially leaking unredacted data through the event bus. Useful for development; opt in explicitly when you want it.
Default is "strict": a redactor that fails closes the
event rather than persisting it unredacted. Pass
redact_on_error="fallback" to keep the unredacted event
flowing when redaction fails (lower fidelity, but lossless).
Default-safe secret redaction¶
When redact is not passed, Session defaults to
:func:redact_secrets so well-known credential shapes
(sk-..., ghp_..., AIza..., JWTs, Bearer ...
etc.) are stripped from every event payload before it hits the
EventLog or any exporter. Pass redact=None to opt out for
a single Session, or unsafe_log_payloads=True for the same
effect with a more searchable construction-site keyword.
Passing your own redact=callable always wins — Session
will not stack the default in front of a user redactor.
Source code in lazybridge/session.py
663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 | |
register_agent ¶
register_tool_edge ¶
Record a tool-call edge between two registered agents.
Source code in lazybridge/session.py
flush ¶
Drain the EventLog's batched-writer queue.
No-op when batched=False. Useful before a checkpoint or
a clean shutdown so recently-emitted events are persisted
before the caller proceeds.
Source code in lazybridge/session.py
close ¶
Release the underlying EventLog's SQLite connections.
Idempotent. Call this when a Session's lifetime ends (e.g.
end of an HTTP request) so file descriptors don't linger until
the owning thread exits. Using Session as a context manager is
equivalent. Exporters that expose close() are flushed too
— useful for OTelExporter's orphaned-span cleanup.
Source code in lazybridge/session.py
usage_summary ¶
Aggregate token usage and cost across all agent runs in this session.
Returns a dict with
- "total": {input_tokens, output_tokens, cost_usd}
- "by_agent": {agent_name: {input_tokens, output_tokens, cost_usd}}
- "by_run": {run_id: {agent_name, input_tokens, output_tokens, cost_usd}}
O(events) with TWO queries total (AGENT_START +
MODEL_RESPONSE). EventLog.query exposes run_id
directly in the result dict.
Source code in lazybridge/session.py
Event log + types¶
lazybridge.EventLog ¶
EventLog(session_id: str, db: str | None = None, *, batched: bool = False, batch_size: int = 100, batch_interval: float = 1.0, max_queue_size: int = 10000, on_full: Literal['drop', 'block', 'hybrid'] = 'hybrid', critical_events: frozenset[str] | set[str] | None = None)
SQLite-backed event log. Thread-safe via thread-local connections.
By default record() performs an INSERT + COMMIT per event
on the calling thread. That is fine for low event rates but
becomes a bottleneck under sustained load. Pass
batched=True to delegate persistence to a background daemon
thread that drains a bounded queue and commits in batches; the
hot path becomes a non-blocking queue.put_nowait.
Source code in lazybridge/session.py
243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 | |
close ¶
Close every thread-local connection and the anchor (if any).
Idempotent. After close() further record / query
calls raise RuntimeError. Required for deterministic FD
cleanup in long-running services that spawn Sessions per
request.
The lock is held across the entire shutdown so a concurrent
record that already obtained a connection via _conn
can't race ahead and commit against a connection we're about
to close — SQLite would otherwise raise ProgrammingError:
Cannot operate on a closed database.
When batching is enabled the background writer is signalled to drain its queue and exit before connections are released.
Source code in lazybridge/session.py
record_many ¶
Insert a batch of pre-serialised rows in a single transaction.
Each row is the 5-tuple
(session_id, run_id, event_type, payload_json, ts) —
the on-disk shape, not a dict. Used by the background batched
writer; callers should use :meth:record instead.
Source code in lazybridge/session.py
flush ¶
Block until every event submitted before the call is persisted.
No-op when batched=False. Pushes a flush sentinel so the
writer commits its current batch immediately rather than waiting
for batch_size or batch_interval, then waits on the
queue's task_done accounting. Returns early if timeout
elapses; the queue may still have items in that case.
Source code in lazybridge/session.py
lazybridge.EventType ¶
Bases: StrEnum
Graph topology¶
lazybridge.GraphSchema ¶
Directed graph of agents, routers, and their connections.
Auto-populated by :class:lazybridge.Session as Agents register
themselves via session=. Can also be built manually for GUI-driven
pipeline construction.
Source code in lazybridge/graph/schema.py
add_agent ¶
Register an Agent (or duck-typed equivalent) as a graph node.
Reads id / name off the agent, and infers provider + model
from either legacy _provider_name / _model_name attributes
or from agent.engine on the v1 :class:~lazybridge.Agent.
Also registers any Python-callable tools the agent exposes as
ToolNode stubs so the graph is fully visible before any
events are emitted (static inspection / demo mode).
Source code in lazybridge/graph/schema.py
add_router ¶
Register a router (e.g. a Plan) as a graph node.
The router object is expected to expose to_graph_node() that
returns {id, name, routes, default}.
Source code in lazybridge/graph/schema.py
clear ¶
Drop all nodes and edges.
Useful when re-using one Session across multiple pipeline
runs and you want each run's graph to start empty. Session's
session_id is preserved so event correlation still works.
Source code in lazybridge/graph/schema.py
save ¶
Save schema to JSON or YAML depending on file extension.
Exporters¶
lazybridge.EventExporter ¶
Bases: Protocol
Protocol satisfied by all exporter classes.
lazybridge.CallbackExporter ¶
lazybridge.ConsoleExporter ¶
Pretty-print events to stdout for human inspection.
Output format (one line per event)::
[agent_name] event_type key=value key=value
Installed automatically by Session(console=True) and
Agent(verbose=True); can also be added manually via
Session(exporters=[ConsoleExporter()]).
Source code in lazybridge/exporters.py
lazybridge.FilteredExporter ¶
lazybridge.JsonFileExporter ¶
Append each event as a JSON line to path.
F7: keeps the file handle open across calls instead of opening and closing
it on every event. Under a typical agent run with 50-200 events the
original per-call open/fwrite/close caused O(n) filesystem syscalls.
close() is called automatically by Session.close() when it
iterates its exporter list.
Source code in lazybridge/exporters.py
close ¶
Flush and close the underlying file handle. Idempotent.