mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-08-28 05:27:41 +00:00
263 lines
11 KiB
Text
263 lines
11 KiB
Text
---
|
|
title: "Observability"
|
|
description: "How to monitor, inspect, and analyze workflow runs"
|
|
---
|
|
|
|
Fabro captures a structured event for every significant action during a workflow run — stage starts and completions, agent tool calls, edge selections, retries, failovers, sandbox lifecycle, and more. These events power real-time monitoring, post-run analysis, and cross-run analytics.
|
|
|
|
## Event stream
|
|
|
|
The event stream is the foundation of Fabro's observability. Every workflow run emits a sequence of `WorkflowRunEvent` records that are:
|
|
|
|
- **Written to `progress.jsonl`** in the run's directory (one JSON object per line)
|
|
- **Broadcast via SSE** to connected API clients in real time
|
|
- **Logged via `tracing`** to the daily log file at `~/.fabro/logs/`
|
|
|
|
### Event types
|
|
|
|
Events fall into several categories:
|
|
|
|
**Run lifecycle** — bookend events for the entire run:
|
|
|
|
| Event | Key fields | Description |
|
|
|---|---|---|
|
|
| `WorkflowRunStarted` | `name`, `run_id`, `base_sha`, `run_branch`, `goal` | Run begins |
|
|
| `WorkflowRunCompleted` | `duration_ms`, `artifact_count`, `total_cost`, `status`, `usage` | Run finishes successfully |
|
|
| `WorkflowRunFailed` | `error`, `duration_ms` | Run terminates with an error |
|
|
|
|
**Stage lifecycle** — events for each node execution:
|
|
|
|
| Event | Key fields | Description |
|
|
|---|---|---|
|
|
| `StageStarted` | `node_id`, `name`, `handler_type`, `attempt`, `max_attempts` | Node begins executing |
|
|
| `StageCompleted` | `node_id`, `duration_ms`, `status`, `usage`, `files_touched` | Node finishes |
|
|
| `StageFailed` | `node_id`, `failure`, `will_retry` | Node fails (may or may not retry) |
|
|
| `StageRetrying` | `node_id`, `attempt`, `max_attempts`, `delay_ms` | Retry scheduled after failure |
|
|
|
|
**Agent activity** — forwarded from the LLM agent session, prefixed with `Agent.`:
|
|
|
|
| Event | Key fields | Description |
|
|
|---|---|---|
|
|
| `Agent.SessionStarted` | `stage` | Agent session begins |
|
|
| `Agent.ToolCallStarted` | `stage`, `tool_name`, `arguments` | Agent invokes a tool |
|
|
| `Agent.ToolCallCompleted` | `stage`, `tool_name`, `output`, `is_error` | Tool returns a result |
|
|
| `Agent.AssistantMessage` | `stage`, `text`, `model`, `usage` | LLM responds with text |
|
|
| `Agent.Error` | `stage`, `error` | Agent-level error |
|
|
| `Agent.LoopDetected` | `stage` | Repeated tool call pattern detected |
|
|
| `Agent.SteeringInjected` | `stage`, `text` | Human steering message injected |
|
|
| `Agent.ContextWindowWarning` | `stage`, `estimated_tokens`, `context_window_size`, `usage_percent` | Token usage exceeds context window threshold |
|
|
| `Agent.CompactionStarted` | `stage`, `estimated_tokens`, `context_window_size` | Context compaction triggered |
|
|
| `Agent.CompactionCompleted` | `stage`, `original_turn_count`, `preserved_turn_count`, `summary_token_estimate`, `tracked_file_count` | Context compaction finished |
|
|
| `Agent.LlmRetry` | `stage`, `provider`, `model`, `attempt`, `delay_secs` | LLM API call retried |
|
|
| `Agent.SubAgentSpawned` | `stage`, `agent_id`, `task` | Sub-agent launched |
|
|
| `Agent.SubAgentCompleted` | `stage`, `agent_id`, `success`, `turns_used` | Sub-agent finished |
|
|
|
|
**Routing and control flow:**
|
|
|
|
| Event | Key fields | Description |
|
|
|---|---|---|
|
|
| `EdgeSelected` | `from_node`, `to_node`, `label`, `condition`, `reason`, `stage_status` | Transition between nodes |
|
|
| `LoopRestart` | `from_node`, `to_node` | Loop restart edge taken |
|
|
| `CheckpointCompleted` | `node_id`, `git_commit_sha` (optional) | Checkpoint saved (with git SHA when git is enabled) |
|
|
| `GitCommit` | `node_id`, `sha` | Git commit created |
|
|
| `GitPush` | `branch`, `success` | Git push attempted |
|
|
| `GitBranch` | `branch`, `sha` | Git branch created |
|
|
| `GitWorktreeAdd` | `path`, `branch` | Git worktree added |
|
|
| `GitWorktreeRemove` | `path` | Git worktree removed |
|
|
| `GitFetch` | `branch`, `success` | Git fetch attempted |
|
|
| `GitReset` | `sha` | Git reset executed |
|
|
| `Failover` | `stage`, `from_provider`, `to_provider`, `error` | LLM provider failover |
|
|
| `RetroStarted` | — | Retrospective generation begins |
|
|
| `RetroCompleted` | `duration_ms` | Retrospective generation finished |
|
|
| `RetroFailed` | `error`, `duration_ms` | Retrospective generation failed |
|
|
|
|
**Parallel execution:**
|
|
|
|
| Event | Key fields | Description |
|
|
|---|---|---|
|
|
| `ParallelStarted` | `branch_count`, `join_policy`, `error_policy` | Fan-out begins |
|
|
| `ParallelBranchStarted` | `branch`, `index` | Individual branch begins |
|
|
| `ParallelBranchCompleted` | `branch`, `duration_ms`, `status` | Branch finishes |
|
|
| `ParallelCompleted` | `duration_ms`, `success_count`, `failure_count` | All branches done |
|
|
|
|
**Human-in-the-loop:**
|
|
|
|
| Event | Key fields | Description |
|
|
|---|---|---|
|
|
| `InterviewStarted` | `question`, `stage`, `question_type` | Human input requested |
|
|
| `InterviewCompleted` | `question`, `answer`, `duration_ms` | Human responded |
|
|
| `InterviewTimeout` | `question`, `stage`, `duration_ms` | Human didn't respond in time |
|
|
|
|
**Sandbox and setup:**
|
|
|
|
| Event | Key fields | Description |
|
|
|---|---|---|
|
|
| `Sandbox.Initializing` | `provider` | Sandbox creation started |
|
|
| `Sandbox.Ready` | `provider`, `duration_ms`, `cpu`, `memory` | Sandbox ready |
|
|
| `SetupStarted` | `command_count` | Setup commands beginning |
|
|
| `SetupCommandCompleted` | `command`, `exit_code`, `duration_ms` | Single setup command finished |
|
|
| `SetupFailed` | `command`, `exit_code`, `stderr` | Setup command failed |
|
|
| `SshAccessReady` | `ssh_command` | SSH connection command printed |
|
|
| `StallWatchdogTimeout` | `node`, `idle_seconds` | No activity for too long |
|
|
|
|
### Event envelope format
|
|
|
|
Each line in `progress.jsonl` is a JSON object with a standard envelope:
|
|
|
|
```json
|
|
{
|
|
"ts": "2026-03-05T14:30:01.234Z",
|
|
"run_id": "01JKXYZ...",
|
|
"event": "Agent.ToolCallStarted",
|
|
"stage": "implement",
|
|
"tool_name": "shell",
|
|
"arguments": {"command": "cargo test"}
|
|
}
|
|
```
|
|
|
|
The `ts`, `run_id`, and `event` fields are always present. The remaining fields vary by event type. Events are flattened — nested variants like agent and sandbox events use dot notation (e.g. `Agent.ToolCallStarted`, `Sandbox.Ready`).
|
|
|
|
## Log files
|
|
|
|
Fabro writes two kinds of logs:
|
|
|
|
### Run logs (`progress.jsonl`)
|
|
|
|
Every run writes its event stream to `{run_dir}/progress.jsonl`. This is the primary data source for post-run analysis — [retros](/execution/retros) read it, and you can query it directly with standard tools:
|
|
|
|
```bash
|
|
# Count tool calls in a run
|
|
grep "ToolCallStarted" ~/.fabro/runs/01JKXYZ.../progress.jsonl | wc -l
|
|
|
|
# Find all failures
|
|
grep -E "StageFailed|WorkflowRunFailed" ~/.fabro/runs/01JKXYZ.../progress.jsonl | jq .
|
|
|
|
# See which edges were taken
|
|
grep "EdgeSelected" ~/.fabro/runs/01JKXYZ.../progress.jsonl | jq '{from: .from_node, to: .to_node}'
|
|
```
|
|
|
|
### Live snapshot (`live.json`)
|
|
|
|
During execution, Fabro also writes `live.json` — a pretty-printed copy of the most recent event. This is useful for quick status checks while a run is in progress:
|
|
|
|
```bash
|
|
cat ~/.fabro/runs/01JKXYZ.../live.json
|
|
```
|
|
|
|
### Application logs
|
|
|
|
Fabro uses the `tracing` crate to write structured logs to `~/.fabro/logs/YYYY-MM-DD.log`. Control the log level with the `FABRO_LOG` environment variable:
|
|
|
|
```bash
|
|
FABRO_LOG=debug fabro run workflow.fabro
|
|
```
|
|
|
|
| Level | What's logged |
|
|
|---|---|
|
|
| `error` | Run failures, stage failures that won't retry |
|
|
| `warn` | Retries, timeouts, interview timeouts, failovers, early terminations |
|
|
| `info` | Run start/complete, SSH access ready |
|
|
| `debug` | Stage start/complete, edge selections, checkpoints, parallel branches, tool calls |
|
|
|
|
## Real-time monitoring
|
|
|
|
### API: Server-Sent Events
|
|
|
|
When running workflows through the API server, subscribe to a live event stream via the [run events endpoint](/api-reference/runs#get-events). Each event is a JSON-serialized `WorkflowRunEvent`. The stream stays open until the run completes.
|
|
|
|
### Web UI
|
|
|
|
The web frontend connects to the SSE stream automatically and displays run progress in real time — stage transitions, agent tool calls, and human gate prompts are all visible as they happen.
|
|
|
|
<Frame caption="The Stages tab shows the full agent conversation including tool calls and responses.">
|
|
<img src="/images/web/run-stages.png" alt="Fabro web UI run stages showing agent conversation with tool calls" />
|
|
</Frame>
|
|
|
|
### CLI progress
|
|
|
|
The CLI displays a live progress bar during execution with per-stage status, duration, and cost tracking. This is rendered to stderr so it doesn't interfere with output piping.
|
|
|
|
## Post-run analysis
|
|
|
|
### Listing runs
|
|
|
|
Browse your run history with `fabro ps`:
|
|
|
|
```bash
|
|
fabro ps
|
|
fabro ps --workflow PlanImplement
|
|
fabro ps --before 2026-03-01
|
|
fabro ps --label team=platform
|
|
fabro ps --json
|
|
```
|
|
|
|
This scans `~/.fabro/runs/` and displays each run's ID, workflow name, status, and start time. Use `--json` for machine-readable output.
|
|
|
|
### Run artifacts
|
|
|
|
Each run's directory contains a standard set of files:
|
|
|
|
| File | Description |
|
|
|---|---|
|
|
| `manifest.json` | Run metadata — ID, workflow name, start time, labels |
|
|
| `progress.jsonl` | Full event stream |
|
|
| `live.json` | Last event snapshot (overwritten during run) |
|
|
| `checkpoint.json` | Final execution state |
|
|
| `retro.json` | Retrospective (if retro generation is enabled) |
|
|
| `conclusion.json` | Terminal status (`completed`, `failed`, `canceled`) |
|
|
|
|
### Inspecting stages and turns
|
|
|
|
The API provides endpoints for drilling into individual stages and the agent turns within them. See the [stages](/api-reference/run-internals#get-stages) and [turns](/api-reference/run-internals#get-stage-turns) API reference for details.
|
|
|
|
## Insights (SQL analytics)
|
|
|
|
The Insights feature lets you run SQL queries across your run data using DuckDB. This is useful for aggregate analysis — finding slow workflows, tracking failure rates, comparing model costs, and spotting trends.
|
|
|
|
Insights is managed through the [Insights API endpoints](/api-reference/insights). You can save, update, and execute queries programmatically.
|
|
|
|
### Example queries
|
|
|
|
**Average run duration by workflow:**
|
|
|
|
```sql
|
|
SELECT workflow_name, AVG(duration_seconds) as avg_duration,
|
|
COUNT(*) as run_count
|
|
FROM runs
|
|
GROUP BY workflow_name
|
|
ORDER BY avg_duration DESC
|
|
LIMIT 20
|
|
```
|
|
|
|
**Daily failure rate:**
|
|
|
|
```sql
|
|
SELECT date_trunc('day', created_at) as day,
|
|
COUNT(*) FILTER (WHERE status = 'failed') as failures,
|
|
COUNT(*) as total
|
|
FROM runs
|
|
GROUP BY 1
|
|
ORDER BY 1 DESC
|
|
LIMIT 30
|
|
```
|
|
|
|
**Top repositories by activity:**
|
|
|
|
```sql
|
|
SELECT repo, COUNT(*) as runs
|
|
FROM runs
|
|
GROUP BY repo
|
|
ORDER BY runs DESC
|
|
```
|
|
|
|
## Aggregate usage
|
|
|
|
The API server tracks aggregate usage counters across all runs — total run count, total runtime, and per-model breakdowns of token usage and cost. See the [usage endpoint](/api-reference/run-outputs#get-usage) in the API reference. Counters reset on server restart.
|
|
|
|
<Frame caption="The Usage tab breaks down token counts and costs by stage and by model.">
|
|
<img src="/images/web/run-usage.png" alt="Fabro web UI run usage showing per-stage and per-model token and cost breakdown" />
|
|
</Frame>
|
|
|
|
## Credential redaction
|
|
|
|
All event output — `progress.jsonl`, SSE streams, and log files — is automatically redacted before being written. Fabro detects and replaces patterns that look like API keys, AWS credentials, bearer tokens, and other secrets with `REDACTED`. This ensures sensitive values that appear in tool call arguments or command output are never persisted to disk.
|