--- title: "Observability" description: "How to monitor, inspect, and analyze workflow runs" --- Fabro captures a structured event for every significant action during a workflow run. These events cover stage execution, agent tool calls, retries, routing, sandbox lifecycle, git checkpoints, and more. ## Event stream Every workflow run emits a sequence of canonical **run event envelopes** that are: - Stored durably in the run store - Broadcast over SSE to connected API clients - Stored for later analysis - Rendered by CLI progress and log tooling - Optionally materialized into JSONL by export/debug paths ### Event names Event names use lowercase dot notation, for example: - `run.started` - `stage.started` - `stage.completed` - `agent.tool.started` - `agent.tool.completed` - `sandbox.ready` - `todo.updated` - `parallel.branch.completed` ### Envelope format Each serialized event envelope has a stable JSON shape: ```json { "id": "01960d0c-5d16-7d6e-8f61-9fd6f4a532b5", "ts": "2026-03-30T12:00:01.000Z", "run_id": "01JQ...", "event": "agent.tool.started", "session_id": "ses_child", "parent_session_id": "ses_parent", "node_id": "implement", "node_label": "Implement", "properties": { "tool_name": "shell", "tool_call_id": "call_1", "arguments": {"command": "cargo test"} } } ``` Envelope fields: | Field | Description | |---|---| | `id` | Unique event id | | `ts` | UTC timestamp | | `run_id` | Workflow run id | | `event` | Event name | | `session_id` | Session that emitted the event, when applicable | | `parent_session_id` | Immediate parent session for forwarded child events | | `node_id` | Node or branch id, when applicable | | `node_label` | Human-facing label for `node_id`, when applicable | | `properties` | Event-specific payload | Only `id`, `ts`, `run_id`, and `event` are always present. Optional fields are omitted when they do not apply. For runtime `for_each` branches, `parallel.branch.started` and `parallel.branch.completed` include the zero-based `index` and an optional `item_label`. They do not include the raw item. The final prompt is recorded by the existing `stage.prompt` event, including the fenced item data, so event streams, run dumps, and retained logs are source-bearing data. Apply the same access controls and retention policy you use for workflow inputs. ## Reading the event stream Because event payload lives in `properties`, most shell queries should look there. ```bash # Count tool calls in a run fabro events 01JKXYZ... | jq -r 'select(.event == "agent.tool.started") | .properties.tool_name' | wc -l # Find stage failures fabro events 01JKXYZ... | jq 'select(.event == "stage.failed")' # See which edges were taken jq '{from: .properties.from_node, to: .properties.to_node, label: .properties.label}' \ <(fabro events 01JKXYZ...) | head ``` If you need files on disk for offline analysis, `fabro dump` exports `events.jsonl` plus run-state projections. ## Event categories Common categories include: | Category | Example events | |---|---| | Run lifecycle | `run.started`, `run.completed`, `run.failed`, `run.notice` | | Stage lifecycle | `stage.started`, `stage.completed`, `stage.failed`, `stage.retrying` | | Agent activity | `agent.message`, `agent.tool.started`, `agent.warning`, `agent.sub.spawned` | | Agent todo state | `todo.created`, `todo.updated`, `todo.deleted` | | Routing | `edge.selected`, `loop.restart`, `parallel.started` | | Git and checkpoints | `checkpoint.completed`, `git.commit`, `git.push` | | Setup and sandbox | `sandbox.initializing`, `sandbox.ready`, `setup.started` | `agent.message` is the canonical post-response event for agent turns. When context-window data is available, it appears on the message payload as `context_window`; there is no separate context-window snapshot event to consume. ## Sub-agent visibility Sub-agent activity now appears as normal agent events with session linkage: - `session_id` identifies the child session - `parent_session_id` identifies its immediate parent Lifecycle events such as `agent.sub.spawned` and `agent.sub.completed` are emitted by the parent session. Tool calls and other child activity are forwarded with their original `session_id`. ## Real-time monitoring ### API: Server-Sent Events When running workflows through the API server, subscribe to the [run events endpoint](/api-reference/runs/stream-run-events). Each SSE payload is a serialized run event envelope in the same shape used by `fabro events` and `events.jsonl` exports. ### Web UI The web frontend consumes the SSE stream automatically and shows stage progress, tool calls, command output, and human interaction as they happen. Use the stage `Thread` and `Debug` views for per-stage activity, or the run-level `Run Events` page when you need the full event stream with search and category filters. The `Run Events` page also includes a Waterfall view for comparing stage durations and inspecting timing details from hover popovers. Fabro web UI run stages showing agent conversation with tool calls ### CLI progress The CLI renders live progress from the same envelope format. This is written to stderr so stdout remains pipe-friendly. ## Post-run analysis Post-run analysis surfaces include: | Surface | Description | |---|---| | `fabro events ` | Full event envelope stream as NDJSON | | `fabro logs ` | Raw per-run worker tracing log, when available | | `fabro inspect ` | Current durable run state, including run/start/checkpoint/conclusion records | | `fabro dump --output ` | Exported `events.jsonl` plus reconstructed JSON and node files | See [stages](/api-reference/run-internals/list-run-stages) and [turns](/api-reference/run-internals/list-stage-turns) for higher-level analysis views built on top of this event stream.