rename docs-internal to files-internal

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Bryan Helmkamp 2026-03-12 14:50:27 -04:00
parent 48eb867ce2
commit 376b132ca9
73 changed files with 1843 additions and 37 deletions

View file

@ -73,8 +73,8 @@ Fabro is an AI-powered workflow orchestration platform. Workflows are defined as
When working on Rust crates, read the relevant strategy doc **before** making changes:
- **`docs-internal/logging-strategy.md`** — read when adding `tracing` calls (`info!`, `debug!`, `warn!`, `error!`), working on error handling paths, or adding new operations that should be observable
- **`docs-internal/events-strategy.md`** — read when adding or modifying `WorkflowRunEvent` variants, touching `EventEmitter`/`emit()`, changing `progress.jsonl` output, or adding new workflow stage types
- **`files-internal/logging-strategy.md`** — read when adding `tracing` calls (`info!`, `debug!`, `warn!`, `error!`), working on error handling paths, or adding new operations that should be observable
- **`files-internal/events-strategy.md`** — read when adding or modifying `WorkflowRunEvent` variants, touching `EventEmitter`/`emit()`, changing `progress.jsonl` output, or adding new workflow stage types
## Shell quoting in sandbox code

View file

@ -13,7 +13,7 @@ The factory operates on three layers:
The first two layers are markdown docs. The third is generated from them.
```
docs-internal/
files-internal/
product/ Layer 1: WHAT to build
business-problem.md
personas.md

View file

@ -1,9 +1,9 @@
Read the feature requirements doc matching $goal under docs-internal/product/features/.
Read the foundation blueprints under docs-internal/architecture/foundation-blueprints/ (backend.md, data-layer.md, frontend.md).
Read all existing feature blueprints under docs-internal/architecture/features/.
Read the feature requirements doc matching $goal under files-internal/product/features/.
Read the foundation blueprints under files-internal/architecture/foundation-blueprints/ (backend.md, data-layer.md, frontend.md).
Read all existing feature blueprints under files-internal/architecture/features/.
Read the current codebase structure.
Write the feature blueprint at docs-internal/architecture/features/ with:
Write the feature blueprint at files-internal/architecture/features/ with:
## Solution Design

View file

@ -1,4 +1,4 @@
Read the feature blueprint just written and the current system diagrams under docs-internal/architecture/system-diagrams/.
Read the feature blueprint just written and the current system diagrams under files-internal/architecture/system-diagrams/.
Update only the diagrams that need changes:
- **entity-relationship-diagram.md**: Add new entities and relationships from the data model section

View file

@ -1,8 +1,8 @@
Execute the approved change manifest:
- For new features: create the product feature doc and architecture feature blueprint following the standard templates under docs-internal/product/features/ and docs-internal/architecture/features/
- For new features: create the product feature doc and architecture feature blueprint following the standard templates under files-internal/product/features/ and files-internal/architecture/features/
- For modified features: update the existing product and architecture docs with the specified changes
- For removed features: delete the product and architecture docs, note affected code for cleanup
- For diagram changes: update the system diagrams under docs-internal/architecture/system-diagrams/
- For diagram changes: update the system diagrams under files-internal/architecture/system-diagrams/
Ensure all docs remain internally consistent after changes.

View file

@ -1,4 +1,4 @@
Read all product docs under docs-internal/product/, all architecture docs under docs-internal/architecture/, and the current codebase.
Read all product docs under files-internal/product/, all architecture docs under files-internal/architecture/, and the current codebase.
The requested change: $goal

View file

@ -1,6 +1,6 @@
Read the feature blueprint matching $goal under docs-internal/architecture/features/.
Read the foundation blueprints under docs-internal/architecture/foundation-blueprints/.
Read the acceptance criteria from the matching doc under docs-internal/product/features/.
Read the feature blueprint matching $goal under files-internal/architecture/features/.
Read the foundation blueprints under files-internal/architecture/foundation-blueprints/.
Read the acceptance criteria from the matching doc under files-internal/product/features/.
Read the relevant existing source code.
Decomposition strategy: $context.human.gate.label

View file

@ -1,6 +1,6 @@
Review the implementation with fresh eyes. You did not write this code.
Read the feature blueprint matching $goal under docs-internal/architecture/features/.
Read the feature blueprint matching $goal under files-internal/architecture/features/.
Read the implementation files created or modified by the previous step.
Evaluate the code for:

View file

@ -1,4 +1,4 @@
Read the acceptance criteria from the feature requirements doc matching $goal under docs-internal/product/features/.
Read the acceptance criteria from the feature requirements doc matching $goal under files-internal/product/features/.
For each acceptance criterion (AC-NNN-XXX.N):
- Verify the implementation satisfies "When [condition], the system shall [behavior]"

View file

@ -1,4 +1,4 @@
Using the ingested artifacts, write the following product documents under docs-internal/product/:
Using the ingested artifacts, write the following product documents under files-internal/product/:
1. **business-problem.md** — The problem this product solves, who it affects, why existing solutions fall short
2. **personas.md** — Target user types with goals, pain points, and usage patterns

View file

@ -1,4 +1,4 @@
Read all product context docs under docs-internal/product/ and all existing feature docs under docs-internal/product/features/.
Read all product context docs under files-internal/product/ and all existing feature docs under files-internal/product/features/.
The feature to specify: $goal

View file

@ -1,4 +1,4 @@
Write the feature requirements document at docs-internal/product/features/ following the template structure:
Write the feature requirements document at files-internal/product/features/ following the template structure:
## Overview

View file

@ -1,7 +1,7 @@
Read all three layers and compare for alignment:
1. **Product docs:** docs-internal/product/ (all feature requirements and acceptance criteria)
2. **Architecture docs:** docs-internal/architecture/ (foundation blueprints, feature blueprints, system diagrams)
1. **Product docs:** files-internal/product/ (all feature requirements and acceptance criteria)
2. **Architecture docs:** files-internal/architecture/ (foundation blueprints, feature blueprints, system diagrams)
3. **Source code:** the actual implementation
For each feature, check:

View file

@ -79,8 +79,8 @@ Model stylesheets set per-node models inside the workflow graph, but you can als
Pass `--model` and optionally `--provider` to `fabro run`:
```bash
fabro run docs-internal/demo/01-hello.dot --model claude-opus-4-6
fabro run docs-internal/demo/04-pipeline.dot --model gemini-3.1-pro-preview --provider gemini
fabro run files-internal/demo/01-hello.dot --model claude-opus-4-6
fabro run files-internal/demo/04-pipeline.dot --model gemini-3.1-pro-preview --provider gemini
```
These flags set the default model for all nodes that don't have an explicit model assigned via a stylesheet.

View file

@ -312,7 +312,7 @@ gate -> slow_path
Instead of inlining long prompts, reference an external file:
```dot
simplify [label="Simplify", prompt="@docs-internal/prompts/simplify.md"]
simplify [label="Simplify", prompt="@files-internal/prompts/simplify.md"]
```
The `@` prefix tells Fabro to load the prompt from a file path relative to the workflow file. Paths support `~` (home directory) and `..` (parent directory):

View file

@ -31,7 +31,7 @@ digraph BranchLoop {
```
```bash
fabro run docs-internal/demo/05-branch-loop.dot
fabro run files-internal/demo/05-branch-loop.dot
```
## Command nodes

View file

