# Events Every serialized run event envelope, whether streamed over SSE, returned by `fabro logs`, or written to a JSONL sink, uses this structure: ```json { "id": "019234ab-cdef-7890-abcd-ef1234567890", "ts": "2026-04-01T12:00:00.123Z", "run_id": "01JQXYZ...", "event": "stage.completed", "session_id": "ses_abc", "parent_session_id": "ses_parent", "node_id": "code", "node_label": "Write Code", "properties": { ... } } ``` ### Envelope fields | Field | Type | Description | |-------|------|-------------| | `id` | string | UUID v7 (time-ordered), unique per event | | `ts` | string | RFC 3339 timestamp with millisecond precision | | `run_id` | string | ULID of the run | | `event` | string | Dot-notation event name | | `session_id` | string? | Agent session id (agent events only) | | `parent_session_id` | string? | Parent agent session id (agent events only) | | `node_id` | string? | Node id (stage, checkpoint, agent, parallel branch, and other node-scoped events) | | `node_label` | string? | Display label for the node (defaults to `node_id` when not set separately) | | `properties` | object | Event-specific fields | --- ## Run events ### `run.started` Emitted when the workflow run begins. ```json { "id": "...", "ts": "...", "run_id": "...", "event": "run.started", "properties": { "name": "my-workflow", "base_branch": "main", "base_sha": "abc123...", "run_branch": "fabro/run-01JQXYZ", "worktree_dir": "/tmp/fabro-worktrees/...", "goal": "Fix the login bug" } } ``` | Property | Type | Description | |----------|------|-------------| | `name` | string | Workflow name | | `base_branch` | string? | Base git branch | | `base_sha` | string? | Base commit SHA | | `run_branch` | string? | Git branch created for this run | | `worktree_dir` | string? | Worktree directory path | | `goal` | string? | Workflow goal text | Note: `run_id` is in the envelope, not in properties. ### `run.completed` Emitted when the workflow run finishes successfully (or with partial success). ```json { "id": "...", "ts": "...", "run_id": "...", "event": "run.completed", "properties": { "duration_ms": 45000, "artifact_count": 3, "status": "success", "total_cost": 0.15, "final_git_commit_sha": "def456...", "usage": { "input_tokens": 15000, "output_tokens": 5000, "total_tokens": 20000, "reasoning_tokens": 2000, "cache_read_tokens": 8000, "cache_write_tokens": 3000, "speed": "standard" } } } ``` | Property | Type | Description | |----------|------|-------------| | `duration_ms` | number | Total run duration in milliseconds | | `artifact_count` | number | Number of artifacts produced | | `status` | string | Final status (`"success"`, `"fail"`, `"partial_success"`) | | `total_cost` | number? | Aggregate cost in USD | | `final_git_commit_sha` | string? | Final HEAD SHA | | `usage` | object? | Aggregate token usage | | `usage.input_tokens` | number | Total input tokens | | `usage.output_tokens` | number | Total output tokens | | `usage.total_tokens` | number | Total tokens (input + output) | | `usage.reasoning_tokens` | number? | Total reasoning/thinking tokens | | `usage.cache_read_tokens` | number? | Total cache read tokens | | `usage.cache_write_tokens` | number? | Total cache write tokens | | `usage.speed` | string? | Speed tier | | `usage.raw` | object? | Raw provider-specific usage data | ### `run.failed` Emitted when the workflow run fails. ```json { "id": "...", "ts": "...", "run_id": "...", "event": "run.failed", "properties": { "error": "Handler error: compilation failed", "duration_ms": 12000, "git_commit_sha": "abc123..." } } ``` | Property | Type | Description | |----------|------|-------------| | `error` | string | Error message (Display representation) | | `duration_ms` | number | Run duration before failure | | `git_commit_sha` | string? | HEAD SHA at time of failure | ### `run.notice` Informational, warning, or error notice emitted during the run. ```json { "id": "...", "ts": "...", "run_id": "...", "event": "run.notice", "properties": { "level": "warn", "code": "missing_env_var", "message": "GITHUB_TOKEN not set, PR creation will be skipped" } } ``` | Property | Type | Description | |----------|------|-------------| | `level` | string | `"info"`, `"warn"`, or `"error"` | | `code` | string | Machine-readable notice code | | `message` | string | Human-readable message | --- ## Stage events ### `stage.started` Emitted when a workflow node begins execution. ```json { "id": "...", "ts": "...", "run_id": "...", "event": "stage.started", "node_id": "code", "node_label": "Write Code", "properties": { "index": 1, "handler_type": "agent", "attempt": 1, "max_attempts": 3 } } ``` | Property | Type | Description | |----------|------|-------------| | `index` | number | Stage execution order index | | `handler_type` | string | Handler type (`"agent"`, `"prompt"`, `"command"`, `"conditional"`, `"human"`, `"parallel"`, etc.) | | `attempt` | number | Current attempt number (1-based) | | `max_attempts` | number | Maximum attempts allowed | ### `stage.completed` Emitted when a workflow node finishes execution. ```json { "id": "...", "ts": "...", "run_id": "...", "event": "stage.completed", "node_id": "code", "node_label": "Write Code", "properties": { "index": 1, "duration_ms": 8000, "status": "success", "preferred_label": "tests_pass", "suggested_next_ids": ["review"], "usage": { "model": "claude-sonnet-4-20250514", "input_tokens": 5000, "output_tokens": 2000, "cache_read_tokens": 3000, "cache_write_tokens": 1000, "reasoning_tokens": 500, "speed": "standard", "cost": 0.05 }, "error": "lint failed", "failure_class": "deterministic", "failure_signature": "clippy::unused_import", "context_updates": {"response.code": "done"}, "jump_to_node": "review", "context_values": {"response.code": "done"}, "node_visits": {"code": 1}, "loop_failure_signatures": {"code|deterministic|clippy::unused_import": 2}, "restart_failure_signatures": {"code|transient_infra|timeout": 1}, "response": "done", "notes": "All tests passing", "files_touched": ["src/main.rs", "src/lib.rs"], "attempt": 1, "max_attempts": 3 } } ``` | Property | Type | Description | |----------|------|-------------| | `index` | number | Stage execution order index | | `duration_ms` | number | Stage duration in milliseconds | | `status` | string | `"success"`, `"fail"`, `"skipped"`, `"partial_success"`, `"retry"` | | `preferred_label` | string? | Edge label hint for routing | | `suggested_next_ids` | string[] | Suggested successor node ids | | `usage` | object? | Token usage for this stage | | `usage.model` | string | Model identifier | | `usage.input_tokens` | number | Input tokens | | `usage.output_tokens` | number | Output tokens | | `usage.cache_read_tokens` | number? | Cache read tokens | | `usage.cache_write_tokens` | number? | Cache write tokens | | `usage.reasoning_tokens` | number? | Reasoning/thinking tokens | | `usage.speed` | string? | Speed tier | | `usage.cost` | number? | Estimated cost in USD | | `error` | string? | Error message (flattened from failure detail) | | `failure_class` | string? | `"transient_infra"`, `"deterministic"`, `"budget_exhausted"`, `"compilation_loop"`, `"canceled"`, `"structural"` | | `failure_signature` | string? | Dedup key for repeated failures | | `context_updates` | object? | Context delta written by this stage | | `jump_to_node` | string? | Non-edge jump target | | `context_values` | object? | Full context snapshot after the stage | | `node_visits` | object? | Node visit counts after the stage | | `loop_failure_signatures` | object? | Loop failure signature counts | | `restart_failure_signatures` | object? | Restart failure signature counts | | `response` | string? | Full LLM or agent response text when produced by the stage | | `notes` | string? | Free-text notes | | `files_touched` | string[] | File paths modified | | `attempt` | number | Attempt number (1-based) | | `max_attempts` | number | Maximum attempts allowed | Note: `failure` is flattened — the `failure.message` becomes `error`, `failure.failure_class` becomes `failure_class`, `failure.failure_signature` becomes `failure_signature`. ### `stage.failed` Emitted when a stage fails (before retry decision). ```json { "id": "...", "ts": "...", "run_id": "...", "event": "stage.failed", "node_id": "code", "node_label": "Write Code", "properties": { "index": 1, "error": "compilation failed", "failure_class": "deterministic", "failure_signature": "rustc::E0308", "will_retry": true } } ``` | Property | Type | Description | |----------|------|-------------| | `index` | number | Stage execution order index | | `error` | string | Error message (flattened from failure detail) | | `failure_class` | string | Failure category | | `failure_signature` | string? | Dedup key for repeated failures | | `will_retry` | boolean | Whether the stage will be retried | ### `stage.retrying` Emitted when a stage is about to be retried. ```json { "id": "...", "ts": "...", "run_id": "...", "event": "stage.retrying", "node_id": "code", "node_label": "Write Code", "properties": { "index": 1, "attempt": 2, "max_attempts": 3, "delay_ms": 1000 } } ``` | Property | Type | Description | |----------|------|-------------| | `index` | number | Stage execution order index | | `attempt` | number | Next attempt number | | `max_attempts` | number | Maximum attempts allowed | | `delay_ms` | number | Delay before retry in milliseconds | ### `stage.prompt` Emitted when a prompt is rendered for an LLM stage. ```json { "id": "...", "ts": "...", "run_id": "...", "event": "stage.prompt", "node_id": "code", "node_label": "code", "properties": { "text": "You are a coding agent. Fix the bug in..." } } ``` | Property | Type | Description | |----------|------|-------------| | `text` | string | Rendered prompt text | --- ## Parallel events ### `parallel.started` Emitted when a parallel node begins executing branches. ```json { "id": "...", "ts": "...", "run_id": "...", "event": "parallel.started", "properties": { "branch_count": 3, "join_policy": "all" } } ``` | Property | Type | Description | |----------|------|-------------| | `branch_count` | number | Number of parallel branches | | `join_policy` | string | Join policy | ### `parallel.branch.started` Emitted when a parallel branch begins. ```json { "id": "...", "ts": "...", "run_id": "...", "event": "parallel.branch.started", "node_id": "branch_a", "node_label": "branch_a", "properties": { "index": 0 } } ``` | Property | Type | Description | |----------|------|-------------| | `index` | number | Branch index | ### `parallel.branch.completed` Emitted when a parallel branch finishes. ```json { "id": "...", "ts": "...", "run_id": "...", "event": "parallel.branch.completed", "node_id": "branch_a", "node_label": "branch_a", "properties": { "index": 0, "duration_ms": 5000, "status": "success" } } ``` | Property | Type | Description | |----------|------|-------------| | `index` | number | Branch index | | `duration_ms` | number | Branch duration in milliseconds | | `status` | string | Branch outcome status | ### `parallel.completed` Emitted when all parallel branches have finished. ```json { "id": "...", "ts": "...", "run_id": "...", "event": "parallel.completed", "properties": { "duration_ms": 12000, "success_count": 2, "failure_count": 1 } } ``` | Property | Type | Description | |----------|------|-------------| | `duration_ms` | number | Total parallel duration | | `success_count` | number | Branches that succeeded | | `failure_count` | number | Branches that failed | --- ## Interview events ### `interview.started` Emitted when a human-in-the-loop question is posed. ```json { "id": "...", "ts": "...", "run_id": "...", "event": "interview.started", "node_id": "review", "node_label": "review", "properties": { "question": "Does this look correct?", "question_type": "approval" } } ``` | Property | Type | Description | |----------|------|-------------| | `question` | string | Question text | | `question_type` | string | Type of question | ### `interview.completed` Emitted when a human answers. ```json { "id": "...", "ts": "...", "run_id": "...", "event": "interview.completed", "properties": { "question": "Does this look correct?", "answer": "yes", "duration_ms": 30000 } } ``` | Property | Type | Description | |----------|------|-------------| | `question` | string | Question text | | `answer` | string | Human's answer | | `duration_ms` | number | Time waiting for answer | ### `interview.timeout` Emitted when a human question times out. ```json { "id": "...", "ts": "...", "run_id": "...", "event": "interview.timeout", "node_id": "review", "node_label": "review", "properties": { "question": "Does this look correct?", "duration_ms": 300000 } } ``` | Property | Type | Description | |----------|------|-------------| | `question` | string | Question text | | `duration_ms` | number | Time waited before timeout | --- ## Checkpoint events ### `checkpoint.completed` Emitted after a checkpoint is saved. ```json { "id": "...", "ts": "...", "run_id": "...", "event": "checkpoint.completed", "node_id": "code", "node_label": "code", "properties": { "status": "success", "git_commit_sha": "abc123...", "diff": "diff --git a/src/lib.rs b/src/lib.rs\n..." } } ``` | Property | Type | Description | |----------|------|-------------| | `status` | string | Checkpoint status | | `git_commit_sha` | string? | Commit SHA at checkpoint time | | `diff` | string? | Git diff captured for the checkpointed node | ### `checkpoint.failed` Emitted when checkpoint saving fails. ```json { "id": "...", "ts": "...", "run_id": "...", "event": "checkpoint.failed", "node_id": "code", "node_label": "code", "properties": { "error": "git commit failed: ..." } } ``` | Property | Type | Description | |----------|------|-------------| | `error` | string | Error message | --- ## Git events ### `git.commit` ```json { "id": "...", "ts": "...", "run_id": "...", "event": "git.commit", "node_id": "code", "node_label": "code", "properties": { "sha": "abc123..." } } ``` | Property | Type | Description | |----------|------|-------------| | `sha` | string | Commit SHA | Note: `node_id` is optional — may be absent for non-stage commits. ### `git.push` ```json { "id": "...", "ts": "...", "run_id": "...", "event": "git.push", "properties": { "branch": "fabro/run-01JQXYZ", "success": true } } ``` | Property | Type | Description | |----------|------|-------------| | `branch` | string | Branch name | | `success` | boolean | Whether push succeeded | ### `git.branch` ```json { "id": "...", "ts": "...", "run_id": "...", "event": "git.branch", "properties": { "branch": "fabro/run-01JQXYZ", "sha": "abc123..." } } ``` | Property | Type | Description | |----------|------|-------------| | `branch` | string | Branch name | | `sha` | string | Branch HEAD SHA | ### `git.worktree.added` ```json { "id": "...", "ts": "...", "run_id": "...", "event": "git.worktree.added", "properties": { "path": "/tmp/fabro-worktrees/...", "branch": "fabro/run-01JQXYZ" } } ``` | Property | Type | Description | |----------|------|-------------| | `path` | string | Worktree directory path | | `branch` | string | Branch name | ### `git.worktree.removed` ```json { "id": "...", "ts": "...", "run_id": "...", "event": "git.worktree.removed", "properties": { "path": "/tmp/fabro-worktrees/..." } } ``` | Property | Type | Description | |----------|------|-------------| | `path` | string | Worktree directory path | ### `git.fetch` ```json { "id": "...", "ts": "...", "run_id": "...", "event": "git.fetch", "properties": { "branch": "main", "success": true } } ``` | Property | Type | Description | |----------|------|-------------| | `branch` | string | Branch name | | `success` | boolean | Whether fetch succeeded | ### `git.reset` ```json { "id": "...", "ts": "...", "run_id": "...", "event": "git.reset", "properties": { "sha": "abc123..." } } ``` | Property | Type | Description | |----------|------|-------------| | `sha` | string | Target commit SHA | --- ## Routing events ### `edge.selected` Emitted when the engine selects the next edge to traverse. ```json { "id": "...", "ts": "...", "run_id": "...", "event": "edge.selected", "properties": { "from_node": "code", "to_node": "review", "label": "tests_pass", "condition": "outcome=success", "reason": "condition", "preferred_label": "tests_pass", "suggested_next_ids": ["review"], "stage_status": "success", "is_jump": false } } ``` | Property | Type | Description | |----------|------|-------------| | `from_node` | string | Source node id | | `to_node` | string | Target node id | | `label` | string? | Edge label | | `condition` | string? | Edge condition expression | | `reason` | string | Selection reason (`"condition"`, `"preferred_label"`, `"jump"`, etc.) | | `preferred_label` | string? | Stage's preferred label hint | | `suggested_next_ids` | string[] | Stage's suggested next node ids | | `stage_status` | string | Outcome status that influenced routing | | `is_jump` | boolean | Whether this bypassed normal edge selection | ### `loop.restart` Emitted when execution loops back to an earlier node. ```json { "id": "...", "ts": "...", "run_id": "...", "event": "loop.restart", "properties": { "from_node": "review", "to_node": "code" } } ``` | Property | Type | Description | |----------|------|-------------| | `from_node` | string | Node that triggered the restart | | `to_node` | string | Node to restart from | --- ## Agent events All agent events have `node_id` (the workflow stage), `node_label`, `session_id`, and `parent_session_id` in the envelope. The `properties` contain the inner agent event fields. ### `agent.session.started` ```json { "id": "...", "ts": "...", "run_id": "...", "event": "agent.session.started", "node_id": "code", "node_label": "code", "session_id": "ses_abc", "parent_session_id": null, "properties": {} } ``` No properties. ### `agent.session.ended` ```json { "id": "...", "ts": "...", "run_id": "...", "event": "agent.session.ended", "node_id": "code", "node_label": "code", "session_id": "ses_abc", "properties": {} } ``` No properties. ### `agent.processing.end` ```json { "id": "...", "ts": "...", "run_id": "...", "event": "agent.processing.end", "node_id": "code", "node_label": "code", "session_id": "ses_abc", "properties": {} } ``` No properties. ### `agent.input` ```json { "id": "...", "ts": "...", "run_id": "...", "event": "agent.input", "node_id": "code", "node_label": "code", "session_id": "ses_abc", "properties": { "text": "Fix the login bug in auth.rs" } } ``` | Property | Type | Description | |----------|------|-------------| | `text` | string | User input text | ### `agent.output.start` Signals the beginning of assistant text output. ```json { "id": "...", "ts": "...", "run_id": "...", "event": "agent.output.start", "node_id": "code", "node_label": "code", "session_id": "ses_abc", "properties": {} } ``` No properties. ### `agent.output.replace` Replaces the current in-progress assistant output buffers. ```json { "id": "...", "ts": "...", "run_id": "...", "event": "agent.output.replace", "node_id": "code", "node_label": "code", "session_id": "ses_abc", "properties": { "text": "I'll fix the login bug by...", "reasoning": "The user wants..." } } ``` | Property | Type | Description | |----------|------|-------------| | `text` | string | Replacement assistant text | | `reasoning` | string? | Replacement reasoning text | ### `agent.message` Emitted when the assistant produces a complete message. ```json { "id": "...", "ts": "...", "run_id": "...", "event": "agent.message", "node_id": "code", "node_label": "code", "session_id": "ses_abc", "properties": { "text": "I've fixed the bug in auth.rs by...", "model": "claude-sonnet-4-20250514", "usage": { "input_tokens": 3000, "output_tokens": 1500, "total_tokens": 4500, "reasoning_tokens": 200, "cache_read_tokens": 1000, "cache_write_tokens": 500 }, "tool_call_count": 2 } } ``` | Property | Type | Description | |----------|------|-------------| | `text` | string | Assistant message text | | `model` | string | Model identifier | | `usage` | object | Token usage for this message | | `usage.input_tokens` | number | Input tokens | | `usage.output_tokens` | number | Output tokens | | `usage.total_tokens` | number | Total tokens | | `usage.reasoning_tokens` | number? | Reasoning tokens | | `usage.cache_read_tokens` | number? | Cache read tokens | | `usage.cache_write_tokens` | number? | Cache write tokens | | `usage.speed` | string? | Speed tier | | `usage.raw` | object? | Raw provider-specific usage | | `tool_call_count` | number | Number of tool calls in this turn | ### `agent.text.delta` Streaming text chunk from the assistant. ```json { "id": "...", "ts": "...", "run_id": "...", "event": "agent.text.delta", "node_id": "code", "node_label": "code", "session_id": "ses_abc", "properties": { "delta": "I'll start by reading" } } ``` | Property | Type | Description | |----------|------|-------------| | `delta` | string | Text chunk | ### `agent.reasoning.delta` Streaming reasoning/thinking chunk from the assistant. ```json { "id": "...", "ts": "...", "run_id": "...", "event": "agent.reasoning.delta", "node_id": "code", "node_label": "code", "session_id": "ses_abc", "properties": { "delta": "The user needs me to..." } } ``` | Property | Type | Description | |----------|------|-------------| | `delta` | string | Reasoning text chunk | ### `agent.tool.started` Emitted when the agent begins a tool call. ```json { "id": "...", "ts": "...", "run_id": "...", "event": "agent.tool.started", "node_id": "code", "node_label": "code", "session_id": "ses_abc", "properties": { "tool_name": "read_file", "tool_call_id": "call_abc123", "arguments": {"path": "src/auth.rs"} } } ``` | Property | Type | Description | |----------|------|-------------| | `tool_name` | string | Tool name | | `tool_call_id` | string | Unique tool call id | | `arguments` | object | Tool call arguments | ### `agent.tool.output.delta` Streaming tool output chunk. ```json { "id": "...", "ts": "...", "run_id": "...", "event": "agent.tool.output.delta", "node_id": "code", "node_label": "code", "session_id": "ses_abc", "properties": { "delta": "fn login(user: &str)..." } } ``` | Property | Type | Description | |----------|------|-------------| | `delta` | string | Output text chunk | ### `agent.tool.completed` Emitted when a tool call finishes. ```json { "id": "...", "ts": "...", "run_id": "...", "event": "agent.tool.completed", "node_id": "code", "node_label": "code", "session_id": "ses_abc", "properties": { "tool_name": "read_file", "tool_call_id": "call_abc123", "output": "fn login(user: &str) -> Result...", "is_error": false } } ``` | Property | Type | Description | |----------|------|-------------| | `tool_name` | string | Tool name | | `tool_call_id` | string | Unique tool call id | | `output` | any | Tool output (string or structured) | | `is_error` | boolean | Whether the tool returned an error | ### `agent.error` Emitted when the agent encounters an error. ```json { "id": "...", "ts": "...", "run_id": "...", "event": "agent.error", "node_id": "code", "node_label": "code", "session_id": "ses_abc", "properties": { "error": { ... } } } ``` | Property | Type | Description | |----------|------|-------------| | `error` | object | AgentError (serialized) | ### `agent.warning` ```json { "id": "...", "ts": "...", "run_id": "...", "event": "agent.warning", "node_id": "code", "node_label": "code", "session_id": "ses_abc", "properties": { "kind": "token_limit", "message": "Approaching context window limit", "details": {} } } ``` | Property | Type | Description | |----------|------|-------------| | `kind` | string | Warning kind | | `message` | string | Warning message | | `details` | object | Additional details | ### `agent.loop.detected` Emitted when the agent detects a tool-use loop. ```json { "id": "...", "ts": "...", "run_id": "...", "event": "agent.loop.detected", "node_id": "code", "node_label": "code", "session_id": "ses_abc", "properties": {} } ``` No properties. ### `agent.turn.limit` Emitted when the agent reaches its maximum turn count. ```json { "id": "...", "ts": "...", "run_id": "...", "event": "agent.turn.limit", "node_id": "code", "node_label": "code", "session_id": "ses_abc", "properties": { "max_turns": 25 } } ``` | Property | Type | Description | |----------|------|-------------| | `max_turns` | number | Maximum turns allowed | ### `agent.skill.expanded` ```json { "id": "...", "ts": "...", "run_id": "...", "event": "agent.skill.expanded", "node_id": "code", "node_label": "code", "session_id": "ses_abc", "properties": { "skill_name": "read_file" } } ``` | Property | Type | Description | |----------|------|-------------| | `skill_name` | string | Expanded skill name | ### `agent.steering.injected` ```json { "id": "...", "ts": "...", "run_id": "...", "event": "agent.steering.injected", "node_id": "code", "node_label": "code", "session_id": "ses_abc", "properties": { "text": "Remember to run tests after changes" } } ``` | Property | Type | Description | |----------|------|-------------| | `text` | string | Injected steering text | ### `agent.compaction.started` ```json { "id": "...", "ts": "...", "run_id": "...", "event": "agent.compaction.started", "node_id": "code", "node_label": "code", "session_id": "ses_abc", "properties": { "estimated_tokens": 50000, "context_window_size": 128000 } } ``` | Property | Type | Description | |----------|------|-------------| | `estimated_tokens` | number | Estimated tokens before compaction | | `context_window_size` | number | Model context window size | ### `agent.compaction.completed` ```json { "id": "...", "ts": "...", "run_id": "...", "event": "agent.compaction.completed", "node_id": "code", "node_label": "code", "session_id": "ses_abc", "properties": { "original_turn_count": 40, "preserved_turn_count": 10, "summary_token_estimate": 2000, "tracked_file_count": 5 } } ``` | Property | Type | Description | |----------|------|-------------| | `original_turn_count` | number | Turns before compaction | | `preserved_turn_count` | number | Turns preserved | | `summary_token_estimate` | number | Token estimate for summary | | `tracked_file_count` | number | Files being tracked | ### `agent.llm.retry` Emitted when an LLM API call is retried. ```json { "id": "...", "ts": "...", "run_id": "...", "event": "agent.llm.retry", "node_id": "code", "node_label": "code", "session_id": "ses_abc", "properties": { "provider": "anthropic", "model": "claude-sonnet-4-20250514", "attempt": 2, "delay_secs": 1.5, "error": { ... } } } ``` | Property | Type | Description | |----------|------|-------------| | `provider` | string | LLM provider name | | `model` | string | Model identifier | | `attempt` | number | Retry attempt number | | `delay_secs` | number | Delay before retry in seconds | | `error` | object | SdkError (serialized) | ### `agent.sub.spawned` Emitted when a sub-agent is spawned. ```json { "id": "...", "ts": "...", "run_id": "...", "event": "agent.sub.spawned", "node_id": "code", "node_label": "code", "session_id": "ses_abc", "properties": { "agent_id": "sub_xyz", "depth": 1, "task": "Write unit tests for auth.rs" } } ``` | Property | Type | Description | |----------|------|-------------| | `agent_id` | string | Sub-agent identifier | | `depth` | number | Nesting depth | | `task` | string | Task description | ### `agent.sub.completed` ```json { "id": "...", "ts": "...", "run_id": "...", "event": "agent.sub.completed", "node_id": "code", "node_label": "code", "session_id": "ses_abc", "properties": { "agent_id": "sub_xyz", "depth": 1, "success": true, "turns_used": 8 } } ``` | Property | Type | Description | |----------|------|-------------| | `agent_id` | string | Sub-agent identifier | | `depth` | number | Nesting depth | | `success` | boolean | Whether the sub-agent succeeded | | `turns_used` | number | Number of turns used | ### `agent.sub.failed` ```json { "id": "...", "ts": "...", "run_id": "...", "event": "agent.sub.failed", "node_id": "code", "node_label": "code", "session_id": "ses_abc", "properties": { "agent_id": "sub_xyz", "depth": 1, "error": { ... } } } ``` | Property | Type | Description | |----------|------|-------------| | `agent_id` | string | Sub-agent identifier | | `depth` | number | Nesting depth | | `error` | object | AgentError (serialized) | ### `agent.sub.closed` ```json { "id": "...", "ts": "...", "run_id": "...", "event": "agent.sub.closed", "node_id": "code", "node_label": "code", "session_id": "ses_abc", "properties": { "agent_id": "sub_xyz", "depth": 1 } } ``` | Property | Type | Description | |----------|------|-------------| | `agent_id` | string | Sub-agent identifier | | `depth` | number | Nesting depth | ### `agent.mcp.ready` ```json { "id": "...", "ts": "...", "run_id": "...", "event": "agent.mcp.ready", "node_id": "code", "node_label": "code", "session_id": "ses_abc", "properties": { "server_name": "filesystem", "tool_count": 5 } } ``` | Property | Type | Description | |----------|------|-------------| | `server_name` | string | MCP server name | | `tool_count` | number | Number of tools available | ### `agent.mcp.failed` ```json { "id": "...", "ts": "...", "run_id": "...", "event": "agent.mcp.failed", "node_id": "code", "node_label": "code", "session_id": "ses_abc", "properties": { "server_name": "filesystem", "error": "Connection refused" } } ``` | Property | Type | Description | |----------|------|-------------| | `server_name` | string | MCP server name | | `error` | string | Error message | ### `agent.failover` Emitted when the agent fails over to a different LLM provider/model. ```json { "id": "...", "ts": "...", "run_id": "...", "event": "agent.failover", "node_id": "code", "node_label": "code", "properties": { "from_provider": "anthropic", "from_model": "claude-sonnet-4-20250514", "to_provider": "openai", "to_model": "gpt-4o", "error": "rate limited" } } ``` | Property | Type | Description | |----------|------|-------------| | `from_provider` | string | Original provider | | `from_model` | string | Original model | | `to_provider` | string | Failover provider | | `to_model` | string | Failover model | | `error` | string | Error that triggered failover | --- ## Subgraph events ### `subgraph.started` ```json { "id": "...", "ts": "...", "run_id": "...", "event": "subgraph.started", "node_id": "pipeline", "node_label": "pipeline", "properties": { "start_node": "sub_start" } } ``` | Property | Type | Description | |----------|------|-------------| | `start_node` | string | First node in the subgraph | ### `subgraph.completed` ```json { "id": "...", "ts": "...", "run_id": "...", "event": "subgraph.completed", "node_id": "pipeline", "node_label": "pipeline", "properties": { "steps_executed": 4, "status": "success", "duration_ms": 25000 } } ``` | Property | Type | Description | |----------|------|-------------| | `steps_executed` | number | Number of steps executed | | `status` | string | Subgraph outcome status | | `duration_ms` | number | Subgraph duration | --- ## Sandbox events Sandbox events have the nested `SandboxEvent` unwrapped into `properties`. ### `sandbox.initializing` ```json { "id": "...", "ts": "...", "run_id": "...", "event": "sandbox.initializing", "properties": { "provider": "daytona" } } ``` | Property | Type | Description | |----------|------|-------------| | `provider` | string | Sandbox provider name | ### `sandbox.ready` ```json { "id": "...", "ts": "...", "run_id": "...", "event": "sandbox.ready", "properties": { "provider": "daytona", "duration_ms": 5000, "name": "sandbox-01JQXYZ", "cpu": 4.0, "memory": 8.0, "url": "https://sandbox.example.com" } } ``` | Property | Type | Description | |----------|------|-------------| | `provider` | string | Sandbox provider name | | `duration_ms` | number | Initialization duration | | `name` | string? | Sandbox instance name | | `cpu` | number? | CPU cores allocated | | `memory` | number? | Memory in GB allocated | | `url` | string? | Sandbox URL | ### `sandbox.failed` ```json { "id": "...", "ts": "...", "run_id": "...", "event": "sandbox.failed", "properties": { "provider": "daytona", "error": "workspace creation failed", "duration_ms": 3000 } } ``` | Property | Type | Description | |----------|------|-------------| | `provider` | string | Sandbox provider name | | `error` | string | Error message | | `duration_ms` | number | Time before failure | ### `sandbox.initialized` Emitted after the engine completes sandbox initialization (distinct from `sandbox.ready` which comes from the sandbox provider). ```json { "id": "...", "ts": "...", "run_id": "...", "event": "sandbox.initialized", "properties": { "working_directory": "/workspace/my-project", "provider": "daytona", "identifier": "sandbox-123", "host_working_directory": "/tmp/fabro-run/worktree", "container_mount_point": "/workspace" } } ``` | Property | Type | Description | |----------|------|-------------| | `working_directory` | string | Working directory inside sandbox | | `provider` | string | Sandbox provider | | `identifier` | string? | Provider-specific sandbox identifier | | `host_working_directory` | string? | Host-side working directory | | `container_mount_point` | string? | Container mount point inside the sandbox | ### `sandbox.cleanup.started` ```json { "id": "...", "ts": "...", "run_id": "...", "event": "sandbox.cleanup.started", "properties": { "provider": "daytona" } } ``` | Property | Type | Description | |----------|------|-------------| | `provider` | string | Sandbox provider name | ### `sandbox.cleanup.completed` ```json { "id": "...", "ts": "...", "run_id": "...", "event": "sandbox.cleanup.completed", "properties": { "provider": "daytona", "duration_ms": 2000 } } ``` | Property | Type | Description | |----------|------|-------------| | `provider` | string | Sandbox provider name | | `duration_ms` | number | Cleanup duration | ### `sandbox.cleanup.failed` ```json { "id": "...", "ts": "...", "run_id": "...", "event": "sandbox.cleanup.failed", "properties": { "provider": "daytona", "error": "workspace not found" } } ``` | Property | Type | Description | |----------|------|-------------| | `provider` | string | Sandbox provider name | | `error` | string | Error message | ### `sandbox.snapshot.pulling` ```json { "id": "...", "ts": "...", "run_id": "...", "event": "sandbox.snapshot.pulling", "properties": { "name": "my-image:latest" } } ``` | Property | Type | Description | |----------|------|-------------| | `name` | string | Image/snapshot name | ### `sandbox.snapshot.pulled` ```json { "id": "...", "ts": "...", "run_id": "...", "event": "sandbox.snapshot.pulled", "properties": { "name": "my-image:latest", "duration_ms": 15000 } } ``` | Property | Type | Description | |----------|------|-------------| | `name` | string | Image/snapshot name | | `duration_ms` | number | Pull duration | ### `sandbox.snapshot.ensuring` ```json { "id": "...", "ts": "...", "run_id": "...", "event": "sandbox.snapshot.ensuring", "properties": { "name": "my-snapshot" } } ``` | Property | Type | Description | |----------|------|-------------| | `name` | string | Snapshot name | ### `sandbox.snapshot.creating` ```json { "id": "...", "ts": "...", "run_id": "...", "event": "sandbox.snapshot.creating", "properties": { "name": "my-snapshot" } } ``` | Property | Type | Description | |----------|------|-------------| | `name` | string | Snapshot name | ### `sandbox.snapshot.ready` ```json { "id": "...", "ts": "...", "run_id": "...", "event": "sandbox.snapshot.ready", "properties": { "name": "my-snapshot", "duration_ms": 30000 } } ``` | Property | Type | Description | |----------|------|-------------| | `name` | string | Snapshot name | | `duration_ms` | number | Creation duration | ### `sandbox.snapshot.failed` ```json { "id": "...", "ts": "...", "run_id": "...", "event": "sandbox.snapshot.failed", "properties": { "name": "my-snapshot", "error": "disk quota exceeded" } } ``` | Property | Type | Description | |----------|------|-------------| | `name` | string | Snapshot name | | `error` | string | Error message | ### `sandbox.git.started` ```json { "id": "...", "ts": "...", "run_id": "...", "event": "sandbox.git.started", "properties": { "url": "https://github.com/org/repo.git", "branch": "main" } } ``` | Property | Type | Description | |----------|------|-------------| | `url` | string | Repository URL | | `branch` | string? | Branch to clone | ### `sandbox.git.completed` ```json { "id": "...", "ts": "...", "run_id": "...", "event": "sandbox.git.completed", "properties": { "url": "https://github.com/org/repo.git", "duration_ms": 8000 } } ``` | Property | Type | Description | |----------|------|-------------| | `url` | string | Repository URL | | `duration_ms` | number | Clone duration | ### `sandbox.git.failed` ```json { "id": "...", "ts": "...", "run_id": "...", "event": "sandbox.git.failed", "properties": { "url": "https://github.com/org/repo.git", "error": "authentication failed" } } ``` | Property | Type | Description | |----------|------|-------------| | `url` | string | Repository URL | | `error` | string | Error message | --- ## Setup events ### `setup.started` ```json { "id": "...", "ts": "...", "run_id": "...", "event": "setup.started", "properties": { "command_count": 3 } } ``` | Property | Type | Description | |----------|------|-------------| | `command_count` | number | Number of setup commands | ### `setup.command.started` ```json { "id": "...", "ts": "...", "run_id": "...", "event": "setup.command.started", "properties": { "command": "npm install", "index": 0 } } ``` | Property | Type | Description | |----------|------|-------------| | `command` | string | Command being run | | `index` | number | Command index | ### `setup.command.completed` ```json { "id": "...", "ts": "...", "run_id": "...", "event": "setup.command.completed", "properties": { "command": "npm install", "index": 0, "exit_code": 0, "duration_ms": 5000 } } ``` | Property | Type | Description | |----------|------|-------------| | `command` | string | Command that ran | | `index` | number | Command index | | `exit_code` | number | Process exit code | | `duration_ms` | number | Command duration | ### `setup.completed` ```json { "id": "...", "ts": "...", "run_id": "...", "event": "setup.completed", "properties": { "duration_ms": 15000 } } ``` | Property | Type | Description | |----------|------|-------------| | `duration_ms` | number | Total setup duration | ### `setup.failed` ```json { "id": "...", "ts": "...", "run_id": "...", "event": "setup.failed", "properties": { "command": "npm install", "index": 1, "exit_code": 1, "stderr": "npm ERR! ..." } } ``` | Property | Type | Description | |----------|------|-------------| | `command` | string | Command that failed | | `index` | number | Command index | | `exit_code` | number | Process exit code | | `stderr` | string | Standard error output | --- ## CLI ensure events ### `cli.ensure.started` ```json { "id": "...", "ts": "...", "run_id": "...", "event": "cli.ensure.started", "properties": { "cli_name": "aider", "provider": "openai" } } ``` | Property | Type | Description | |----------|------|-------------| | `cli_name` | string | CLI tool name | | `provider` | string | LLM provider | ### `cli.ensure.completed` ```json { "id": "...", "ts": "...", "run_id": "...", "event": "cli.ensure.completed", "properties": { "cli_name": "aider", "provider": "openai", "already_installed": true, "node_installed": false, "duration_ms": 500 } } ``` | Property | Type | Description | |----------|------|-------------| | `cli_name` | string | CLI tool name | | `provider` | string | LLM provider | | `already_installed` | boolean | Whether it was already present | | `node_installed` | boolean | Whether Node.js was installed | | `duration_ms` | number | Duration | ### `cli.ensure.failed` ```json { "id": "...", "ts": "...", "run_id": "...", "event": "cli.ensure.failed", "properties": { "cli_name": "aider", "provider": "openai", "error": "pip install failed", "duration_ms": 3000 } } ``` | Property | Type | Description | |----------|------|-------------| | `cli_name` | string | CLI tool name | | `provider` | string | LLM provider | | `error` | string | Error message | | `duration_ms` | number | Duration | --- ## Pull request events ### `pull_request.created` ```json { "id": "...", "ts": "...", "run_id": "...", "event": "pull_request.created", "properties": { "pr_url": "https://github.com/org/repo/pull/42", "pr_number": 42, "draft": true } } ``` | Property | Type | Description | |----------|------|-------------| | `pr_url` | string | Pull request URL | | `pr_number` | number | Pull request number | | `draft` | boolean | Whether the PR is a draft | ### `pull_request.failed` ```json { "id": "...", "ts": "...", "run_id": "...", "event": "pull_request.failed", "properties": { "error": "insufficient permissions" } } ``` | Property | Type | Description | |----------|------|-------------| | `error` | string | Error message | --- ## Devcontainer events ### `devcontainer.resolved` ```json { "id": "...", "ts": "...", "run_id": "...", "event": "devcontainer.resolved", "properties": { "dockerfile_lines": 15, "environment_count": 3, "lifecycle_command_count": 2, "workspace_folder": "/workspace" } } ``` | Property | Type | Description | |----------|------|-------------| | `dockerfile_lines` | number | Lines in generated Dockerfile | | `environment_count` | number | Environment variables defined | | `lifecycle_command_count` | number | Lifecycle commands to run | | `workspace_folder` | string | Workspace folder path | ### `devcontainer.lifecycle.started` ```json { "id": "...", "ts": "...", "run_id": "...", "event": "devcontainer.lifecycle.started", "properties": { "phase": "postCreateCommand", "command_count": 2 } } ``` | Property | Type | Description | |----------|------|-------------| | `phase` | string | Lifecycle phase name | | `command_count` | number | Commands in this phase | ### `devcontainer.lifecycle.command.started` ```json { "id": "...", "ts": "...", "run_id": "...", "event": "devcontainer.lifecycle.command.started", "properties": { "phase": "postCreateCommand", "command": "npm install", "index": 0 } } ``` | Property | Type | Description | |----------|------|-------------| | `phase` | string | Lifecycle phase name | | `command` | string | Command being run | | `index` | number | Command index | ### `devcontainer.lifecycle.command.completed` ```json { "id": "...", "ts": "...", "run_id": "...", "event": "devcontainer.lifecycle.command.completed", "properties": { "phase": "postCreateCommand", "command": "npm install", "index": 0, "exit_code": 0, "duration_ms": 8000 } } ``` | Property | Type | Description | |----------|------|-------------| | `phase` | string | Lifecycle phase name | | `command` | string | Command that ran | | `index` | number | Command index | | `exit_code` | number | Process exit code | | `duration_ms` | number | Command duration | ### `devcontainer.lifecycle.completed` ```json { "id": "...", "ts": "...", "run_id": "...", "event": "devcontainer.lifecycle.completed", "properties": { "phase": "postCreateCommand", "duration_ms": 12000 } } ``` | Property | Type | Description | |----------|------|-------------| | `phase` | string | Lifecycle phase name | | `duration_ms` | number | Phase duration | ### `devcontainer.lifecycle.failed` ```json { "id": "...", "ts": "...", "run_id": "...", "event": "devcontainer.lifecycle.failed", "properties": { "phase": "postCreateCommand", "command": "npm install", "index": 0, "exit_code": 1, "stderr": "npm ERR! ..." } } ``` | Property | Type | Description | |----------|------|-------------| | `phase` | string | Lifecycle phase name | | `command` | string | Command that failed | | `index` | number | Command index | | `exit_code` | number | Process exit code | | `stderr` | string | Standard error output | --- ## Asset events ### `asset.captured` ```json { "id": "...", "ts": "...", "run_id": "...", "event": "asset.captured", "node_id": "code", "node_label": "code", "properties": { "attempt": 1, "node_slug": "code", "path": "screenshot.png", "mime": "image/png", "content_md5": "d41d8cd98f00b204e9800998ecf8427e", "content_sha256": "e3b0c44298fc1c149afbf4c8996fb924...", "bytes": 45000 } } ``` | Property | Type | Description | |----------|------|-------------| | `attempt` | number | Attempt number | | `node_slug` | string | Node slug for asset path | | `path` | string | Asset file path | | `mime` | string | MIME type | | `content_md5` | string | MD5 hash | | `content_sha256` | string | SHA-256 hash | | `bytes` | number | File size in bytes | --- ## SSH events ### `ssh.ready` ```json { "id": "...", "ts": "...", "run_id": "...", "event": "ssh.ready", "properties": { "ssh_command": "ssh user@host -p 2222" } } ``` | Property | Type | Description | |----------|------|-------------| | `ssh_command` | string | SSH command to connect | --- ## Watchdog events ### `watchdog.timeout` Emitted when the stall watchdog detects no progress. ```json { "id": "...", "ts": "...", "run_id": "...", "event": "watchdog.timeout", "node_id": "code", "node_label": "code", "properties": { "idle_seconds": 1800 } } ``` | Property | Type | Description | |----------|------|-------------| | `idle_seconds` | number | Seconds since last activity | --- ## Retro events ### `retro.started` ```json { "id": "...", "ts": "...", "run_id": "...", "event": "retro.started", "properties": { "prompt": "Analyze the workflow run data at `/tmp/retro_data/` ...", "provider": "anthropic", "model": "claude-sonnet-4-20250514" } } ``` | Property | Type | Description | |----------|------|-------------| | `prompt` | string? | Prompt sent to the retro agent | | `provider` | string? | LLM provider for the retro agent | | `model` | string? | Model used for the retro agent | ### `retro.completed` ```json { "id": "...", "ts": "...", "run_id": "...", "event": "retro.completed", "properties": { "duration_ms": 5000, "response": "The run was mostly smooth...", "retro": {"smoothness": "smooth"} } } ``` | Property | Type | Description | |----------|------|-------------| | `duration_ms` | number | Retro duration | | `response` | string? | Raw assistant response from the retro agent | | `retro` | object? | Parsed `Retro` payload | ### `retro.failed` ```json { "id": "...", "ts": "...", "run_id": "...", "event": "retro.failed", "properties": { "error": "LLM request failed", "duration_ms": 3000 } } ``` | Property | Type | Description | |----------|------|-------------| | `error` | string | Error message | | `duration_ms` | number | Duration before failure |