Response capture
After an agent or prompt node completes, Fabro captures the full response text and persists it tostages/{rank:03}-{node_id}@{visit}/response.md in metadata snapshots and fabro dump output. It also writes the final outcome (status, context updates, routing directives) to stages/{rank:03}-{node_id}@{visit}/status.json.
Context updates
Every agent and prompt node sets three context keys from its response:
These keys are available to downstream nodes via the context. The
last_response key provides a quick preview, while response.{node_id} preserves the complete output for nodes that need it.
Routing directives
Agent and prompt nodes can influence which edge is taken after they complete by including a JSON object with routing fields in their response. Fabro scans the LLM output for the last JSON object containing any recognized routing field:How extraction works
Fabro finds all balanced{...} JSON objects in the response text, parses each one, and uses the last object that contains at least one recognized field (preferred_next_label, outcome, failure_reason, suggested_next_ids, context_updates). The JSON can appear anywhere in the response — inside a fenced code block, inline with natural language, or at the end.
Validated routing output
Setoutput_schema="routing" on an agent, prompt, or command node to require Fabro’s built-in routing directive schema:
output_schema="routing", the routing JSON must be an object with at least one recognized routing field (preferred_next_label, outcome, failure_reason, suggested_next_ids, or context_updates) and those fields must have the expected types. Malformed routing JSON fails validation instead of being silently ignored.
Validated routing uses the same reverse scan as normal routing extraction: Fabro validates the last parsable JSON object that contains a recognized routing field. If a routing object is present but malformed or has invalid field types, Fabro repairs that response instead of falling through to a file fallback.
Fabro repairs invalid structured output inside the same LLM context before failing an agent or prompt node. For prompt nodes, Fabro appends the invalid assistant response and a corrective user message to the same message list. For agent nodes using the API backend, Fabro sends the corrective message to the same live agent session. output_retries controls these repair turns and defaults to 2; output_retries=0 validates once and fails without a repair turn. Negative values are treated as 0. These repair turns are separate from workflow max_retries and do not consume node retry attempts.
Command nodes instead validate only after an exit-code-0 script, applying the same reverse scan to merged stdout and stderr. Print the intended JSON object last. Invalid output fails deterministically without a repair turn, command retry, or status.json fallback; output_retries, retry_policy, and max_retries do not retry the validation failure. Nonzero exits keep their normal command failure behavior without schema validation.
Routing fallback sources
Agent nodes can provide routing directives through fallback files. Fabro checks sources in this order:
This fallback chain applies to normal routing extraction and to
output_schema="routing". For validated routing, Fabro only advances to the next source when the current source has no JSON object or no object with recognized routing fields. If the current source contains malformed routing JSON or valid JSON with wrong routing field types, validation fails and Fabro starts the repair loop instead.
The last-file fallback only reads .json and .md files (case-insensitive), and the routing JSON must be the final JSON object in the file, with only whitespace after it. Fabro ignores other file types and routing JSON followed by any other content. These restrictions do not apply to the dedicated status.json fallback.
Prompt nodes do not use file fallbacks; they validate or extract routing directives from the response text only. Command nodes likewise have no file fallback and validate only their merged stdout and stderr.
If no source provides routing directives, the transition falls through to condition matching, unconditional edges, or weight-based tiebreaking as described in Transitions.
Instructing the agent
Fabro does not automatically instruct agents to emit routing JSON. You must include instructions in your prompt:Custom structured outputs
Agent, prompt, and command nodes can also validate their final JSON object against a JSON Schema file:output_schema="@path/to/schema.json" uses the same workflow file-reference rules as prompt files: the schema is loaded relative to the workflow file and inlined before execution. The final JSON object in the LLM response, or in a successful command’s merged stdout and stderr, is validated with jsonschema.
Custom schema validation only reads response or command output text. It does not fall back to status.json or the last file touched by the agent.
When custom schema validation succeeds, Fabro stores the parsed JSON value in context at:
For example, node
audit writes its parsed custom output to output.audit. Fabro still stores raw LLM response text at response.audit; command output remains available through command.output.
If custom schema validation fails for an agent or prompt node, Fabro sends concise validation feedback to the same prompt conversation or agent session and asks for corrected JSON. After output_retries repair turns are exhausted, the node fails terminally with output schema validation failed after N repair attempt(s). A command node instead fails deterministically after its first validation, without a repair turn or command retry.
Structured output validation applies to agent, prompt, and command nodes.
backend="acp" does not support output_schema in this release. Custom schemas update output.{node_id}; routing schemas update routing fields and merge context_updates as flat context keys that edge conditions can read. Edge conditions cannot traverse the custom output.{node_id} object.Output logging
Fabro writes several files per stage tostages/{rank:03}-{node_id}@{visit}/ in metadata snapshots and fabro dump output:
These files are written for every agent and prompt node execution, including retries. Use them for debugging unexpected agent behavior or verifying that routing directives were extracted correctly.
File tracking
Fabro records which files each stage touches. When an agent callswrite_file or edit_file, Fabro tracks the file path. When using a CLI-based agent backend, Fabro snapshots the Git working tree before and after execution and diffs the results.
The tracked paths are stored as files_touched on the stage outcome:
Where files_touched appears
How tracking works
For the API backend, Fabro subscribes to agent session events. When aToolCallStarted event fires for write_file or edit_file, Fabro records the file_path argument as pending. When the corresponding ToolCallCompleted arrives without an error, the path is confirmed as touched. Failed tool calls are discarded.
For the CLI and ACP backends, Fabro takes a different approach: it runs git diff --name-only and git ls-files --others --exclude-standard before and after the external agent session, then computes the difference. Any files that appear in the “after” snapshot but not “before” are recorded as touched.
Artifact offloading
When a stage produces a large context value — an LLM response or any context update — Fabro automatically offloads it into a global content-addressed blob store instead of leaving the full value inline in durable context. Command output is streamed to a stage log file while the command runs, then finalized into a durable blob ref after completion.How offloading works
After each node completes, Fabro checks every context update. If the serialized JSON of a value exceeds 100KB, it is stored once by SHA-256 hash and replaced with a durable blob ref. Command output is always stored this way after completion, even when it is small or empty:blob:// refs, not host-specific file paths.
Preamble rendering
When Fabro builds a preamble for a downstream stage, it first materializes any blob refs into execution-local files and then renders a reference instead of inlining the full content:Git storage
Large offloaded context values are not stored on the Git metadata branch. The metadata branch keeps checkpoint JSON and stage metadata; blob payloads live in the durable blob store and are referenced byblob://sha256/....
Captured stage artifacts such as screenshots, videos, reports, and traces still use the artifact store and metadata export paths described below.
Remote sandbox syncing
For remote sandboxes (Docker, Daytona), execution-time file access happens inside the sandbox filesystem.- Blob refs are materialized into
{working_directory}/.fabro/blobs/{blob_id}.json - Explicit non-blob
file://refs keep the existing copy-on-demand behavior and are copied into{working_directory}/.fabro/artifacts/{filename}when needed
file:// pointers during execution.
For local sandboxes, syncing is a no-op since the agent can already access the host filesystem directly.
Automatic asset capture
When[run.artifacts] contains include patterns, Fabro scans the sandbox after each stage and captures matching files. Artifact collection is opt-in; no scan runs when the include list is empty.
How asset capture works
- Fabro compiles and validates the configured workspace-relative globs.
- The sandbox provider enumerates regular files and their sizes without recursing through symlinks below the workspace root.
- Fabro applies the globs to normalized relative paths, enforces its collection limits, and downloads the selected files.
What gets captured
Configure the paths your workflow produces:run.toml
* and ? stay within one path segment, while ** crosses directories. See [run.artifacts] for the complete matching contract.
Fabro prunes dependency, cache, and build directories including .git, node_modules, target, .venv, .cache, and dist.