Two branches, two purposes
Each run creates two Git branches that work in tandem:| Branch | Ref format | Contains |
|---|---|---|
| Run branch | fabro/run/{run_id} | File changes made by agents and commands — the actual work product |
| Metadata branch | fabro/meta/{run_id} | Checkpoint JSON, the workflow graph, run and start records, and offloaded artifacts |
Run branch commits
After each node finishes, Fabro stages file changes and creates a commit on the run branch. Files matching[checkpoint] exclude_globs patterns (configured in run.toml or settings.toml) are excluded from staging:
| Part | Description |
|---|---|
| Subject line | fabro({run_id}): {node_id} ({status}) |
Fabro-Run trailer | The run ID |
Fabro-Completed trailer | Number of completed nodes so far |
Fabro-Checkpoint trailer | SHA of the corresponding commit on the metadata branch |
Fabro-Checkpoint trailer links each run branch commit to its metadata branch commit, so you can navigate from file changes to the full execution state and back.
Fabro disables Git commit and tag signing for checkpoint commits created inside a sandbox. Your personal or repository-level signing settings can stay enabled, but sandbox bookkeeping does not need access to your signing key.
Metadata branch
The metadata branch (fabro/meta/{run_id}) is an orphan branch that stores structured run data using Git’s object storage directly (via git2). It is initialized at run start with:
run.json— Current projection snapshot: run spec, start/status records, current checkpoint, conclusion, sandbox, retro state, and other run-level metadatagraph.fabro— Workflow source for the run
run.json— Refreshed projection snapshot with the new current checkpointstages/{node_id}@{visit}/...— Per-stage execution trace files (prompts, responses, status, diffs, stdout/stderr, and tool metadata)stages/retro/*.md— Retro prompt/response text when present
What’s in a checkpoint
Therun.json.checkpoint snapshot captures everything needed to resume a run:
| Field | Description |
|---|---|
timestamp | When the checkpoint was created |
current_node | The node that just completed |
next_node_id | The next node the engine would execute |
completed_nodes | Ordered list of all completed node IDs |
node_retries | How many retry attempts each node has used |
node_outcomes | Full outcome (status, context updates, usage) for each completed node |
context_values | Snapshot of the entire run context |
git_commit_sha | SHA of the run branch commit at this checkpoint |
loop_failure_signatures | Failure signature counts for loop detection |
restart_failure_signatures | Failure signature counts across loop-restart edges |
resume, inspect, and API reads do not need to rely on scratch files.
Worktrees
Fabro uses Git worktrees to isolate workflow runs from your working directory. When a run starts in a clean Git repository:- Fabro records the current HEAD as the base SHA
- Creates a new branch
fabro/run/{run_id}at that SHA - Adds a worktree at
{run_dir}/worktreeon that branch - Changes into the worktree directory for the duration of the run
If the working directory has uncommitted changes, Fabro skips worktree setup and runs in place, logging a warning. Git checkpointing is disabled in this case.
Resuming a run
Resume an interrupted run from its durable checkpoint:What happens during resume
What happens during resume
- Fabro looks up the run directory by ID prefix
- Validates that a checkpoint exists in durable state and no engine process is already running
- Cleans stale local artifacts from the previous execution
- Resets status to
Submittedand spawns a new engine subprocess with--resume - The engine restores the full context, completed node list, retry counts, and failure signatures from durable state
- If the checkpointed node used
fullfidelity, downgrades the first resumed node tosummary:high(since the original conversation thread no longer exists in memory) - Continues execution from
next_node_id
The checkpoint cycle
Here’s the full sequence that runs after every node completes:- Append checkpoint event — Persist the new checkpoint into durable run state
- Write metadata branch — Serialize the checkpoint and any new artifacts to the metadata branch (shadow commit)
- Commit to run branch — Stage all file changes, commit with structured trailers linking to the shadow commit SHA
- Update durable checkpoint — Persist the
git_commit_shaassociated with the run-branch commit
RunNotice warning event. Resume still uses the durable checkpoint in the run store.
Inspecting run history
Because checkpoints are plain Git commits, you can inspect them with standard Git tools:Rewinding to an earlier checkpoint
If a later stage goes off-track, you can rewind a terminal run to an earlier checkpoint and resume from there instead of restarting the entire workflow. Rewind creates a replacement run at the target checkpoint, archives the source run, and prints the new run ID to resume:succeeded, failed, or dead). If the source is archived, unarchive it first. If archive fails after the replacement run is created, do not retry fabro rewind; archive the source run manually.
See fabro rewind for the full command reference.
Forking a run
If you want to explore an alternate path from a checkpoint without archiving the original run, usefabro fork instead of fabro rewind. Fork creates a new independent run branching from the target checkpoint; the original run stays intact.
fabro rewind --list, fabro fork --list, fabro rewind, and fabro fork are server-backed. The server must be able to read the run’s recorded working_directory; otherwise these commands fail. Timeline listing reads the metadata branch as-is and does not rebuild a missing metadata branch, so a missing branch appears as an empty timeline.
See fabro fork for the full command reference.
When checkpointing is active
Git checkpointing activates automatically when:- The working directory is a clean Git repository (local and Docker sandboxes)
- The sandbox is Daytona (metadata branch on the host, commits inside the sandbox)
- The working directory has uncommitted changes
- The working directory is not a Git repository
- The run uses
--dry-run