@ -52,7 +52,7 @@ digraph Ensemble {
```
```bash
fabro run docs-internal/demo/11-ensemble.dot
fabro run files-internal/demo/11-ensemble.dot
```
<Note>

View file

@ -34,7 +34,7 @@ digraph Hello {
Run it:
```bash
fabro run docs-internal/demo/01-hello.dot
fabro run files-internal/demo/01-hello.dot
```
### What's happening
@ -68,7 +68,7 @@ digraph ToolUse {
```
```bash
fabro run docs-internal/demo/02-tool-use.dot
fabro run files-internal/demo/02-tool-use.dot
```
### What's happening
@ -108,7 +108,7 @@ digraph SubAgent {
```
```bash
fabro run docs-internal/demo/03-subagent.dot
fabro run files-internal/demo/03-subagent.dot
```
### What's happening

View file

@ -36,7 +36,7 @@ digraph MultiModel {
```
```bash
fabro run docs-internal/demo/08-multi-model.dot
fabro run files-internal/demo/08-multi-model.dot
```
## Model stylesheets

View file

@ -40,7 +40,7 @@ digraph Parallel {
```
```bash
fabro run docs-internal/demo/06-parallel.dot
fabro run files-internal/demo/06-parallel.dot
```
## Fan-out with the fork node

View file

@ -22,7 +22,7 @@ digraph PlanImplement {
plan [label="Plan", prompt="Analyze the goal and codebase. Write a clear, step-by-step implementation plan to a Markdown file called plan.md. Include what files will change and why.", reasoning_effort="high"]
approve [shape=hexagon, label="Approve Plan"]
implement [label="Implement", prompt="Read plan.md and implement every step. Make all the code changes described in the plan."]
simplify [label="Simplify", prompt="@docs-internal/prompts/simplify.md"]
simplify [label="Simplify", prompt="@files-internal/prompts/simplify.md"]
start -> plan -> approve
approve -> implement [label="[A] Approve"]
@ -32,7 +32,7 @@ digraph PlanImplement {
```
```bash
fabro run docs-internal/demo/10-plan-implement.dot
fabro run files-internal/demo/10-plan-implement.dot
```
## Human gates
@ -77,10 +77,10 @@ The `plan` node sets `reasoning_effort="high"`. This tells the model to think ha
## Prompt file references
The `simplify` node uses `@docs-internal/prompts/simplify.md` instead of an inline prompt string:
The `simplify` node uses `@files-internal/prompts/simplify.md` instead of an inline prompt string:
```dot
simplify [label="Simplify", prompt="@docs-internal/prompts/simplify.md"]
simplify [label="Simplify", prompt="@files-internal/prompts/simplify.md"]
```
The `@` prefix tells Fabro to load the prompt from a Markdown file, resolved relative to the DOT file's location. This keeps DOT files concise and lets you version prompts as standalone files. See [Prompts](/agents/prompts) for details.

View file

@ -62,7 +62,7 @@ digraph SubWorkflow {
```
```bash
fabro run docs-internal/demo/12-sub-workflow.dot
fabro run files-internal/demo/12-sub-workflow.dot
```
## The house node

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 99 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 108 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 45 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 50 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 395 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 179 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 395 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View file

@ -0,0 +1,2 @@
<?xml version='1.0' encoding='utf-8'?>
<svg xmlns="http://www.w3.org/2000/svg" width="640" height="656" viewBox="0 0 640 656"><rect width="640" height="656" fill="#ffffff" /><text x="30" y="80" font-family="system-ui, Helvetica, sans-serif" font-size="13" font-weight="600" fill="#364152">Primary</text><rect x="170" y="30" width="140" height="90" rx="6" fill="#b5ddef" stroke="#d0d8e3" stroke-width="1" /><text x="170" y="136" font-family="system-ui, Helvetica, sans-serif" font-size="11" font-weight="500" fill="#1a1f2e">Teal 300</text><text x="170" y="152" font-family="ui-monospace, monospace" font-size="10" fill="#7b8da2">#B5DDEF</text><rect x="320" y="30" width="140" height="90" rx="6" fill="#67b2d7" /><text x="320" y="136" font-family="system-ui, Helvetica, sans-serif" font-size="11" font-weight="500" fill="#1a1f2e">Teal 500</text><text x="320" y="152" font-family="ui-monospace, monospace" font-size="10" fill="#7b8da2">#67B2D7</text><rect x="470" y="30" width="140" height="90" rx="6" fill="#357f9e" /><text x="470" y="136" font-family="system-ui, Helvetica, sans-serif" font-size="11" font-weight="500" fill="#1a1f2e">Teal 700</text><text x="470" y="152" font-family="ui-monospace, monospace" font-size="10" fill="#7b8da2">#357F9E</text><text x="30" y="234" font-family="system-ui, Helvetica, sans-serif" font-size="13" font-weight="600" fill="#364152">Neutrals Dark</text><rect x="170" y="184" width="140" height="90" rx="6" fill="#0f1729" /><text x="170" y="290" font-family="system-ui, Helvetica, sans-serif" font-size="11" font-weight="500" fill="#1a1f2e">Navy 950</text><text x="170" y="306" font-family="ui-monospace, monospace" font-size="10" fill="#7b8da2">#0F1729</text><rect x="320" y="184" width="140" height="90" rx="6" fill="#252c3d" /><text x="320" y="290" font-family="system-ui, Helvetica, sans-serif" font-size="11" font-weight="500" fill="#1a1f2e">Navy 800</text><text x="320" y="306" font-family="ui-monospace, monospace" font-size="10" fill="#7b8da2">#252C3D</text><rect x="470" y="184" width="140" height="90" rx="6" fill="#4b5768" /><text x="470" y="290" font-family="system-ui, Helvetica, sans-serif" font-size="11" font-weight="500" fill="#1a1f2e">Navy 600</text><text x="470" y="306" font-family="ui-monospace, monospace" font-size="10" fill="#7b8da2">#4B5768</text><text x="30" y="388" font-family="system-ui, Helvetica, sans-serif" font-size="13" font-weight="600" fill="#364152">Neutrals Light</text><rect x="170" y="338" width="140" height="90" rx="6" fill="#f7f9fb" stroke="#d0d8e3" stroke-width="1" /><text x="170" y="444" font-family="system-ui, Helvetica, sans-serif" font-size="11" font-weight="500" fill="#1a1f2e">Ice 50</text><text x="170" y="460" font-family="ui-monospace, monospace" font-size="10" fill="#7b8da2">#F7F9FB</text><rect x="320" y="338" width="140" height="90" rx="6" fill="#e8edf3" stroke="#d0d8e3" stroke-width="1" /><text x="320" y="444" font-family="system-ui, Helvetica, sans-serif" font-size="11" font-weight="500" fill="#1a1f2e">Ice 100</text><text x="320" y="460" font-family="ui-monospace, monospace" font-size="10" fill="#7b8da2">#E8EDF3</text><rect x="470" y="338" width="140" height="90" rx="6" fill="#a8b5c5" /><text x="470" y="444" font-family="system-ui, Helvetica, sans-serif" font-size="11" font-weight="500" fill="#1a1f2e">Ice 300</text><text x="470" y="460" font-family="ui-monospace, monospace" font-size="10" fill="#7b8da2">#A8B5C5</text><text x="30" y="542" font-family="system-ui, Helvetica, sans-serif" font-size="13" font-weight="600" fill="#364152">Accent</text><rect x="170" y="492" width="140" height="90" rx="6" fill="#5ac8a8" /><text x="170" y="598" font-family="system-ui, Helvetica, sans-serif" font-size="11" font-weight="500" fill="#1a1f2e">Mint</text><text x="170" y="614" font-family="ui-monospace, monospace" font-size="10" fill="#7b8da2">#5AC8A8</text><rect x="320" y="492" width="140" height="90" rx="6" fill="#f0a45b" /><text x="320" y="598" font-family="system-ui, Helvetica, sans-serif" font-size="11" font-weight="500" fill="#1a1f2e">Amber</text><text x="320" y="614" font-family="ui-monospace, monospace" font-size="10" fill="#7b8da2">#F0A45B</text><rect x="470" y="492" width="140" height="90" rx="6" fill="#e86b6b" /><text x="470" y="598" font-family="system-ui, Helvetica, sans-serif" font-size="11" font-weight="500" fill="#1a1f2e">Coral</text><text x="470" y="614" font-family="ui-monospace, monospace" font-size="10" fill="#7b8da2">#E86B6B</text></svg>

After

Width:  |  Height:  |  Size: 4.4 KiB

View file

@ -0,0 +1,11 @@
digraph Hello {
graph [goal="Write a haiku about software workflows"]
rankdir=LR
start [shape=Mdiamond, label="Start"]
exit [shape=Msquare, label="Exit"]
compose [label="Compose", prompt="Write a haiku (5-7-5 syllable) about software workflows. Output only the haiku, nothing else.", shape=tab, reasoning_effort="low"]
start -> compose -> exit
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

View file

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 14.1.2 (20260124.0452)
-->
<!-- Title: Hello Pages: 1 -->
<svg width="291pt" height="47pt"
viewBox="0.00 0.00 291.00 47.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 42.5)">
<title>Hello</title>
<polygon fill="white" stroke="none" points="-4,4 -4,-42.5 287.07,-42.5 287.07,4 -4,4"/>
<!-- start -->
<g id="node1" class="node">
<title>start</title>
<polygon fill="none" stroke="black" points="40.05,-37.25 0,-19.25 40.05,-1.25 80.1,-19.25 40.05,-37.25"/>
<polyline fill="none" stroke="black" points="10.95,-24.17 10.95,-14.33"/>
<polyline fill="none" stroke="black" points="29.11,-6.17 51,-6.17"/>
<polyline fill="none" stroke="black" points="69.16,-14.33 69.16,-24.17"/>
<polyline fill="none" stroke="black" points="51,-32.33 29.11,-32.33"/>
<text xml:space="preserve" text-anchor="middle" x="40.05" y="-14.2" font-family="Times,serif" font-size="14.00">Start</text>
</g>
<!-- compose -->
<g id="node3" class="node">
<title>compose</title>
<ellipse fill="none" stroke="black" cx="162.33" cy="-19.25" rx="46.23" ry="18"/>
<text xml:space="preserve" text-anchor="middle" x="162.33" y="-14.2" font-family="Times,serif" font-size="14.00">Compose</text>
</g>
<!-- start&#45;&gt;compose -->
<g id="edge1" class="edge">
<title>start&#45;&gt;compose</title>
<path fill="none" stroke="black" d="M81.1,-19.25C88.54,-19.25 96.44,-19.25 104.28,-19.25"/>
<polygon fill="black" stroke="black" points="104.12,-22.75 114.12,-19.25 104.12,-15.75 104.12,-22.75"/>
</g>
<!-- exit -->
<g id="node2" class="node">
<title>exit</title>
<polygon fill="none" stroke="black" points="283.07,-38.5 244.57,-38.5 244.57,0 283.07,0 283.07,-38.5"/>
<polyline fill="none" stroke="black" points="256.57,-38.5 244.57,-26.5"/>
<polyline fill="none" stroke="black" points="244.57,-12 256.57,0"/>
<polyline fill="none" stroke="black" points="271.07,0 283.07,-12"/>
<polyline fill="none" stroke="black" points="283.07,-26.5 271.07,-38.5"/>
<text xml:space="preserve" text-anchor="middle" x="263.82" y="-14.2" font-family="Times,serif" font-size="14.00">Exit</text>
</g>
<!-- compose&#45;&gt;exit -->
<g id="edge2" class="edge">
<title>compose&#45;&gt;exit</title>
<path fill="none" stroke="black" d="M209,-19.25C217.08,-19.25 225.31,-19.25 232.82,-19.25"/>
<polygon fill="black" stroke="black" points="232.75,-22.75 242.75,-19.25 232.75,-15.75 232.75,-22.75"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View file

@ -0,0 +1,11 @@
digraph ToolUse {
graph [goal="Explore the current directory using shell tools"]
rankdir=LR
start [shape=Mdiamond, label="Start"]
exit [shape=Msquare, label="Exit"]
explore [label="Explore", prompt="Use bash to list the files in the current directory, then read the first 5 lines of any README or CLAUDE.md file you find. Summarize what this project is about in 2-3 sentences."]
start -> explore -> exit
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

View file

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 14.1.2 (20260124.0452)
-->
<!-- Title: ToolUse Pages: 1 -->
<svg width="279pt" height="47pt"
viewBox="0.00 0.00 279.00 47.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 42.5)">
<title>ToolUse</title>
<polygon fill="white" stroke="none" points="-4,4 -4,-42.5 274.78,-42.5 274.78,4 -4,4"/>
<!-- start -->
<g id="node1" class="node">
<title>start</title>
<polygon fill="none" stroke="black" points="40.05,-37.25 0,-19.25 40.05,-1.25 80.1,-19.25 40.05,-37.25"/>
<polyline fill="none" stroke="black" points="10.95,-24.17 10.95,-14.33"/>
<polyline fill="none" stroke="black" points="29.11,-6.17 51,-6.17"/>
<polyline fill="none" stroke="black" points="69.16,-14.33 69.16,-24.17"/>
<polyline fill="none" stroke="black" points="51,-32.33 29.11,-32.33"/>
<text xml:space="preserve" text-anchor="middle" x="40.05" y="-14.2" font-family="Times,serif" font-size="14.00">Start</text>
</g>
<!-- explore -->
<g id="node3" class="node">
<title>explore</title>
<ellipse fill="none" stroke="black" cx="156.19" cy="-19.25" rx="40.09" ry="18"/>
<text xml:space="preserve" text-anchor="middle" x="156.19" y="-14.2" font-family="Times,serif" font-size="14.00">Explore</text>
</g>
<!-- start&#45;&gt;explore -->
<g id="edge1" class="edge">
<title>start&#45;&gt;explore</title>
<path fill="none" stroke="black" d="M81.29,-19.25C88.71,-19.25 96.54,-19.25 104.23,-19.25"/>
<polygon fill="black" stroke="black" points="104.18,-22.75 114.18,-19.25 104.18,-15.75 104.18,-22.75"/>
</g>
<!-- exit -->
<g id="node2" class="node">
<title>exit</title>
<polygon fill="none" stroke="black" points="270.78,-38.5 232.28,-38.5 232.28,0 270.78,0 270.78,-38.5"/>
<polyline fill="none" stroke="black" points="244.28,-38.5 232.28,-26.5"/>
<polyline fill="none" stroke="black" points="232.28,-12 244.28,0"/>
<polyline fill="none" stroke="black" points="258.78,0 270.78,-12"/>
<polyline fill="none" stroke="black" points="270.78,-26.5 258.78,-38.5"/>
<text xml:space="preserve" text-anchor="middle" x="251.53" y="-14.2" font-family="Times,serif" font-size="14.00">Exit</text>
</g>
<!-- explore&#45;&gt;exit -->
<g id="edge2" class="edge">
<title>explore&#45;&gt;exit</title>
<path fill="none" stroke="black" d="M196.78,-19.25C204.86,-19.25 213.24,-19.25 220.91,-19.25"/>
<polygon fill="black" stroke="black" points="220.71,-22.75 230.71,-19.25 220.71,-15.75 220.71,-22.75"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View file

@ -0,0 +1,11 @@
digraph SubAgent {
graph [goal="Research and summarize using a sub-agent"]
rankdir=LR
start [shape=Mdiamond, label="Start"]
exit [shape=Msquare, label="Exit"]
research [label="Research", prompt="You have a sub-agent available via the spawn_agent tool. Spawn a sub-agent to list the files in the current directory and read the first 10 lines of any README or CLAUDE.md. Then, using the sub-agent's findings, write a 2-sentence summary of the project."]
start -> research -> exit
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

View file

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 14.1.2 (20260124.0452)
-->
<!-- Title: SubAgent Pages: 1 -->
<svg width="288pt" height="47pt"
viewBox="0.00 0.00 288.00 47.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 42.5)">
<title>SubAgent</title>
<polygon fill="white" stroke="none" points="-4,4 -4,-42.5 284,-42.5 284,4 -4,4"/>
<!-- start -->
<g id="node1" class="node">
<title>start</title>
<polygon fill="none" stroke="black" points="40.05,-37.25 0,-19.25 40.05,-1.25 80.1,-19.25 40.05,-37.25"/>
<polyline fill="none" stroke="black" points="10.95,-24.17 10.95,-14.33"/>
<polyline fill="none" stroke="black" points="29.11,-6.17 51,-6.17"/>
<polyline fill="none" stroke="black" points="69.16,-14.33 69.16,-24.17"/>
<polyline fill="none" stroke="black" points="51,-32.33 29.11,-32.33"/>
<text xml:space="preserve" text-anchor="middle" x="40.05" y="-14.2" font-family="Times,serif" font-size="14.00">Start</text>
</g>
<!-- research -->
<g id="node3" class="node">
<title>research</title>
<ellipse fill="none" stroke="black" cx="160.8" cy="-19.25" rx="44.7" ry="18"/>
<text xml:space="preserve" text-anchor="middle" x="160.8" y="-14.2" font-family="Times,serif" font-size="14.00">Research</text>
</g>
<!-- start&#45;&gt;research -->
<g id="edge1" class="edge">
<title>start&#45;&gt;research</title>
<path fill="none" stroke="black" d="M81.25,-19.25C88.76,-19.25 96.72,-19.25 104.58,-19.25"/>
<polygon fill="black" stroke="black" points="104.46,-22.75 114.46,-19.25 104.46,-15.75 104.46,-22.75"/>
</g>
<!-- exit -->
<g id="node2" class="node">
<title>exit</title>
<polygon fill="none" stroke="black" points="280,-38.5 241.5,-38.5 241.5,0 280,0 280,-38.5"/>
<polyline fill="none" stroke="black" points="253.5,-38.5 241.5,-26.5"/>
<polyline fill="none" stroke="black" points="241.5,-12 253.5,0"/>
<polyline fill="none" stroke="black" points="268,0 280,-12"/>
<polyline fill="none" stroke="black" points="280,-26.5 268,-38.5"/>
<text xml:space="preserve" text-anchor="middle" x="260.75" y="-14.2" font-family="Times,serif" font-size="14.00">Exit</text>
</g>
<!-- research&#45;&gt;exit -->
<g id="edge2" class="edge">
<title>research&#45;&gt;exit</title>
<path fill="none" stroke="black" d="M205.9,-19.25C213.94,-19.25 222.15,-19.25 229.66,-19.25"/>
<polygon fill="black" stroke="black" points="229.6,-22.75 239.6,-19.25 229.6,-15.75 229.6,-22.75"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View file

@ -0,0 +1,13 @@
digraph Pipeline {
graph [goal="Analyze the current directory and suggest improvements"]
rankdir=LR
start [shape=Mdiamond, label="Start"]
exit [shape=Msquare, label="Exit"]
scan [label="Scan Files", shape=parallelogram, script="find . -maxdepth 2 -type f | head -30"]
analyze [label="Analyze", prompt="Review the file listing from the previous step. Identify what kind of project this is and summarize its structure in 3-4 bullet points.", shape=tab, reasoning_effort="low"]
suggest [label="Suggest", prompt="Based on the analysis, suggest 3 concrete improvements to the project structure. Be specific and actionable.", shape=tab, reasoning_effort="low"]
start -> scan -> analyze -> suggest -> exit
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View file

@ -0,0 +1,75 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 14.1.2 (20260124.0452)
-->
<!-- Title: Pipeline Pages: 1 -->
<svg width="579pt" height="47pt"
viewBox="0.00 0.00 579.00 47.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 42.5)">
<title>Pipeline</title>
<polygon fill="white" stroke="none" points="-4,4 -4,-42.5 574.62,-42.5 574.62,4 -4,4"/>
<!-- start -->
<g id="node1" class="node">
<title>start</title>
<polygon fill="none" stroke="black" points="40.05,-37.25 0,-19.25 40.05,-1.25 80.1,-19.25 40.05,-37.25"/>
<polyline fill="none" stroke="black" points="10.95,-24.17 10.95,-14.33"/>
<polyline fill="none" stroke="black" points="29.11,-6.17 51,-6.17"/>
<polyline fill="none" stroke="black" points="69.16,-14.33 69.16,-24.17"/>
<polyline fill="none" stroke="black" points="51,-32.33 29.11,-32.33"/>
<text xml:space="preserve" text-anchor="middle" x="40.05" y="-14.2" font-family="Times,serif" font-size="14.00">Start</text>
</g>
<!-- scan -->
<g id="node3" class="node">
<title>scan</title>
<polygon fill="none" stroke="black" points="260.68,-37.25 145.68,-37.25 116.1,-1.25 231.1,-1.25 260.68,-37.25"/>
<text xml:space="preserve" text-anchor="middle" x="188.39" y="-14.2" font-family="Times,serif" font-size="14.00">Scan Files</text>
</g>
<!-- start&#45;&gt;scan -->
<g id="edge1" class="edge">
<title>start&#45;&gt;scan</title>
<path fill="none" stroke="black" d="M81.04,-19.25C92.82,-19.25 106.06,-19.25 119.09,-19.25"/>
<polygon fill="black" stroke="black" points="118.93,-22.75 128.93,-19.25 118.93,-15.75 118.93,-22.75"/>
</g>
<!-- exit -->
<g id="node2" class="node">
<title>exit</title>
<polygon fill="none" stroke="black" points="570.62,-38.5 532.12,-38.5 532.12,0 570.62,0 570.62,-38.5"/>
<polyline fill="none" stroke="black" points="544.12,-38.5 532.12,-26.5"/>
<polyline fill="none" stroke="black" points="532.12,-12 544.12,0"/>
<polyline fill="none" stroke="black" points="558.62,0 570.62,-12"/>
<polyline fill="none" stroke="black" points="570.62,-26.5 558.62,-38.5"/>
<text xml:space="preserve" text-anchor="middle" x="551.37" y="-14.2" font-family="Times,serif" font-size="14.00">Exit</text>
</g>
<!-- analyze -->
<g id="node4" class="node">
<title>analyze</title>
<ellipse fill="none" stroke="black" cx="338.31" cy="-19.25" rx="41.63" ry="18"/>
<text xml:space="preserve" text-anchor="middle" x="338.31" y="-14.2" font-family="Times,serif" font-size="14.00">Analyze</text>
</g>
<!-- scan&#45;&gt;analyze -->
<g id="edge2" class="edge">
<title>scan&#45;&gt;analyze</title>
<path fill="none" stroke="black" d="M246.16,-19.25C259.01,-19.25 272.59,-19.25 285.23,-19.25"/>
<polygon fill="black" stroke="black" points="285.02,-22.75 295.02,-19.25 285.02,-15.75 285.02,-22.75"/>
</g>
<!-- suggest -->
<g id="node5" class="node">
<title>suggest</title>
<ellipse fill="none" stroke="black" cx="456.03" cy="-19.25" rx="40.09" ry="18"/>
<text xml:space="preserve" text-anchor="middle" x="456.03" y="-14.2" font-family="Times,serif" font-size="14.00">Suggest</text>
</g>
<!-- analyze&#45;&gt;suggest -->
<g id="edge3" class="edge">
<title>analyze&#45;&gt;suggest</title>
<path fill="none" stroke="black" d="M380.43,-19.25C388.11,-19.25 396.23,-19.25 404.16,-19.25"/>
<polygon fill="black" stroke="black" points="404.07,-22.75 414.07,-19.25 404.07,-15.75 404.07,-22.75"/>
</g>
<!-- suggest&#45;&gt;exit -->
<g id="edge4" class="edge">
<title>suggest&#45;&gt;exit</title>
<path fill="none" stroke="black" d="M496.61,-19.25C504.7,-19.25 513.07,-19.25 520.74,-19.25"/>
<polygon fill="black" stroke="black" points="520.54,-22.75 530.54,-19.25 520.54,-15.75 520.54,-22.75"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

View file

@ -0,0 +1,16 @@
digraph BranchLoop {
graph [goal="Create a Python script that passes its test suite"]
rankdir=LR
start [shape=Mdiamond, label="Start"]
exit [shape=Msquare, label="Exit"]
plan [label="Plan", prompt="Plan a small Python script (fizzbuzz.py) and a test file (test_fizzbuzz.py) using pytest. Describe what you will create.", shape=tab, reasoning_effort="low"]
implement [label="Implement", prompt="Create fizzbuzz.py and test_fizzbuzz.py as planned. Write the files to disk."]
validate [label="Validate", shape=parallelogram, script="python3 -m pytest test_fizzbuzz.py -v 2>&1 || true"]
gate [shape=diamond, label="Tests passing?"]
start -> plan -> implement -> validate -> gate
gate -> exit [label="Pass", condition="outcome=success"]
gate -> implement [label="Fix"]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View file

@ -0,0 +1,95 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 14.1.2 (20260124.0452)
-->
<!-- Title: BranchLoop Pages: 1 -->
<svg width="796pt" height="86pt"
viewBox="0.00 0.00 796.00 86.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 82.25)">
<title>BranchLoop</title>
<polygon fill="white" stroke="none" points="-4,4 -4,-82.25 792.37,-82.25 792.37,4 -4,4"/>
<!-- start -->
<g id="node1" class="node">
<title>start</title>
<polygon fill="none" stroke="black" points="40.05,-37.25 0,-19.25 40.05,-1.25 80.1,-19.25 40.05,-37.25"/>
<polyline fill="none" stroke="black" points="10.95,-24.17 10.95,-14.33"/>
<polyline fill="none" stroke="black" points="29.11,-6.17 51,-6.17"/>
<polyline fill="none" stroke="black" points="69.16,-14.33 69.16,-24.17"/>
<polyline fill="none" stroke="black" points="51,-32.33 29.11,-32.33"/>
<text xml:space="preserve" text-anchor="middle" x="40.05" y="-14.2" font-family="Times,serif" font-size="14.00">Start</text>
</g>
<!-- plan -->
<g id="node3" class="node">
<title>plan</title>
<ellipse fill="none" stroke="black" cx="144.4" cy="-19.25" rx="27.3" ry="18"/>
<text xml:space="preserve" text-anchor="middle" x="144.4" y="-14.2" font-family="Times,serif" font-size="14.00">Plan</text>
</g>
<!-- start&#45;&gt;plan -->
<g id="edge1" class="edge">
<title>start&#45;&gt;plan</title>
<path fill="none" stroke="black" d="M81.21,-19.25C89.26,-19.25 97.69,-19.25 105.66,-19.25"/>
<polygon fill="black" stroke="black" points="105.38,-22.75 115.38,-19.25 105.38,-15.75 105.38,-22.75"/>
</g>
<!-- exit -->
<g id="node2" class="node">
<title>exit</title>
<polygon fill="none" stroke="black" points="788.37,-38.5 749.87,-38.5 749.87,0 788.37,0 788.37,-38.5"/>
<polyline fill="none" stroke="black" points="761.87,-38.5 749.87,-26.5"/>
<polyline fill="none" stroke="black" points="749.87,-12 761.87,0"/>
<polyline fill="none" stroke="black" points="776.37,0 788.37,-12"/>
<polyline fill="none" stroke="black" points="788.37,-26.5 776.37,-38.5"/>
<text xml:space="preserve" text-anchor="middle" x="769.12" y="-14.2" font-family="Times,serif" font-size="14.00">Exit</text>
</g>
<!-- implement -->
<g id="node4" class="node">
<title>implement</title>
<ellipse fill="none" stroke="black" cx="260.56" cy="-19.25" rx="51.86" ry="18"/>
<text xml:space="preserve" text-anchor="middle" x="260.56" y="-14.2" font-family="Times,serif" font-size="14.00">Implement</text>
</g>
<!-- plan&#45;&gt;implement -->
<g id="edge2" class="edge">
<title>plan&#45;&gt;implement</title>
<path fill="none" stroke="black" d="M172.16,-19.25C179.68,-19.25 188.18,-19.25 196.86,-19.25"/>
<polygon fill="black" stroke="black" points="196.78,-22.75 206.78,-19.25 196.78,-15.75 196.78,-22.75"/>
</g>
<!-- validate -->
<g id="node5" class="node">
<title>validate</title>
<polygon fill="none" stroke="black" points="469.99,-78.25 374.09,-78.25 349.42,-42.25 445.32,-42.25 469.99,-78.25"/>
<text xml:space="preserve" text-anchor="middle" x="409.7" y="-55.2" font-family="Times,serif" font-size="14.00">Validate</text>
</g>
<!-- implement&#45;&gt;validate -->
<g id="edge3" class="edge">
<title>implement&#45;&gt;validate</title>
<path fill="none" stroke="black" d="M301.77,-30.45C313.4,-33.69 326.45,-37.33 339.32,-40.92"/>
<polygon fill="black" stroke="black" points="338.03,-44.19 348.6,-43.5 339.91,-37.45 338.03,-44.19"/>
</g>
<!-- gate -->
<g id="node6" class="node">
<title>gate</title>
<polygon fill="none" stroke="black" points="598.43,-37.25 506.99,-19.25 598.43,-1.25 689.87,-19.25 598.43,-37.25"/>
<text xml:space="preserve" text-anchor="middle" x="598.43" y="-14.2" font-family="Times,serif" font-size="14.00">Tests passing?</text>
</g>
<!-- validate&#45;&gt;gate -->
<g id="edge4" class="edge">
<title>validate&#45;&gt;gate</title>
<path fill="none" stroke="black" d="M451.68,-51.25C478.31,-45.4 513.35,-37.71 542.4,-31.33"/>
<polygon fill="black" stroke="black" points="543.12,-34.76 552.14,-29.19 541.62,-27.92 543.12,-34.76"/>
</g>
<!-- gate&#45;&gt;exit -->
<g id="edge5" class="edge">
<title>gate&#45;&gt;exit</title>
<path fill="none" stroke="black" d="M692.16,-19.25C708.93,-19.25 725.24,-19.25 738.43,-19.25"/>
<polygon fill="black" stroke="black" points="738.07,-22.75 748.07,-19.25 738.07,-15.75 738.07,-22.75"/>
<text xml:space="preserve" text-anchor="middle" x="719.87" y="-22.45" font-family="Times,serif" font-size="14.00">Pass</text>
</g>
<!-- gate&#45;&gt;implement -->
<g id="edge6" class="edge">
<title>gate&#45;&gt;implement</title>
<path fill="none" stroke="black" d="M514.14,-17.38C499.43,-17.12 484.26,-16.89 469.99,-16.75 416.4,-16.22 403,-15.99 349.42,-16.75 341.24,-16.87 332.63,-17.04 324.13,-17.25"/>
<polygon fill="black" stroke="black" points="324.08,-13.75 314.17,-17.51 324.26,-20.75 324.08,-13.75"/>
<text xml:space="preserve" text-anchor="middle" x="409.7" y="-19.95" font-family="Times,serif" font-size="14.00">Fix</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5 KiB

View file

@ -0,0 +1,25 @@
digraph Parallel {
graph [goal="Perform a multi-perspective code review"]
rankdir=LR
start [shape=Mdiamond, label="Start"]
exit [shape=Msquare, label="Exit"]
fork [label="Fork Analysis", shape=component, join_policy="wait_all", error_policy="continue"]
security [label="Security Audit", prompt="Examine the codebase for security concerns: hardcoded secrets, injection risks, unsafe dependencies. List findings as bullet points.", shape=tab, reasoning_effort="low"]
architecture [label="Architecture Review", prompt="Assess the codebase architecture: separation of concerns, dependency structure, modularity. List findings as bullet points.", shape=tab, reasoning_effort="low"]
quality [label="Code Quality", prompt="Check code quality: naming conventions, dead code, test coverage gaps, error handling. List findings as bullet points.", shape=tab, reasoning_effort="low"]
merge [label="Merge Findings", shape=tripleoctagon]
report [label="Final Report", prompt="Synthesize the security, architecture, and code quality findings into a prioritized summary report with top 5 action items.", shape=tab]
start -> fork
fork -> security
fork -> architecture
fork -> quality
security -> merge
architecture -> merge
quality -> merge
merge -> report -> exit
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

View file

@ -0,0 +1,127 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 14.1.2 (20260124.0452)
-->
<!-- Title: Parallel Pages: 1 -->
<svg width="857pt" height="152pt"
viewBox="0.00 0.00 857.00 152.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 148)">
<title>Parallel</title>
<polygon fill="white" stroke="none" points="-4,4 -4,-148 852.88,-148 852.88,4 -4,4"/>
<!-- start -->
<g id="node1" class="node">
<title>start</title>
<polygon fill="none" stroke="black" points="40.05,-90 0,-72 40.05,-54 80.1,-72 40.05,-90"/>
<polyline fill="none" stroke="black" points="10.95,-76.92 10.95,-67.08"/>
<polyline fill="none" stroke="black" points="29.11,-58.92 51,-58.92"/>
<polyline fill="none" stroke="black" points="69.16,-67.08 69.16,-76.92"/>
<polyline fill="none" stroke="black" points="51,-85.08 29.11,-85.08"/>
<text xml:space="preserve" text-anchor="middle" x="40.05" y="-66.95" font-family="Times,serif" font-size="14.00">Start</text>
</g>
<!-- fork -->
<g id="node3" class="node">
<title>fork</title>
<polygon fill="none" stroke="black" points="207.85,-90 116.1,-90 116.1,-86 112.1,-86 112.1,-82 116.1,-82 116.1,-62 112.1,-62 112.1,-58 116.1,-58 116.1,-54 207.85,-54 207.85,-90"/>
<polyline fill="none" stroke="black" points="116.1,-86 120.1,-86 120.1,-82 116.1,-82"/>
<polyline fill="none" stroke="black" points="116.1,-62 120.1,-62 120.1,-58 116.1,-58"/>
<text xml:space="preserve" text-anchor="middle" x="161.98" y="-66.95" font-family="Times,serif" font-size="14.00">Fork Analysis</text>
</g>
<!-- start&#45;&gt;fork -->
<g id="edge1" class="edge">
<title>start&#45;&gt;fork</title>
<path fill="none" stroke="black" d="M81.32,-72C88.72,-72 96.58,-72 104.36,-72"/>
<polygon fill="black" stroke="black" points="104.14,-75.5 114.14,-72 104.14,-68.5 104.14,-75.5"/>
</g>
<!-- exit -->
<g id="node2" class="node">
<title>exit</title>
<polygon fill="none" stroke="black" points="848.88,-91.25 810.38,-91.25 810.38,-52.75 848.88,-52.75 848.88,-91.25"/>
<polyline fill="none" stroke="black" points="822.38,-91.25 810.38,-79.25"/>
<polyline fill="none" stroke="black" points="810.38,-64.75 822.38,-52.75"/>
<polyline fill="none" stroke="black" points="836.88,-52.75 848.88,-64.75"/>
<polyline fill="none" stroke="black" points="848.88,-79.25 836.88,-91.25"/>
<text xml:space="preserve" text-anchor="middle" x="829.63" y="-66.95" font-family="Times,serif" font-size="14.00">Exit</text>
</g>
<!-- security -->
<g id="node4" class="node">
<title>security</title>
<ellipse fill="none" stroke="black" cx="331.54" cy="-126" rx="64.66" ry="18"/>
<text xml:space="preserve" text-anchor="middle" x="331.54" y="-120.95" font-family="Times,serif" font-size="14.00">Security Audit</text>
</g>
<!-- fork&#45;&gt;security -->
<g id="edge2" class="edge">
<title>fork&#45;&gt;security</title>
<path fill="none" stroke="black" d="M208.26,-87.3C219.85,-91.16 232.3,-95.27 243.85,-99 254.21,-102.34 265.28,-105.84 275.93,-109.18"/>
<polygon fill="black" stroke="black" points="274.8,-112.49 285.38,-112.12 276.88,-105.81 274.8,-112.49"/>
</g>
<!-- architecture -->
<g id="node5" class="node">
<title>architecture</title>
<ellipse fill="none" stroke="black" cx="331.54" cy="-72" rx="87.69" ry="18"/>
<text xml:space="preserve" text-anchor="middle" x="331.54" y="-66.95" font-family="Times,serif" font-size="14.00">Architecture Review</text>
</g>
<!-- fork&#45;&gt;architecture -->
<g id="edge3" class="edge">
<title>fork&#45;&gt;architecture</title>
<path fill="none" stroke="black" d="M208.32,-72C215.85,-72 223.91,-72 232.16,-72"/>
<polygon fill="black" stroke="black" points="231.99,-75.5 241.99,-72 231.99,-68.5 231.99,-75.5"/>
</g>
<!-- quality -->
<g id="node6" class="node">
<title>quality</title>
<ellipse fill="none" stroke="black" cx="331.54" cy="-18" rx="60.56" ry="18"/>
<text xml:space="preserve" text-anchor="middle" x="331.54" y="-12.95" font-family="Times,serif" font-size="14.00">Code Quality</text>
</g>
<!-- fork&#45;&gt;quality -->
<g id="edge4" class="edge">
<title>fork&#45;&gt;quality</title>
<path fill="none" stroke="black" d="M208.26,-56.7C219.85,-52.84 232.3,-48.73 243.85,-45 254.65,-41.52 266.24,-37.85 277.31,-34.39"/>
<polygon fill="black" stroke="black" points="278.21,-37.78 286.71,-31.46 276.12,-31.1 278.21,-37.78"/>
</g>
<!-- merge -->
<g id="node7" class="node">
<title>merge</title>
<polygon fill="none" stroke="black" points="615.39,-64.54 615.39,-79.46 570.82,-90 507.8,-90 463.23,-79.46 463.23,-64.54 507.8,-54 570.82,-54 615.39,-64.54"/>
<polygon fill="none" stroke="black" points="619.39,-61.38 619.39,-82.62 571.29,-94 507.33,-94 459.23,-82.62 459.23,-61.38 507.33,-50 571.29,-50 619.39,-61.38"/>
<polygon fill="none" stroke="black" points="623.39,-58.22 623.39,-85.78 571.76,-98 506.86,-98 455.23,-85.78 455.23,-58.22 506.86,-46 571.76,-46 623.39,-58.22"/>
<text xml:space="preserve" text-anchor="middle" x="539.31" y="-66.95" font-family="Times,serif" font-size="14.00">Merge Findings</text>
</g>
<!-- security&#45;&gt;merge -->
<g id="edge5" class="edge">
<title>security&#45;&gt;merge</title>
<path fill="none" stroke="black" d="M379.75,-113.61C403.42,-107.4 432.72,-99.71 459.67,-92.64"/>
<polygon fill="black" stroke="black" points="460.4,-96.06 469.19,-90.14 458.63,-89.29 460.4,-96.06"/>
</g>
<!-- architecture&#45;&gt;merge -->
<g id="edge6" class="edge">
<title>architecture&#45;&gt;merge</title>
<path fill="none" stroke="black" d="M419.51,-72C427.48,-72 435.59,-72 443.63,-72"/>
<polygon fill="black" stroke="black" points="443.42,-75.5 453.42,-72 443.42,-68.5 443.42,-75.5"/>
</g>
<!-- quality&#45;&gt;merge -->
<g id="edge7" class="edge">
<title>quality&#45;&gt;merge</title>
<path fill="none" stroke="black" d="M377.95,-29.92C401.88,-36.2 431.91,-44.08 459.5,-51.32"/>
<polygon fill="black" stroke="black" points="458.31,-54.62 468.87,-53.78 460.09,-47.85 458.31,-54.62"/>
</g>
<!-- report -->
<g id="node8" class="node">
<title>report</title>
<ellipse fill="none" stroke="black" cx="716.88" cy="-72" rx="57.49" ry="18"/>
<text xml:space="preserve" text-anchor="middle" x="716.88" y="-66.95" font-family="Times,serif" font-size="14.00">Final Report</text>
</g>
<!-- merge&#45;&gt;report -->
<g id="edge8" class="edge">
<title>merge&#45;&gt;report</title>
<path fill="none" stroke="black" d="M623.73,-72C631.75,-72 639.83,-72 647.68,-72"/>
<polygon fill="black" stroke="black" points="647.48,-75.5 657.48,-72 647.48,-68.5 647.48,-75.5"/>
</g>
<!-- report&#45;&gt;exit -->
<g id="edge9" class="edge">
<title>report&#45;&gt;exit</title>
<path fill="none" stroke="black" d="M774.55,-72C782.97,-72 791.35,-72 798.91,-72"/>
<polygon fill="black" stroke="black" points="798.85,-75.5 808.85,-72 798.85,-68.5 798.85,-75.5"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.7 KiB

View file

@ -0,0 +1,18 @@
digraph HumanGate {
graph [goal="Propose and implement a README improvement"]
rankdir=LR
start [shape=Mdiamond, label="Start"]
exit [shape=Msquare, label="Exit"]
draft [label="Draft Proposal", prompt="Read the README.md (or note its absence). Propose a specific improvement: either create one or enhance the existing one. Describe your proposed changes clearly but do NOT make any changes yet.", shape=tab]
approve [label="Approve Changes?", shape=hexagon]
apply [label="Apply Changes", prompt="Apply the proposed README changes that were approved."]
skip [label="Skip", prompt="Acknowledged. No changes made.", shape=tab, reasoning_effort="low"]
start -> draft -> approve
approve -> apply [label="[A] Approve"]
approve -> skip [label="[S] Skip"]
apply -> exit
skip -> exit
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

View file

@ -0,0 +1,95 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 14.1.2 (20260124.0452)
-->
<!-- Title: HumanGate Pages: 1 -->
<svg width="798pt" height="98pt"
viewBox="0.00 0.00 798.00 98.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 94)">
<title>HumanGate</title>
<polygon fill="white" stroke="none" points="-4,4 -4,-94 793.75,-94 793.75,4 -4,4"/>
<!-- start -->
<g id="node1" class="node">
<title>start</title>
<polygon fill="none" stroke="black" points="40.05,-63 0,-45 40.05,-27 80.1,-45 40.05,-63"/>
<polyline fill="none" stroke="black" points="10.95,-49.92 10.95,-40.08"/>
<polyline fill="none" stroke="black" points="29.11,-31.92 51,-31.92"/>
<polyline fill="none" stroke="black" points="69.16,-40.08 69.16,-49.92"/>
<polyline fill="none" stroke="black" points="51,-58.08 29.11,-58.08"/>
<text xml:space="preserve" text-anchor="middle" x="40.05" y="-39.95" font-family="Times,serif" font-size="14.00">Start</text>
</g>
<!-- draft -->
<g id="node3" class="node">
<title>draft</title>
<ellipse fill="none" stroke="black" cx="182.27" cy="-45" rx="65.17" ry="18"/>
<text xml:space="preserve" text-anchor="middle" x="182.27" y="-39.95" font-family="Times,serif" font-size="14.00">Draft Proposal</text>
</g>
<!-- start&#45;&gt;draft -->
<g id="edge1" class="edge">
<title>start&#45;&gt;draft</title>
<path fill="none" stroke="black" d="M81.23,-45C88.85,-45 97.06,-45 105.37,-45"/>
<polygon fill="black" stroke="black" points="105.22,-48.5 115.22,-45 105.22,-41.5 105.22,-48.5"/>
</g>
<!-- exit -->
<g id="node2" class="node">
<title>exit</title>
<polygon fill="none" stroke="black" points="789.75,-63.25 751.25,-63.25 751.25,-24.75 789.75,-24.75 789.75,-63.25"/>
<polyline fill="none" stroke="black" points="763.25,-63.25 751.25,-51.25"/>
<polyline fill="none" stroke="black" points="751.25,-36.75 763.25,-24.75"/>
<polyline fill="none" stroke="black" points="777.75,-24.75 789.75,-36.75"/>
<polyline fill="none" stroke="black" points="789.75,-51.25 777.75,-63.25"/>
<text xml:space="preserve" text-anchor="middle" x="770.5" y="-38.95" font-family="Times,serif" font-size="14.00">Exit</text>
</g>
<!-- approve -->
<g id="node4" class="node">
<title>approve</title>
<polygon fill="none" stroke="black" points="472.77,-45 425.69,-63 331.52,-63 284.44,-45 331.52,-27 425.69,-27 472.77,-45"/>
<text xml:space="preserve" text-anchor="middle" x="378.61" y="-39.95" font-family="Times,serif" font-size="14.00">Approve Changes?</text>
</g>
<!-- draft&#45;&gt;approve -->
<g id="edge2" class="edge">
<title>draft&#45;&gt;approve</title>
<path fill="none" stroke="black" d="M247.71,-45C255.54,-45 263.68,-45 271.91,-45"/>
<polygon fill="black" stroke="black" points="271.64,-48.5 281.64,-45 271.64,-41.5 271.64,-48.5"/>
</g>
<!-- apply -->
<g id="node5" class="node">
<title>apply</title>
<ellipse fill="none" stroke="black" cx="646.01" cy="-72" rx="68.24" ry="18"/>
<text xml:space="preserve" text-anchor="middle" x="646.01" y="-66.95" font-family="Times,serif" font-size="14.00">Apply Changes</text>
</g>
<!-- approve&#45;&gt;apply -->
<g id="edge3" class="edge">
<title>approve&#45;&gt;apply</title>
<path fill="none" stroke="black" d="M454.24,-52.59C490.65,-56.3 534.31,-60.74 570.68,-64.44"/>
<polygon fill="black" stroke="black" points="569.9,-67.88 580.2,-65.41 570.61,-60.91 569.9,-67.88"/>
<text xml:space="preserve" text-anchor="middle" x="525.27" y="-66.47" font-family="Times,serif" font-size="14.00">[A] Approve</text>
</g>
<!-- skip -->
<g id="node6" class="node">
<title>skip</title>
<ellipse fill="none" stroke="black" cx="646.01" cy="-18" rx="27.81" ry="18"/>
<text xml:space="preserve" text-anchor="middle" x="646.01" y="-12.95" font-family="Times,serif" font-size="14.00">Skip</text>
</g>
<!-- approve&#45;&gt;skip -->
<g id="edge4" class="edge">
<title>approve&#45;&gt;skip</title>
<path fill="none" stroke="black" d="M454.24,-37.41C503.95,-32.35 567.2,-25.92 606.91,-21.88"/>
<polygon fill="black" stroke="black" points="607.09,-25.38 616.69,-20.88 606.39,-18.41 607.09,-25.38"/>
<text xml:space="preserve" text-anchor="middle" x="525.27" y="-36.32" font-family="Times,serif" font-size="14.00">[S] Skip</text>
</g>
<!-- apply&#45;&gt;exit -->
<g id="edge5" class="edge">
<title>apply&#45;&gt;exit</title>
<path fill="none" stroke="black" d="M698.6,-60.21C712.67,-56.99 727.44,-53.61 739.84,-50.78"/>
<polygon fill="black" stroke="black" points="740.45,-54.23 749.42,-48.59 738.89,-47.41 740.45,-54.23"/>
</g>
<!-- skip&#45;&gt;exit -->
<g id="edge6" class="edge">
<title>skip&#45;&gt;exit</title>
<path fill="none" stroke="black" d="M672.99,-23.51C692.59,-27.68 719.52,-33.39 739.95,-37.73"/>
<polygon fill="black" stroke="black" points="739.13,-41.13 749.64,-39.78 740.59,-34.28 739.13,-41.13"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.8 KiB

View file

@ -0,0 +1,21 @@
digraph MultiModel {
graph [
goal="Build and review a utility function using multiple models",
model_stylesheet="
* { llm_model: claude-haiku-4-5; llm_provider: anthropic; reasoning_effort: low; }
.coding { llm_model: claude-sonnet-4-5; llm_provider: anthropic; reasoning_effort: high; }
#review { llm_model: claude-sonnet-4-5; llm_provider: anthropic; reasoning_effort: high; }
"
]
rankdir=LR
start [shape=Mdiamond, label="Start"]
exit [shape=Msquare, label="Exit"]
spec [label="Write Spec", prompt="Write a brief spec for a TypeScript string utility module with 3 functions: slugify, truncate, and capitalize. Output the spec only.", shape=tab]
implement [label="Implement", prompt="Implement the TypeScript string utility module from the spec. Write it to string-utils.ts.", class="coding"]
test [label="Write Tests", prompt="Write tests for the string utility module using Bun's test runner. Write to string-utils.test.ts.", class="coding"]
review [label="Code Review", prompt="Review the implementation and tests. Check for edge cases, type safety, and correctness. Provide a brief verdict.", shape=tab]
start -> spec -> implement -> test -> review -> exit
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View file

@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 14.1.2 (20260124.0452)
-->
<!-- Title: MultiModel Pages: 1 -->
<svg width="744pt" height="47pt"
viewBox="0.00 0.00 744.00 47.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 42.5)">
<title>MultiModel</title>
<polygon fill="white" stroke="none" points="-4,4 -4,-42.5 740.03,-42.5 740.03,4 -4,4"/>
<!-- start -->
<g id="node1" class="node">
<title>start</title>
<polygon fill="none" stroke="black" points="40.05,-37.25 0,-19.25 40.05,-1.25 80.1,-19.25 40.05,-37.25"/>
<polyline fill="none" stroke="black" points="10.95,-24.17 10.95,-14.33"/>
<polyline fill="none" stroke="black" points="29.11,-6.17 51,-6.17"/>
<polyline fill="none" stroke="black" points="69.16,-14.33 69.16,-24.17"/>
<polyline fill="none" stroke="black" points="51,-32.33 29.11,-32.33"/>
<text xml:space="preserve" text-anchor="middle" x="40.05" y="-14.2" font-family="Times,serif" font-size="14.00">Start</text>
</g>
<!-- spec -->
<g id="node3" class="node">
<title>spec</title>
<ellipse fill="none" stroke="black" cx="168.48" cy="-19.25" rx="52.38" ry="18"/>
<text xml:space="preserve" text-anchor="middle" x="168.48" y="-14.2" font-family="Times,serif" font-size="14.00">Write Spec</text>
</g>
<!-- start&#45;&gt;spec -->
<g id="edge1" class="edge">
<title>start&#45;&gt;spec</title>
<path fill="none" stroke="black" d="M81.05,-19.25C88.44,-19.25 96.32,-19.25 104.2,-19.25"/>
<polygon fill="black" stroke="black" points="104.18,-22.75 114.18,-19.25 104.18,-15.75 104.18,-22.75"/>
</g>
<!-- exit -->
<g id="node2" class="node">
<title>exit</title>
<polygon fill="none" stroke="black" points="736.03,-38.5 697.53,-38.5 697.53,0 736.03,0 736.03,-38.5"/>
<polyline fill="none" stroke="black" points="709.53,-38.5 697.53,-26.5"/>
<polyline fill="none" stroke="black" points="697.53,-12 709.53,0"/>
<polyline fill="none" stroke="black" points="724.03,0 736.03,-12"/>
<polyline fill="none" stroke="black" points="736.03,-26.5 724.03,-38.5"/>
<text xml:space="preserve" text-anchor="middle" x="716.78" y="-14.2" font-family="Times,serif" font-size="14.00">Exit</text>
</g>
<!-- implement -->
<g id="node4" class="node coding">
<title>implement</title>
<ellipse fill="none" stroke="black" cx="308.71" cy="-19.25" rx="51.86" ry="18"/>
<text xml:space="preserve" text-anchor="middle" x="308.71" y="-14.2" font-family="Times,serif" font-size="14.00">Implement</text>
</g>
<!-- spec&#45;&gt;implement -->
<g id="edge2" class="edge">
<title>spec&#45;&gt;implement</title>
<path fill="none" stroke="black" d="M221.34,-19.25C229.07,-19.25 237.09,-19.25 245.01,-19.25"/>
<polygon fill="black" stroke="black" points="244.96,-22.75 254.96,-19.25 244.96,-15.75 244.96,-22.75"/>
</g>
<!-- test -->
<g id="node5" class="node coding">
<title>test</title>
<ellipse fill="none" stroke="black" cx="449.98" cy="-19.25" rx="53.4" ry="18"/>
<text xml:space="preserve" text-anchor="middle" x="449.98" y="-14.2" font-family="Times,serif" font-size="14.00">Write Tests</text>
</g>
<!-- implement&#45;&gt;test -->
<g id="edge3" class="edge">
<title>implement&#45;&gt;test</title>
<path fill="none" stroke="black" d="M360.78,-19.25C368.69,-19.25 376.95,-19.25 385.11,-19.25"/>
<polygon fill="black" stroke="black" points="385,-22.75 395,-19.25 385,-15.75 385,-22.75"/>
</g>
<!-- review -->
<g id="node6" class="node">
<title>review</title>
<ellipse fill="none" stroke="black" cx="600.45" cy="-19.25" rx="61.08" ry="18"/>
<text xml:space="preserve" text-anchor="middle" x="600.45" y="-14.2" font-family="Times,serif" font-size="14.00">Code Review</text>
</g>
<!-- test&#45;&gt;review -->
<g id="edge4" class="edge">
<title>test&#45;&gt;review</title>
<path fill="none" stroke="black" d="M503.74,-19.25C511.5,-19.25 519.61,-19.25 527.67,-19.25"/>
<polygon fill="black" stroke="black" points="527.5,-22.75 537.5,-19.25 527.5,-15.75 527.5,-22.75"/>
</g>
<!-- review&#45;&gt;exit -->
<g id="edge5" class="edge">
<title>review&#45;&gt;exit</title>
<path fill="none" stroke="black" d="M661.96,-19.25C670.3,-19.25 678.54,-19.25 685.97,-19.25"/>
<polygon fill="black" stroke="black" points="685.72,-22.75 695.72,-19.25 685.72,-15.75 685.72,-22.75"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.3 KiB

View file

@ -0,0 +1,11 @@
digraph Simplify {
graph [goal="Review and simplify recent code changes"]
rankdir=LR
start [shape=Mdiamond, label="Start"]
exit [shape=Msquare, label="Exit"]
simplify [label="Simplify", prompt="@files-internal/prompts/simplify.md"]
start -> simplify -> exit
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

View file

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 14.1.2 (20260124.0452)
-->
<!-- Title: Simplify Pages: 1 -->
<svg width="286pt" height="47pt"
viewBox="0.00 0.00 286.00 47.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 42.5)">
<title>Simplify</title>
<polygon fill="white" stroke="none" points="-4,4 -4,-42.5 281.95,-42.5 281.95,4 -4,4"/>
<!-- start -->
<g id="node1" class="node">
<title>start</title>
<polygon fill="none" stroke="black" points="40.05,-37.25 0,-19.25 40.05,-1.25 80.1,-19.25 40.05,-37.25"/>
<polyline fill="none" stroke="black" points="10.95,-24.17 10.95,-14.33"/>
<polyline fill="none" stroke="black" points="29.11,-6.17 51,-6.17"/>
<polyline fill="none" stroke="black" points="69.16,-14.33 69.16,-24.17"/>
<polyline fill="none" stroke="black" points="51,-32.33 29.11,-32.33"/>
<text xml:space="preserve" text-anchor="middle" x="40.05" y="-14.2" font-family="Times,serif" font-size="14.00">Start</text>
</g>
<!-- simplify -->
<g id="node3" class="node">
<title>simplify</title>
<ellipse fill="none" stroke="black" cx="159.78" cy="-19.25" rx="43.67" ry="18"/>
<text xml:space="preserve" text-anchor="middle" x="159.78" y="-14.2" font-family="Times,serif" font-size="14.00">Simplify</text>
</g>
<!-- start&#45;&gt;simplify -->
<g id="edge1" class="edge">
<title>start&#45;&gt;simplify</title>
<path fill="none" stroke="black" d="M81.24,-19.25C88.75,-19.25 96.73,-19.25 104.59,-19.25"/>
<polygon fill="black" stroke="black" points="104.44,-22.75 114.44,-19.25 104.44,-15.75 104.44,-22.75"/>
</g>
<!-- exit -->
<g id="node2" class="node">
<title>exit</title>
<polygon fill="none" stroke="black" points="277.95,-38.5 239.45,-38.5 239.45,0 277.95,0 277.95,-38.5"/>
<polyline fill="none" stroke="black" points="251.45,-38.5 239.45,-26.5"/>
<polyline fill="none" stroke="black" points="239.45,-12 251.45,0"/>
<polyline fill="none" stroke="black" points="265.95,0 277.95,-12"/>
<polyline fill="none" stroke="black" points="277.95,-26.5 265.95,-38.5"/>
<text xml:space="preserve" text-anchor="middle" x="258.7" y="-14.2" font-family="Times,serif" font-size="14.00">Exit</text>
</g>
<!-- simplify&#45;&gt;exit -->
<g id="edge2" class="edge">
<title>simplify&#45;&gt;exit</title>
<path fill="none" stroke="black" d="M203.85,-19.25C211.85,-19.25 220.05,-19.25 227.56,-19.25"/>
<polygon fill="black" stroke="black" points="227.51,-22.75 237.51,-19.25 227.51,-15.75 227.51,-22.75"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View file

@ -0,0 +1,17 @@
digraph PlanImplement {
graph [goal="Plan, approve, implement, and simplify a change"]
rankdir=LR
start [shape=Mdiamond, label="Start"]
exit [shape=Msquare, label="Exit"]
plan [label="Plan", prompt="Analyze the goal and codebase. Write a clear, step-by-step implementation plan to a Markdown file called plan.md. Include what files will change and why.", reasoning_effort="high"]
approve [shape=hexagon, label="Approve Plan"]
implement [label="Implement", prompt="Read plan.md and implement every step. Make all the code changes described in the plan."]
simplify [label="Simplify", prompt="@files-internal/prompts/simplify.md"]
start -> plan -> approve
approve -> implement [label="[A] Approve"]
approve -> plan [label="[R] Revise"]
implement -> simplify -> exit
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View file

@ -0,0 +1,95 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 14.1.2 (20260124.0452)
-->
<!-- Title: PlanImplement Pages: 1 -->
<svg width="826pt" height="49pt"
viewBox="0.00 0.00 826.00 49.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 45.07)">
<title>PlanImplement</title>
<polygon fill="white" stroke="none" points="-4,4 -4,-45.07 822.18,-45.07 822.18,4 -4,4"/>
<!-- start -->
<g id="node1" class="node">
<title>start</title>
<polygon fill="none" stroke="black" points="40.05,-39.82 0,-21.82 40.05,-3.82 80.1,-21.82 40.05,-39.82"/>
<polyline fill="none" stroke="black" points="10.95,-26.74 10.95,-16.9"/>
<polyline fill="none" stroke="black" points="29.11,-8.74 51,-8.74"/>
<polyline fill="none" stroke="black" points="69.16,-16.9 69.16,-26.74"/>
<polyline fill="none" stroke="black" points="51,-34.9 29.11,-34.9"/>
<text xml:space="preserve" text-anchor="middle" x="40.05" y="-16.77" font-family="Times,serif" font-size="14.00">Start</text>
</g>
<!-- plan -->
<g id="node3" class="node">
<title>plan</title>
<ellipse fill="none" stroke="black" cx="144.4" cy="-21.82" rx="27.3" ry="18"/>
<text xml:space="preserve" text-anchor="middle" x="144.4" y="-16.77" font-family="Times,serif" font-size="14.00">Plan</text>
</g>
<!-- start&#45;&gt;plan -->
<g id="edge1" class="edge">
<title>start&#45;&gt;plan</title>
<path fill="none" stroke="black" d="M81.21,-21.82C89.26,-21.82 97.69,-21.82 105.66,-21.82"/>
<polygon fill="black" stroke="black" points="105.38,-25.32 115.38,-21.82 105.38,-18.32 105.38,-25.32"/>
</g>
<!-- exit -->
<g id="node2" class="node">
<title>exit</title>
<polygon fill="none" stroke="black" points="818.18,-41.07 779.68,-41.07 779.68,-2.57 818.18,-2.57 818.18,-41.07"/>
<polyline fill="none" stroke="black" points="791.68,-41.07 779.68,-29.07"/>
<polyline fill="none" stroke="black" points="779.68,-14.57 791.68,-2.57"/>
<polyline fill="none" stroke="black" points="806.18,-2.57 818.18,-14.57"/>
<polyline fill="none" stroke="black" points="818.18,-29.07 806.18,-41.07"/>
<text xml:space="preserve" text-anchor="middle" x="798.93" y="-16.77" font-family="Times,serif" font-size="14.00">Exit</text>
</g>
<!-- approve -->
<g id="node4" class="node">
<title>approve</title>
<polygon fill="none" stroke="black" points="409.61,-21.82 373.75,-39.82 302.05,-39.82 266.19,-21.82 302.05,-3.82 373.75,-3.82 409.61,-21.82"/>
<text xml:space="preserve" text-anchor="middle" x="337.9" y="-16.77" font-family="Times,serif" font-size="14.00">Approve Plan</text>
</g>
<!-- plan&#45;&gt;approve -->
<g id="edge2" class="edge">
<title>plan&#45;&gt;approve</title>
<path fill="none" stroke="black" d="M172.09,-23.22C177.89,-23.47 184,-23.69 189.69,-23.82 211.78,-24.32 235.73,-24.25 257.77,-23.95"/>
<polygon fill="black" stroke="black" points="257.65,-27.45 267.59,-23.79 257.54,-20.45 257.65,-27.45"/>
</g>
<!-- approve&#45;&gt;plan -->
<g id="edge4" class="edge">
<title>approve&#45;&gt;plan</title>
<path fill="none" stroke="black" d="M291.53,-8.6C262.28,-1.92 223.54,3.46 189.69,-3.32 185.53,-4.16 181.26,-5.39 177.1,-6.84"/>
<polygon fill="black" stroke="black" points="175.92,-3.54 167.89,-10.46 178.48,-10.06 175.92,-3.54"/>
<text xml:space="preserve" text-anchor="middle" x="218.94" y="-6.52" font-family="Times,serif" font-size="14.00">[R] Revise</text>
</g>
<!-- implement -->
<g id="node5" class="node">
<title>implement</title>
<ellipse fill="none" stroke="black" cx="566.47" cy="-21.82" rx="51.86" ry="18"/>
<text xml:space="preserve" text-anchor="middle" x="566.47" y="-16.77" font-family="Times,serif" font-size="14.00">Implement</text>
</g>
<!-- approve&#45;&gt;implement -->
<g id="edge3" class="edge">
<title>approve&#45;&gt;implement</title>
<path fill="none" stroke="black" d="M410.59,-21.82C440.21,-21.82 474.25,-21.82 502.85,-21.82"/>
<polygon fill="black" stroke="black" points="502.83,-25.32 512.83,-21.82 502.83,-18.32 502.83,-25.32"/>
<text xml:space="preserve" text-anchor="middle" x="462.11" y="-25.02" font-family="Times,serif" font-size="14.00">[A] Approve</text>
</g>
<!-- simplify -->
<g id="node6" class="node">
<title>simplify</title>
<ellipse fill="none" stroke="black" cx="699.01" cy="-21.82" rx="43.67" ry="18"/>
<text xml:space="preserve" text-anchor="middle" x="699.01" y="-16.77" font-family="Times,serif" font-size="14.00">Simplify</text>
</g>
<!-- implement&#45;&gt;simplify -->
<g id="edge5" class="edge">
<title>implement&#45;&gt;simplify</title>
<path fill="none" stroke="black" d="M618.69,-21.82C626.84,-21.82 635.31,-21.82 643.53,-21.82"/>
<polygon fill="black" stroke="black" points="643.44,-25.32 653.44,-21.82 643.44,-18.32 643.44,-25.32"/>
</g>
<!-- simplify&#45;&gt;exit -->
<g id="edge6" class="edge">
<title>simplify&#45;&gt;exit</title>
<path fill="none" stroke="black" d="M742.96,-21.82C751.42,-21.82 760.13,-21.82 768.05,-21.82"/>
<polygon fill="black" stroke="black" points="767.81,-25.32 777.81,-21.82 767.81,-18.32 767.81,-25.32"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5 KiB

View file

@ -0,0 +1,37 @@
digraph Ensemble {
graph [
goal="Get independent opinions from multiple providers, then synthesize",
model_stylesheet="
#opus { llm_model: claude-opus-4-6; llm_provider: anthropic; }
#gemini { llm_model: gemini-3.1-pro-preview; llm_provider: gemini; }
#codex { llm_model: gpt-5.3-codex; llm_provider: openai; }
#mercury { llm_model: mercury-2; llm_provider: inception; }
#synth { llm_model: claude-opus-4-6; llm_provider: anthropic; reasoning_effort: high; }
"
]
rankdir=LR
start [shape=Mdiamond, label="Start"]
exit [shape=Msquare, label="Exit"]
fork [label="Fan Out", shape=component, join_policy="wait_all", error_policy="continue"]
opus [label="Opus", prompt="Analyze the goal. Provide your independent assessment, recommendations, and any code or prose needed. Be thorough.", shape=tab]
gemini [label="Gemini", prompt="Analyze the goal. Provide your independent assessment, recommendations, and any code or prose needed. Be thorough.", shape=tab]
codex [label="Codex", prompt="Analyze the goal. Provide your independent assessment, recommendations, and any code or prose needed. Be thorough.", shape=tab]
mercury [label="Mercury", prompt="Analyze the goal. Provide your independent assessment, recommendations, and any code or prose needed. Be thorough.", shape=tab]
merge [label="Merge", shape=tripleoctagon]
synth [label="Synthesize", prompt="You have received independent analyses from four different models (Opus, Gemini, Codex, Mercury). Compare their perspectives: identify consensus, highlight disagreements, and synthesize the strongest ideas into a single coherent recommendation. Note where models agreed and where they diverged.", shape=tab]
start -> fork
fork -> opus
fork -> gemini
fork -> codex
fork -> mercury
opus -> merge
gemini -> merge
codex -> merge
mercury -> merge
merge -> synth -> exit
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

View file

@ -0,0 +1,145 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 14.1.2 (20260124.0452)
-->
<!-- Title: Ensemble Pages: 1 -->
<svg width="648pt" height="206pt"
viewBox="0.00 0.00 648.00 206.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 202)">
<title>Ensemble</title>
<polygon fill="white" stroke="none" points="-4,4 -4,-202 643.67,-202 643.67,4 -4,4"/>
<!-- start -->
<g id="node1" class="node">
<title>start</title>
<polygon fill="none" stroke="black" points="40.05,-117 0,-99 40.05,-81 80.1,-99 40.05,-117"/>
<polyline fill="none" stroke="black" points="10.95,-103.92 10.95,-94.08"/>
<polyline fill="none" stroke="black" points="29.11,-85.92 51,-85.92"/>
<polyline fill="none" stroke="black" points="69.16,-94.08 69.16,-103.92"/>
<polyline fill="none" stroke="black" points="51,-112.08 29.11,-112.08"/>
<text xml:space="preserve" text-anchor="middle" x="40.05" y="-93.95" font-family="Times,serif" font-size="14.00">Start</text>
</g>
<!-- fork -->
<g id="node3" class="node">
<title>fork</title>
<polygon fill="none" stroke="black" points="176.35,-117 116.1,-117 116.1,-113 112.1,-113 112.1,-109 116.1,-109 116.1,-89 112.1,-89 112.1,-85 116.1,-85 116.1,-81 176.35,-81 176.35,-117"/>
<polyline fill="none" stroke="black" points="116.1,-113 120.1,-113 120.1,-109 116.1,-109"/>
<polyline fill="none" stroke="black" points="116.1,-89 120.1,-89 120.1,-85 116.1,-85"/>
<text xml:space="preserve" text-anchor="middle" x="146.23" y="-93.95" font-family="Times,serif" font-size="14.00">Fan Out</text>
</g>
<!-- start&#45;&gt;fork -->
<g id="edge1" class="edge">
<title>start&#45;&gt;fork</title>
<path fill="none" stroke="black" d="M81.03,-99C88.73,-99 96.8,-99 104.52,-99"/>
<polygon fill="black" stroke="black" points="104.36,-102.5 114.36,-99 104.36,-95.5 104.36,-102.5"/>
</g>
<!-- exit -->
<g id="node2" class="node">
<title>exit</title>
<polygon fill="none" stroke="black" points="639.67,-118.25 601.17,-118.25 601.17,-79.75 639.67,-79.75 639.67,-118.25"/>
<polyline fill="none" stroke="black" points="613.17,-118.25 601.17,-106.25"/>
<polyline fill="none" stroke="black" points="601.17,-91.75 613.17,-79.75"/>
<polyline fill="none" stroke="black" points="627.67,-79.75 639.67,-91.75"/>
<polyline fill="none" stroke="black" points="639.67,-106.25 627.67,-118.25"/>
<text xml:space="preserve" text-anchor="middle" x="620.42" y="-93.95" font-family="Times,serif" font-size="14.00">Exit</text>
</g>
<!-- opus -->
<g id="node4" class="node">
<title>opus</title>
<ellipse fill="none" stroke="black" cx="255.51" cy="-180" rx="30.37" ry="18"/>
<text xml:space="preserve" text-anchor="middle" x="255.51" y="-174.95" font-family="Times,serif" font-size="14.00">Opus</text>
</g>
<!-- fork&#45;&gt;opus -->
<g id="edge2" class="edge">
<title>fork&#45;&gt;opus</title>
<path fill="none" stroke="black" d="M168.11,-117.43C180.63,-128.2 197.05,-141.82 212.35,-153 216.14,-155.77 220.21,-158.57 224.27,-161.27"/>
<polygon fill="black" stroke="black" points="222.02,-163.99 232.32,-166.49 225.83,-158.11 222.02,-163.99"/>
</g>
<!-- gemini -->
<g id="node5" class="node">
<title>gemini</title>
<ellipse fill="none" stroke="black" cx="255.51" cy="-126" rx="39.07" ry="18"/>
<text xml:space="preserve" text-anchor="middle" x="255.51" y="-120.95" font-family="Times,serif" font-size="14.00">Gemini</text>
</g>
<!-- fork&#45;&gt;gemini -->
<g id="edge3" class="edge">
<title>fork&#45;&gt;gemini</title>
<path fill="none" stroke="black" d="M176.83,-106.45C186.97,-109 198.55,-111.92 209.63,-114.7"/>
<polygon fill="black" stroke="black" points="208.44,-118.01 218.99,-117.06 210.15,-111.23 208.44,-118.01"/>
</g>
<!-- codex -->
<g id="node6" class="node">
<title>codex</title>
<ellipse fill="none" stroke="black" cx="255.51" cy="-72" rx="34.97" ry="18"/>
<text xml:space="preserve" text-anchor="middle" x="255.51" y="-66.95" font-family="Times,serif" font-size="14.00">Codex</text>
</g>
<!-- fork&#45;&gt;codex -->
<g id="edge4" class="edge">
<title>fork&#45;&gt;codex</title>
<path fill="none" stroke="black" d="M176.83,-91.55C187.83,-88.78 200.52,-85.59 212.43,-82.59"/>
<polygon fill="black" stroke="black" points="213.2,-86.01 222.04,-80.17 211.49,-79.22 213.2,-86.01"/>
</g>
<!-- mercury -->
<g id="node7" class="node">
<title>mercury</title>
<ellipse fill="none" stroke="black" cx="255.51" cy="-18" rx="43.16" ry="18"/>
<text xml:space="preserve" text-anchor="middle" x="255.51" y="-12.95" font-family="Times,serif" font-size="14.00">Mercury</text>
</g>
<!-- fork&#45;&gt;mercury -->
<g id="edge5" class="edge">
<title>fork&#45;&gt;mercury</title>
<path fill="none" stroke="black" d="M168.11,-80.57C180.63,-69.8 197.05,-56.18 212.35,-45 214.98,-43.08 217.75,-41.14 220.55,-39.23"/>
<polygon fill="black" stroke="black" points="222.42,-42.19 228.83,-33.76 218.56,-36.35 222.42,-42.19"/>
</g>
<!-- merge -->
<g id="node8" class="node">
<title>merge</title>
<polygon fill="none" stroke="black" points="419.49,-91.54 419.49,-106.46 396.99,-117 365.18,-117 342.68,-106.46 342.68,-91.54 365.18,-81 396.99,-81 419.49,-91.54"/>
<polygon fill="none" stroke="black" points="423.49,-89 423.49,-109 397.89,-121 364.28,-121 338.68,-109 338.68,-89 364.28,-77 397.89,-77 423.49,-89"/>
<polygon fill="none" stroke="black" points="427.49,-86.46 427.49,-111.54 398.78,-125 363.39,-125 334.68,-111.54 334.68,-86.46 363.39,-73 398.78,-73 427.49,-86.46"/>
<text xml:space="preserve" text-anchor="middle" x="381.09" y="-93.95" font-family="Times,serif" font-size="14.00">Merge</text>
</g>
<!-- opus&#45;&gt;merge -->
<g id="edge6" class="edge">
<title>opus&#45;&gt;merge</title>
<path fill="none" stroke="black" d="M277.14,-166.88C284.01,-162.49 291.69,-157.56 298.68,-153 312.4,-144.06 327.35,-134.18 340.65,-125.34"/>
<polygon fill="black" stroke="black" points="342.36,-128.41 348.75,-119.96 338.48,-122.58 342.36,-128.41"/>
</g>
<!-- gemini&#45;&gt;merge -->
<g id="edge7" class="edge">
<title>gemini&#45;&gt;merge</title>
<path fill="none" stroke="black" d="M291.26,-118.41C301.22,-116.23 312.35,-113.8 323.27,-111.41"/>
<polygon fill="black" stroke="black" points="324.02,-114.83 333.04,-109.28 322.52,-107.99 324.02,-114.83"/>
</g>
<!-- codex&#45;&gt;merge -->
<g id="edge8" class="edge">
<title>codex&#45;&gt;merge</title>
<path fill="none" stroke="black" d="M288.34,-78.95C299.07,-81.3 311.39,-83.99 323.45,-86.63"/>
<polygon fill="black" stroke="black" points="322.4,-89.98 332.92,-88.69 323.89,-83.14 322.4,-89.98"/>
</g>
<!-- mercury&#45;&gt;merge -->
<g id="edge9" class="edge">
<title>mercury&#45;&gt;merge</title>
<path fill="none" stroke="black" d="M280.19,-33.07C286.23,-36.94 292.7,-41.11 298.68,-45 312.4,-53.94 327.35,-63.82 340.65,-72.66"/>
<polygon fill="black" stroke="black" points="338.48,-75.42 348.75,-78.04 342.36,-69.59 338.48,-75.42"/>
</g>
<!-- synth -->
<g id="node9" class="node">
<title>synth</title>
<ellipse fill="none" stroke="black" cx="514.33" cy="-99" rx="50.84" ry="18"/>
<text xml:space="preserve" text-anchor="middle" x="514.33" y="-93.95" font-family="Times,serif" font-size="14.00">Synthesize</text>
</g>
<!-- merge&#45;&gt;synth -->
<g id="edge10" class="edge">
<title>merge&#45;&gt;synth</title>
<path fill="none" stroke="black" d="M427.99,-99C435.73,-99 443.87,-99 451.92,-99"/>
<polygon fill="black" stroke="black" points="451.69,-102.5 461.69,-99 451.69,-95.5 451.69,-102.5"/>
</g>
<!-- synth&#45;&gt;exit -->
<g id="edge11" class="edge">
<title>synth&#45;&gt;exit</title>
<path fill="none" stroke="black" d="M565.55,-99C573.77,-99 582.06,-99 589.59,-99"/>
<polygon fill="black" stroke="black" points="589.51,-102.5 599.51,-99 589.51,-95.5 589.51,-102.5"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.6 KiB

View file

@ -0,0 +1,11 @@
digraph WebFetch {
graph [goal="Fetch a web page and summarize its contents"]
rankdir=LR
start [shape=Mdiamond, label="Start"]
exit [shape=Msquare, label="Exit"]
fetch [label="Fetch & Summarize", prompt="Use the web_fetch tool to fetch https://httpbin.org/html and summarize the page contents in 2-3 sentences. Write your summary to summary.txt."]
start -> fetch -> exit
}

View file

@ -0,0 +1,11 @@
digraph WebSearch {
graph [goal="Search the web for a topic and summarize the findings"]
rankdir=LR
start [shape=Mdiamond, label="Start"]
exit [shape=Msquare, label="Exit"]
search [label="Search & Summarize", prompt="Use the web_search tool to search for 'what is DOT graph language' and summarize the top results in 2-3 sentences. Write your summary to search-results.txt."]
start -> search -> exit
}

View file

@ -0,0 +1,13 @@
digraph SearchImageGen {
graph [goal="Search the web for a famous landmark, then generate an image of it"]
rankdir=LR
start [shape=Mdiamond, label="Start"]
exit [shape=Msquare, label="Exit"]
research [label="Research", prompt="Use web_search to find a brief description of the Sagrada Familia in Barcelona. Save a 2-3 sentence description to landmark.txt."]
imagine [label="Generate Image", prompt="Read landmark.txt. Using its description, craft a vivid, detailed image generation prompt.\nThen run: mkdir -p output && imagegen '<your prompt>' output/landmark.png\nThe imagegen command takes a text prompt and an output path."]
start -> research -> imagine -> exit
}

View file

@ -0,0 +1,19 @@
version = 1
goal = "Search the web for a famous landmark, then generate an image of it"
graph = "14-search-imagegen.dot"
[sandbox]
provider = "daytona"
[sandbox.env]
GEMINI_API_KEY = "${env.GEMINI_API_KEY}"
[sandbox.daytona.snapshot]
name = "imagegen-tools-v3"
cpu = 4
memory = 8
disk = 10
dockerfile = { path = "../../arc/workflows/imagegen/Dockerfile.imagegen" }
[assets]
include = ["output/**"]

View file

@ -0,0 +1,300 @@
# Fabro Events Strategy
Fabro emits structured **workflow run events** during execution for observability. Events write to `progress.jsonl` (one JSON object per line) and `live.json` (latest event snapshot) inside the run's log directory. Events are the primary record of what happened during a run — they feed the retro system, CLI verbose output, and live monitoring.
Events are distinct from tracing logs (see `logging-strategy.md`). Tracing is developer diagnostics; events are the structured audit trail consumed by tooling.
## Architecture
```
Engine/Handler → WorkflowRunEvent enum → EventEmitter
├─ .trace() → tracing log line (automatic)
├─ flatten_event() → progress.jsonl + live.json
└─ on_event() callbacks → CLI output, cost tracking, etc.
```
**Three layers:**
1. **Rust enum** (`WorkflowRunEvent` in `event.rs`) — the source of truth for event structure. Variants use Rust naming and types. `AgentEvent` and `SandboxEvent` from `fabro-agent` are wrapped as `Agent { stage, event }` and `Sandbox { event }`.
2. **Flattening** (`flatten_event()` in `event.rs`) — serializes the enum via serde, then restructures nested/tagged variants into `(event_name, flat_fields_map)`. Wrapper variants use dot notation: `Agent.ToolCallStarted`, `Sandbox.Initializing`.
3. **Field renaming** (`rename_fields()` in `event.rs`) — post-processes the flat fields to give them self-describing names for JSONL output. This avoids changing the Rust enum while making the external format unambiguous.
## JSONL Envelope
Every line in `progress.jsonl` has three envelope fields, then the event's own fields merged at the top level:
```json
{"ts":"2025-06-15T12:00:00.123Z","run_id":"01J...","event":"StageCompleted","node_id":"plan","node_label":"Plan","stage_index":0,"duration_ms":5000,"status":"success",...}
```
| Envelope field | Type | Description |
|---|---|---|
| `ts` | ISO 8601 string | UTC timestamp with millisecond precision |
| `run_id` | string | ULID for this workflow run |
| `event` | string | Event name (matches Rust variant, dot-separated for wrapped types) |
The envelope is built in `cli/run.rs`. Field names from the event that collide with envelope keys (`ts`, `run_id`, `event`) are dropped — the `run_id` from `WorkflowRunStarted` populates the envelope itself.
## Node Terminology
- **`node_id`** — programmatic identifier (the id from the DOT graph). Stable, used for matching.
- **`node_label`** — display name (from the DOT `label` attribute, defaults to `node_id`). Human-readable.
Every event that references a graph node should include both. Stage events carry both from the Rust enum. Events that only have an id (Agent, ParallelBranch, etc.) get `node_label` defaulted to `node_id` by `rename_fields()`.
## Field Naming Conventions
### Rules
1. **Self-describing** — a field name should be unambiguous without knowing the event type. Use `node_id` not `name`, `stage_index` not `index`, `sandbox_provider` not `provider`.
2. **`_id` suffix** for identifiers — `node_id`, `from_node_id`, `to_node_id`, `start_node_id`, `tool_call_id`, `agent_id`.
3. **`_ms` suffix** for durations — `duration_ms`, `delay_ms`. Always milliseconds.
4. **`_count` suffix** for counts — `branch_count`, `command_count`, `tool_call_count`.
5. **No prefix** for fields that are already unambiguous — `error`, `status`, `command`, `question`, `answer`, `model`.
6. **`snake_case`** for all field names.
### Rename Table (Rust enum → JSONL)
The Rust enum uses short field names for ergonomics. `rename_fields()` transforms them for the JSONL output:
| Rust field | JSONL field | Events | Reason |
|---|---|---|---|
| `name` | `workflow_name` | WorkflowRunStarted | Disambiguate |
| `name` | `node_label` | Stage* | Display name |
| `name` | `snapshot_name` | Sandbox.Snapshot* | Disambiguate |
| `index` | `stage_index` | Stage* | Disambiguate |
| `index` | `branch_index` | ParallelBranch* | Disambiguate |
| `index` | `command_index` | SetupCommand*, SetupFailed, DevcontainerLifecycleCommand*, DevcontainerLifecycleFailed | Disambiguate |
| `stage` | `node_id` | Agent.*, Interview*, Prompt | Unify terminology |
| `branch` | `node_id` | ParallelBranch* | Consistent |
| `node` | `node_id` | StallWatchdogTimeout | Consistent |
| `from_node` | `from_node_id` | EdgeSelected, LoopRestart | `_id` suffix |
| `to_node` | `to_node_id` | EdgeSelected, LoopRestart | `_id` suffix |
| `start_node` | `start_node_id` | SubgraphStarted | `_id` suffix |
| `provider` | `sandbox_provider` | Sandbox.* | Disambiguate |
| `text` | `prompt_text` | Prompt | Disambiguate |
| _(inserted)_ | `node_label` | Agent.*, ParallelBranch*, etc. | Defaults to `node_id` |
Fields not in this table pass through unchanged.
## Adding a New Event
### Step 1: Add to the Rust enum
Add a variant to `WorkflowRunEvent` in `event.rs`. Use the short Rust field names (they'll be renamed in step 3).
```rust
MyNewEvent {
node_id: String, // if it references a graph node
name: String, // if it has a display label (will become node_label)
duration_ms: u64,
// ...
},
```
For events that wrap `AgentEvent` or `SandboxEvent`, add the variant to those enums in `fabro-agent` instead — they're automatically wrapped by the existing `Agent { stage, event }` and `Sandbox { event }` variants.
### Step 2: Add a trace() match arm
Add a match arm in `WorkflowRunEvent::trace()`. Choose the tracing level per `logging-strategy.md`:
- INFO for lifecycle boundaries (started/completed at the workflow level)
- DEBUG for individual steps (stage started, tool call, etc.)
- WARN for retries and degraded behavior
- ERROR for terminal failures
```rust
Self::MyNewEvent { node_id, duration_ms, .. } => {
debug!(node_id, duration_ms, "My new event happened");
}
```
### Step 3: Add rename rules (if needed)
If your event has fields that need renaming (ambiguous `name`, `index`, `stage`, etc.), add a branch in `rename_fields()` in `event.rs`. If your event references a graph node and only has `node_id`, call `default_node_label(fields)` to insert `node_label`.
### Step 4: Emit from engine or handler
Emit via the `EventEmitter`:
```rust
self.services.emitter.emit(&WorkflowRunEvent::MyNewEvent {
node_id: node.id.clone(),
name: node.label().to_string(),
duration_ms: elapsed,
});
```
### Step 5: Update format_event_summary
Add a match arm in `format_event_summary()` in `cli/mod.rs` for `-v` verbose output:
```rust
WorkflowRunEvent::MyNewEvent { node_id, duration_ms, .. } => {
format!("[MY_NEW_EVENT] node_id={node_id} duration={duration_ms}ms")
}
```
### Step 6: Update tests
- Add a serialization test in `event.rs` (serde round-trip)
- Add a `rename_fields` test if you added rename rules
- Update integration test patterns in `integration.rs` if matching on the new event
## Complete Event Reference
### Workflow lifecycle
| Event | JSONL fields |
|---|---|
| `WorkflowRunStarted` | `workflow_name`, `run_id`, `base_sha`?, `run_branch`?, `worktree_dir`? |
| `WorkflowRunCompleted` | `duration_ms`, `artifact_count`, `total_cost`?, `final_git_commit_sha`? |
| `WorkflowRunFailed` | `error`, `duration_ms`, `git_commit_sha`? |
### Stage execution
| Event | JSONL fields |
|---|---|
| `StageStarted` | `node_id`, `node_label`, `stage_index`, `handler_type`?, `attempt`, `max_attempts` |
| `StageCompleted` | `node_id`, `node_label`, `stage_index`, `duration_ms`, `status`, `preferred_label`?, `suggested_next_ids`, `usage`?, `failure_reason`?, `notes`?, `files_touched`, `attempt`, `max_attempts`, `failure_class`? |
| `StageFailed` | `node_id`, `node_label`, `stage_index`, `error`, `will_retry`, `failure_reason`?, `failure_class`? |
| `StageRetrying` | `node_id`, `node_label`, `stage_index`, `attempt`, `max_attempts`, `delay_ms` |
### Parallel execution
| Event | JSONL fields |
|---|---|
| `ParallelStarted` | `branch_count`, `join_policy`, `error_policy` |
| `ParallelBranchStarted` | `node_id`, `node_label`, `branch_index` |
| `ParallelBranchCompleted` | `node_id`, `node_label`, `branch_index`, `duration_ms`, `status` |
| `ParallelCompleted` | `duration_ms`, `success_count`, `failure_count` |
| `ParallelEarlyTermination` | `reason`, `completed_count`, `pending_count` |
### Graph navigation
| Event | JSONL fields |
|---|---|
| `EdgeSelected` | `from_node_id`, `to_node_id`, `label`?, `condition`? |
| `LoopRestart` | `from_node_id`, `to_node_id` |
| `SubgraphStarted` | `node_id`, `node_label`, `start_node_id` |
| `SubgraphCompleted` | `node_id`, `node_label`, `steps_executed`, `status`, `duration_ms` |
### Checkpoints and git
| Event | JSONL fields |
|---|---|
| `CheckpointSaved` | `node_id`, `node_label` |
| `GitCheckpoint` | `run_id`, `node_id`, `node_label`, `status`, `git_commit_sha` |
| `GitCheckpointFailed` | `node_id`, `node_label`, `error` |
### Human interaction
| Event | JSONL fields |
|---|---|
| `InterviewStarted` | `question`, `node_id`, `node_label`, `question_type` |
| `InterviewCompleted` | `question`, `answer`, `duration_ms` |
| `InterviewTimeout` | `question`, `node_id`, `node_label`, `duration_ms` |
| `Prompt` | `node_id`, `node_label`, `prompt_text` |
### Setup
| Event | JSONL fields |
|---|---|
| `SetupStarted` | `command_count` |
| `SetupCommandStarted` | `command`, `command_index` |
| `SetupCommandCompleted` | `command`, `command_index`, `exit_code`, `duration_ms` |
| `SetupCompleted` | `duration_ms` |
| `SetupFailed` | `command`, `command_index`, `exit_code`, `stderr` |
### Devcontainer
| Event | JSONL fields |
|---|---|
| `DevcontainerResolved` | `dockerfile_lines`, `environment_count`, `lifecycle_command_count`, `workspace_folder` |
| `DevcontainerLifecycleStarted` | `phase`, `command_count` |
| `DevcontainerLifecycleCommandStarted` | `phase`, `command`, `command_index` |
| `DevcontainerLifecycleCommandCompleted` | `phase`, `command`, `command_index`, `exit_code`, `duration_ms` |
| `DevcontainerLifecycleCompleted` | `phase`, `duration_ms` |
| `DevcontainerLifecycleFailed` | `phase`, `command`, `command_index`, `exit_code`, `stderr` |
### Stall detection
| Event | JSONL fields |
|---|---|
| `StallWatchdogTimeout` | `node_id`, `node_label`, `idle_seconds` |
### Agent events (prefixed `Agent.`)
All agent events include `node_id` and `node_label`.
| Event | Additional JSONL fields |
|---|---|
| `Agent.SessionStarted` | _(none)_ |
| `Agent.SessionEnded` | _(none)_ |
| `Agent.UserInput` | `text` |
| `Agent.AssistantTextStart` | _(none)_ |
| `Agent.AssistantMessage` | `text`, `model`, `usage` (object), `tool_call_count` |
| `Agent.TextDelta` | `delta` |
| `Agent.ToolCallStarted` | `tool_name`, `tool_call_id`, `arguments` |
| `Agent.ToolCallOutputDelta` | `delta` |
| `Agent.ToolCallCompleted` | `tool_name`, `tool_call_id`, `output`, `is_error` |
| `Agent.Error` | `error` |
| `Agent.ContextWindowWarning` | `estimated_tokens`, `context_window_size`, `usage_percent` |
| `Agent.LoopDetected` | _(none)_ |
| `Agent.TurnLimitReached` | `max_turns` |
| `Agent.SkillExpanded` | `skill_name` |
| `Agent.SteeringInjected` | `text` |
| `Agent.CompactionStarted` | `estimated_tokens`, `context_window_size` |
| `Agent.CompactionCompleted` | `original_turn_count`, `preserved_turn_count`, `summary_token_estimate`, `tracked_file_count` |
| `Agent.LlmRetry` | `provider`, `model`, `attempt`, `delay_secs`, `error` |
| `Agent.SubAgentSpawned` | `agent_id`, `depth`, `task` |
| `Agent.SubAgentCompleted` | `agent_id`, `depth`, `success`, `turns_used` |
| `Agent.SubAgentFailed` | `agent_id`, `depth`, `error` |
| `Agent.SubAgentClosed` | `agent_id`, `depth` |
| `Agent.SubAgentEvent.*` | `agent_id`, `depth`, `nested_event` (JSON) |
| `Agent.McpServerReady` | `server_name`, `tool_count` |
| `Agent.McpServerFailed` | `server_name`, `error` |
### Sandbox events (prefixed `Sandbox.`)
| Event | JSONL fields |
|---|---|
| `Sandbox.Initializing` | `sandbox_provider` |
| `Sandbox.Ready` | `sandbox_provider`, `duration_ms` |
| `Sandbox.InitializeFailed` | `sandbox_provider`, `error`, `duration_ms` |
| `Sandbox.CleanupStarted` | `sandbox_provider` |
| `Sandbox.CleanupCompleted` | `sandbox_provider`, `duration_ms` |
| `Sandbox.CleanupFailed` | `sandbox_provider`, `error` |
| `Sandbox.SnapshotPulling` | `snapshot_name` |
| `Sandbox.SnapshotPulled` | `snapshot_name`, `duration_ms` |
| `Sandbox.SnapshotEnsuring` | `snapshot_name` |
| `Sandbox.SnapshotCreating` | `snapshot_name` |
| `Sandbox.SnapshotReady` | `snapshot_name`, `duration_ms` |
| `Sandbox.SnapshotFailed` | `snapshot_name`, `error` |
| `Sandbox.GitCloneStarted` | `url`, `branch` |
| `Sandbox.GitCloneCompleted` | `url`, `duration_ms` |
| `Sandbox.GitCloneFailed` | `url`, `error` |
## Error Fields
Error information is stored as plain strings. The `error` field contains the human-readable message; `failure_class` contains the machine-readable classification.
| Field | Type | Events | Purpose |
|---|---|---|---|
| `error` | string | StageFailed, WorkflowRunFailed, Agent.Error, Agent.LlmRetry, Sandbox.*Failed, etc. | Human-readable error message |
| `failure_reason` | string? | StageFailed, StageCompleted | Outcome-level failure description |
| `failure_class` | string? | StageFailed, StageCompleted | Machine classification: `transient_infra`, `deterministic`, `budget_exhausted`, `compilation_loop`, `canceled`, `structural` |
`failure_class` is derived from `ArcError::failure_class()` for handler errors, or from handler hints in `context_updates["failure_class"]` for outcome-based failures. See `error.rs` for the classification logic.
## Consumers
| Consumer | Reads | Purpose |
|---|---|---|
| `retro.rs` `extract_stage_durations()` | `node_label`, `duration_ms` from `StageCompleted` | Build retro report |
| `cli/run.rs` non-verbose listener | `name`, `duration_ms`, `status`, `usage` from `StageCompleted/Failed` | CLI progress output |
| `cli/mod.rs` `format_event_summary()` | All events | `-v` verbose output |
| `cli/run.rs` cost accumulator | `usage` from `StageCompleted` | Total cost tracking |
| `cli/run.rs` git SHA tracker | `git_commit_sha` from `GitCheckpoint` | Final SHA for `conclusion.json` |
| External tooling | `progress.jsonl` | Live monitoring, dashboards |

View file

@ -0,0 +1,180 @@
# Fabro Logging Strategy
Fabro uses the `tracing` crate for structured, file-based logging. Logs write to `~/.fabro/logs/YYYY-MM-DD.log`, controlled by the `FABRO_LOG` env var (default: `info`). Logs are for **developers debugging issues after the fact** — they are not user-facing output.
Production runs at INFO level. INFO should be low-volume and high-signal — the summary of what happened. When something goes wrong, developers enable `FABRO_LOG=debug` to get the full picture. DEBUG can be as verbose as needed since it's only turned on temporarily.
## When to Log
**Log at INFO (always on in production):**
- Lifecycle boundaries of top-level operations — session started/completed, pipeline started/completed, server ready
- Failures and warnings — every error/warn path, with enough context to diagnose the cause
- Keep it sparse: a typical agent session should produce ~5-10 INFO lines
**Log at DEBUG (enabled on-demand for investigation):**
- Individual steps within an operation — each LLM request, each tool call, each pipeline node
- External interactions with detail — request parameters, response metadata, token counts
- Decision points — why a code path was taken (retry triggered, fallback used, config value resolved)
- State changes and intermediate results — config resolution, parsing outcomes
**Do not log:**
- Hot loops or per-token streaming events (use DEBUG only if truly needed for diagnosis)
- Data that belongs in user-facing output (`eprintln!` for CLI feedback, not tracing)
- Redundant information already captured by a parent event (if you logged "starting X", you don't need to log every sub-step at the same level)
- Events that are already traced via `EventEnum::trace()` — the event enums (`AgentEvent`, `PipelineEvent`, `ExecutionEnvEvent`) each have a `trace()` method called automatically at their emit site; do not add manual `info!`/`debug!` calls that duplicate what `trace()` already emits
- Wrapper/forwarding variants that re-emit an inner event — `PipelineEvent::Agent`, `PipelineEvent::ExecutionEnv`, and `AgentEvent::SubAgentEvent` are no-ops in `trace()` because the inner event is already traced at its origin
- Secrets, API keys, or auth tokens — even at DEBUG level
## Log Levels
### ERROR — Something failed and the operation cannot continue
The current operation is aborting. A human reviewing logs should investigate every ERROR.
```rust
error!(server = %name, error = %err, "MCP server failed to start");
error!(provider = %provider, status = %status, "LLM request failed after all retries");
```
### WARN — Something unexpected happened but execution continues
Degraded behavior, fallback paths, or conditions that might indicate a problem.
```rust
warn!(server = %name, "MCP server disconnected, removing tools");
warn!(attempt = attempt, max = max_retries, error = %err, "LLM request failed, retrying");
```
### INFO — The production log level
INFO is always on. It should tell you **what** happened at a high level: which operations started, which completed, and key outcomes. Think of INFO as the audit trail — enough to answer "what did the system do?" but not so much that it creates noise. A typical agent session should produce a handful of INFO lines, not hundreds.
```rust
info!(model = %model, "Starting agent session");
info!(server = %name, tools = tool_count, "MCP server ready");
info!(pipeline = %name, "Pipeline complete");
info!(turns = turn_count, tool_calls = tool_call_count, "Agent session complete");
```
### DEBUG — Turn this on when something goes wrong
DEBUG is off in production by default. Enable it with `FABRO_LOG=debug` to investigate a specific issue. DEBUG events provide the **how** and **why**: request/response details, intermediate state, config resolution, individual steps within a larger operation. DEBUG can be verbose — that's fine, since it's only enabled temporarily.
```rust
debug!(model = %model, messages = msg_count, tools = tool_count, "Sending LLM request");
debug!(provider = %provider, input_tokens = input, output_tokens = output, "LLM response received");
debug!(tool = %name, duration_ms = elapsed, "Tool call complete");
debug!(path = %path.display(), "Loading workflow file");
debug!(env_var = "ANTHROPIC_API_KEY", "API key resolved from environment");
```
## How to Write a Log Event
### Message: describe what happened
The message string is a short, human-readable description. Use sentence fragments starting with a verb or noun. No variable interpolation in the message — put variable data in structured fields.
```rust
// Good — message is a fixed string, data is in fields
info!(server = %name, tools = tool_count, "MCP server ready");
// Bad — variable data interpolated into message string
info!("MCP server '{}' ready with {} tools", name, tool_count);
```
Fixed message strings make logs grepable and let tooling aggregate events by message.
### Fields: attach structured context
Fields are key-value pairs that make events queryable. Include enough context that the event is useful on its own without reading surrounding log lines.
**Field naming:**
- Use `snake_case` for field names
- Use consistent names across the codebase (see table below)
- Keep names short but unambiguous
**Common field names:**
| Field | Used for |
|-------|----------|
| `model` | LLM model identifier |
| `provider` | LLM provider name (anthropic, openai, gemini) |
| `server` | MCP server name |
| `tool` | Tool name being called |
| `turn` | Agent turn number |
| `attempt` | Retry attempt number |
| `error` | Error value on failure |
| `path` | File system path |
| `duration_ms` | Elapsed time in milliseconds |
| `input_tokens` | Token count for LLM input |
| `output_tokens` | Token count for LLM output |
**Field format specifiers:**
- `%` (Display) for user-readable values: `server = %name`, `error = %err`, `path = %path.display()`
- `?` (Debug) for internal/enum values: `level = ?params.level`, `status = ?response.status`
- No specifier for primitives: `tools = tool_count`, `attempt = 3`
### Examples by crate
**fabro-agent:**
```rust
info!(model = %model, "Starting agent session");
info!(turns = turn_count, tool_calls = total_calls, "Agent session complete");
debug!(turn = turn_number, "Starting agent turn");
debug!(tool = %name, "Executing tool call");
debug!(tool = %name, duration_ms = elapsed, "Tool call complete");
warn!(tool = %name, error = %err, "Tool execution failed");
```
**fabro-llm:**
```rust
debug!(provider = %provider, model = %model, messages = count, "Sending LLM request");
debug!(provider = %provider, model = %model, input_tokens = input, output_tokens = output, "LLM response received");
warn!(provider = %provider, attempt = n, error = %err, "Request failed, retrying");
error!(provider = %provider, error = %err, "Request failed after all retries");
```
**fabro-workflows:**
```rust
info!(pipeline = %name, "Starting pipeline execution");
info!(pipeline = %name, nodes = count, "Pipeline complete");
debug!(node = %id, handler = %handler_type, "Executing pipeline node");
debug!(node = %id, duration_ms = elapsed, "Pipeline node complete");
```
**fabro-mcp:**
```rust
info!(server = %name, tools = tool_count, "MCP server ready");
debug!(server = %name, transport = %transport_type, "Connecting to MCP server");
error!(server = %name, error = %err, "MCP server failed to start");
```
## Cross-Package Guidelines
Every crate that does meaningful work should emit tracing events. The `tracing` dependency is workspace-level — add it to any crate's `Cargo.toml` with:
```toml
tracing.workspace = true
```
The subscriber is initialized once in `fabro-cli`. Library crates (`fabro-agent`, `fabro-llm`, etc.) only emit events — they never configure the subscriber. This means:
- Library crates import `tracing::{info, debug, warn, error}` and call the macros
- The events go nowhere in unit tests (this is fine — tests verify behavior, not log output)
- The events are captured by whatever subscriber the binary sets up
When adding tracing to a new crate, start with the boundaries: INFO for the start/end of top-level operations, DEBUG for the individual steps within them. When in doubt about the level, use DEBUG — it's easy to promote something to INFO later, but hard to demote a noisy INFO event without breaking someone's log monitoring.
## Event Enum Tracing
The domain event enums (`AgentEvent`, `PipelineEvent`, `ExecutionEnvEvent`) each implement a `pub fn trace(&self)` method (or `trace(&self, session_id: &str)` for `AgentEvent`) that emits a structured tracing log line per variant. This method is called automatically from each enum's emit site, so every emitted event produces a log line without any additional code at the call site.
**Rules for event tracing:**
- **Add tracing for new variants** by adding a match arm in the enum's `trace()` method. Choose the level based on the guidelines above (INFO for lifecycle boundaries, DEBUG for individual steps, WARN/ERROR for failures).
- **Do not add manual log calls at emit sites.** The `trace()` call in the emitter handles it. Adding `info!` or `debug!` next to an `emit()` call will double-log.
- **Wrapper variants are no-ops.** When one event enum wraps another (`PipelineEvent::Agent` wraps `AgentEvent`, `AgentEvent::SubAgentEvent` wraps a child `AgentEvent`), the wrapper's `trace()` arm is `{}` because the inner event was already traced at its origin. This prevents double-logging.
- **Streaming noise variants are no-ops.** `TextDelta` and `ToolCallOutputDelta` produce no log output — per-token events would flood the logs even at DEBUG level.

View file

@ -0,0 +1,130 @@
# Updating Web UI Screenshots
Screenshots of the Fabro web UI are embedded in the public docs. This guide covers how to retake them when the UI changes.
## Prerequisites
- Docker installed
- Chrome running with DevTools MCP or similar screenshot tool
- The `fabro` Docker image already built (`docker compose -f docker/docker-compose.yaml build`)
## Boot the demo environment
```bash
docker compose -f docker/docker-compose.yaml up api web -d
```
Wait ~10 seconds for both services to be ready, then verify:
```bash
curl -s -o /dev/null -w "%{http_code}" http://localhost:5173/runs
# Should return 200
```
The web container runs in demo mode (`FABRO_DEMO=1`), which returns synthetic data from the API.
## Applying temporary UI changes for screenshots
The Docker image bakes in the fabro-web source at build time. To make temporary changes (like hiding nav items), edit files inside the running container using `sed`:
```bash
# Example: hide Start and Settings nav items
docker exec docker-web-1 sed -i '/{.*"Start".*}/d; /{.*"Settings".*}/d' \
/app/apps/fabro-web/app/layouts/app-shell.tsx
```
**Do not use `docker cp`** to replace source files — it breaks Vite's module resolution and causes 500 errors. Use `sed -i` inside the container instead, which preserves the file inode and lets HMR work correctly.
`docker cp` works fine for static assets in `public/` (logos, images), just not for source files that Vite processes.
After `sed` edits, wait a few seconds for HMR to rebuild, then verify pages still return 200 before taking screenshots.
## Updating logos
The logos are static files in `apps/fabro-web/public/`. The Docker compose file mounts this directory as a volume:
```yaml
volumes:
- ../apps/fabro-web/public:/app/apps/fabro-web/public
```
So changes to `apps/fabro-web/public/logotype.svg` (dark theme) and `apps/fabro-web/public/logotype-light.svg` (light theme) are picked up immediately by the running container. The source-of-truth logos are in `docs/logo/dark.svg` and `docs/logo/light.svg`.
## Browser setup
Set the browser viewport to **1200x800**. This width:
- Fits the full nav bar without overlap
- Provides a good aspect ratio for embedding in docs
- Shows enough content in kanban boards and tables
If the nav bar is too crowded at this width, hide low-priority items (Start, Settings) using the `sed` technique above.
## Taking screenshots
Screenshots live in `docs/images/web/`. Each screenshot maps to a specific URL:
| File | URL |
|---|---|
| `workflows-list.png` | `/workflows` |
| `workflow-detail.png` | `/workflows/fix_build` |
| `workflow-diagram.png` | `/workflows/fix_build/diagram` |
| `workflow-runs.png` | `/workflows/fix_build/runs` |
| `runs-board.png` | `/runs` |
| `run-overview.png` | `/runs/run-1` |
| `run-stages.png` | `/runs/run-1/stages/detect-drift` |
| `run-files-changed.png` | `/runs/run-1/compare` |
| `run-retro.png` | `/runs/run-1/retro` |
| `run-usage.png` | `/runs/run-1/usage` |
| `retros-list.png` | `/retros` |
### Verification checklist
**Verify every screenshot after taking it.** Open the saved PNG and check:
1. **No 500 errors** — the most common failure mode. The demo API or HMR can transiently break. If you see a 500 error page, wait a few seconds and retake.
2. **Correct logo** — should say "Fabro", not "Arc".
3. **No nav overlap** — the rightmost nav item should not overlap the theme toggle or user avatar.
4. **Content fully loaded** — watch for "Loading diagram..." or spinner states. For the run overview page, wait 2-3 seconds after navigation for the workflow graph to render.
5. **Dark theme** — all screenshots should use the dark theme (default). If you accidentally toggled to light theme, toggle back before continuing.
6. **Correct page** — verify the active tab/breadcrumb matches the expected page.
### Pages that need extra wait time
- **Run overview** (`/runs/run-1`) — the workflow graph diagram takes 2-3 seconds to render after the page loads. Wait before screenshotting.
- **After `sed` edits** — HMR needs a few seconds to rebuild. The first navigation after an edit may hit a transient error; retry once.
## Where screenshots are used in docs
Each screenshot is wrapped in a `<Frame caption="...">` component. To find all usages:
```bash
grep -r "images/web/" docs/ --include="*.mdx"
```
Current placements:
| Screenshot | Doc page |
|---|---|
| `runs-board.png` | `core-concepts/how-fabro-works.mdx` |
| `run-overview.png` | `core-concepts/how-fabro-works.mdx` |
| `workflows-list.png` | `core-concepts/workflows.mdx` |
| `workflow-detail.png` | `core-concepts/workflows.mdx` |
| `workflow-diagram.png` | `core-concepts/workflows.mdx` |
| `workflow-runs.png` | `core-concepts/workflows.mdx` |
| `run-stages.png` | `execution/observability.mdx` |
| `run-usage.png` | `execution/observability.mdx` |
| `retros-list.png` | `execution/retros.mdx` |
| `run-retro.png` | `execution/retros.mdx` |
| `run-files-changed.png` | `human-tools/steering.mdx` |
## Cleanup
```bash
docker compose -f docker/docker-compose.yaml down
```
If you also started the docs server:
```bash
docker stop mintlify-dev
```

View file

@ -8,7 +8,7 @@ digraph PlanImplement {
plan [label="Plan", prompt="Analyze the goal and codebase. Write a clear, step-by-step implementation plan to a Markdown file called plan.md. Include what files will change and why.", reasoning_effort="high"]
approve [shape=hexagon, label="Approve Plan"]
implement [label="Implement", prompt="Read plan.md and implement every step. Make all the code changes described in the plan."]
simplify [label="Simplify", prompt="@docs-internal/prompts/simplify.md"]
simplify [label="Simplify", prompt="@files-internal/prompts/simplify.md"]
start -> plan -> approve
approve -> implement [label="[A] Approve"]