diff --git a/AGENTS.md b/AGENTS.md index 3a80da6db..2686dd873 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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 diff --git a/arc/workflows/factory/README.md b/arc/workflows/factory/README.md index a4f1f0562..2be4fd769 100644 --- a/arc/workflows/factory/README.md +++ b/arc/workflows/factory/README.md @@ -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 diff --git a/arc/workflows/factory/prompts/architect/blueprint.md b/arc/workflows/factory/prompts/architect/blueprint.md index 72add3add..d54d17644 100644 --- a/arc/workflows/factory/prompts/architect/blueprint.md +++ b/arc/workflows/factory/prompts/architect/blueprint.md @@ -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 diff --git a/arc/workflows/factory/prompts/architect/diagram.md b/arc/workflows/factory/prompts/architect/diagram.md index ce666c9d5..1841aca8b 100644 --- a/arc/workflows/factory/prompts/architect/diagram.md +++ b/arc/workflows/factory/prompts/architect/diagram.md @@ -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 diff --git a/arc/workflows/factory/prompts/expand/execute.md b/arc/workflows/factory/prompts/expand/execute.md index e3ed3143c..50296293c 100644 --- a/arc/workflows/factory/prompts/expand/execute.md +++ b/arc/workflows/factory/prompts/expand/execute.md @@ -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. diff --git a/arc/workflows/factory/prompts/expand/propose.md b/arc/workflows/factory/prompts/expand/propose.md index 6bda1088d..faf98dc8b 100644 --- a/arc/workflows/factory/prompts/expand/propose.md +++ b/arc/workflows/factory/prompts/expand/propose.md @@ -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 diff --git a/arc/workflows/factory/prompts/implement/plan.md b/arc/workflows/factory/prompts/implement/plan.md index a67b3fef1..cf99bd8d1 100644 --- a/arc/workflows/factory/prompts/implement/plan.md +++ b/arc/workflows/factory/prompts/implement/plan.md @@ -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 diff --git a/arc/workflows/factory/prompts/implement/review.md b/arc/workflows/factory/prompts/implement/review.md index 750960791..ef2e2407d 100644 --- a/arc/workflows/factory/prompts/implement/review.md +++ b/arc/workflows/factory/prompts/implement/review.md @@ -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: diff --git a/arc/workflows/factory/prompts/implement/validate.md b/arc/workflows/factory/prompts/implement/validate.md index 8c69faaee..e266a4197 100644 --- a/arc/workflows/factory/prompts/implement/validate.md +++ b/arc/workflows/factory/prompts/implement/validate.md @@ -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]" diff --git a/arc/workflows/factory/prompts/seed/draft.md b/arc/workflows/factory/prompts/seed/draft.md index d3e41260f..c5d996c89 100644 --- a/arc/workflows/factory/prompts/seed/draft.md +++ b/arc/workflows/factory/prompts/seed/draft.md @@ -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 diff --git a/arc/workflows/factory/prompts/specify/clarify.md b/arc/workflows/factory/prompts/specify/clarify.md index e70966bfd..73c2f0092 100644 --- a/arc/workflows/factory/prompts/specify/clarify.md +++ b/arc/workflows/factory/prompts/specify/clarify.md @@ -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 diff --git a/arc/workflows/factory/prompts/specify/require.md b/arc/workflows/factory/prompts/specify/require.md index 18ebad146..c0954671a 100644 --- a/arc/workflows/factory/prompts/specify/require.md +++ b/arc/workflows/factory/prompts/specify/require.md @@ -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 diff --git a/arc/workflows/factory/prompts/sync/detect.md b/arc/workflows/factory/prompts/sync/detect.md index 38f11bd9f..8018af7ca 100644 --- a/arc/workflows/factory/prompts/sync/detect.md +++ b/arc/workflows/factory/prompts/sync/detect.md @@ -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: diff --git a/scripts/release.sh b/bin/dev/release.sh similarity index 100% rename from scripts/release.sh rename to bin/dev/release.sh diff --git a/docs/core-concepts/models.mdx b/docs/core-concepts/models.mdx index 99ee42321..ac4d28074 100644 --- a/docs/core-concepts/models.mdx +++ b/docs/core-concepts/models.mdx @@ -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. diff --git a/docs/reference/dot-language.mdx b/docs/reference/dot-language.mdx index 215497236..f859d053e 100644 --- a/docs/reference/dot-language.mdx +++ b/docs/reference/dot-language.mdx @@ -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): diff --git a/docs/tutorials/branch-loop.mdx b/docs/tutorials/branch-loop.mdx index ad50531e7..a02b93079 100644 --- a/docs/tutorials/branch-loop.mdx +++ b/docs/tutorials/branch-loop.mdx @@ -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 diff --git a/docs/tutorials/ensemble.mdx b/docs/tutorials/ensemble.mdx index e64208437..54b45a50b 100644 --- a/docs/tutorials/ensemble.mdx +++ b/docs/tutorials/ensemble.mdx @@ -52,7 +52,7 @@ digraph Ensemble { ``` ```bash -fabro run docs-internal/demo/11-ensemble.dot +fabro run files-internal/demo/11-ensemble.dot ``` diff --git a/docs/tutorials/hello-world.mdx b/docs/tutorials/hello-world.mdx index aba0d3623..cbae7678e 100644 --- a/docs/tutorials/hello-world.mdx +++ b/docs/tutorials/hello-world.mdx @@ -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 diff --git a/docs/tutorials/multi-model.mdx b/docs/tutorials/multi-model.mdx index 8e8503006..652fb0b8f 100644 --- a/docs/tutorials/multi-model.mdx +++ b/docs/tutorials/multi-model.mdx @@ -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 diff --git a/docs/tutorials/parallel-review.mdx b/docs/tutorials/parallel-review.mdx index c5efe0909..0d718eb0e 100644 --- a/docs/tutorials/parallel-review.mdx +++ b/docs/tutorials/parallel-review.mdx @@ -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 diff --git a/docs/tutorials/plan-implement.mdx b/docs/tutorials/plan-implement.mdx index b4ff0c393..053946966 100644 --- a/docs/tutorials/plan-implement.mdx +++ b/docs/tutorials/plan-implement.mdx @@ -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. diff --git a/docs/tutorials/sub-workflow.mdx b/docs/tutorials/sub-workflow.mdx index e78429d51..58c7e7970 100644 --- a/docs/tutorials/sub-workflow.mdx +++ b/docs/tutorials/sub-workflow.mdx @@ -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 diff --git a/files-internal/assets/brand/logo/logotype-black.svg b/files-internal/assets/brand/logo/logotype-black.svg new file mode 100644 index 000000000..93803a35a --- /dev/null +++ b/files-internal/assets/brand/logo/logotype-black.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/files-internal/assets/brand/logo/logotype.svg b/files-internal/assets/brand/logo/logotype.svg new file mode 100644 index 000000000..7ea74a6a9 --- /dev/null +++ b/files-internal/assets/brand/logo/logotype.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/files-internal/assets/brand/logo/symbol-black.svg b/files-internal/assets/brand/logo/symbol-black.svg new file mode 100644 index 000000000..9ac4ba359 --- /dev/null +++ b/files-internal/assets/brand/logo/symbol-black.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/files-internal/assets/brand/logo/symbol.svg b/files-internal/assets/brand/logo/symbol.svg new file mode 100644 index 000000000..ea5e66e90 --- /dev/null +++ b/files-internal/assets/brand/logo/symbol.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/files-internal/assets/brand/palette-lockups.svg b/files-internal/assets/brand/palette-lockups.svg new file mode 100644 index 000000000..cf56ebe18 --- /dev/null +++ b/files-internal/assets/brand/palette-lockups.svg @@ -0,0 +1,2 @@ + +Ice 100 on Navy 950Teal 500 on Navy 950Navy 950 on Ice 50Navy 800 on Ice 100 \ No newline at end of file diff --git a/files-internal/assets/brand/palette-mockup-icons.svg b/files-internal/assets/brand/palette-mockup-icons.svg new file mode 100644 index 000000000..3e050ddb9 --- /dev/null +++ b/files-internal/assets/brand/palette-mockup-icons.svg @@ -0,0 +1,2 @@ + +ABCD \ No newline at end of file diff --git a/files-internal/assets/brand/palette-mockup.svg b/files-internal/assets/brand/palette-mockup.svg new file mode 100644 index 000000000..75fb3829e --- /dev/null +++ b/files-internal/assets/brand/palette-mockup.svg @@ -0,0 +1,2 @@ + +ABCD \ No newline at end of file diff --git a/files-internal/assets/brand/palette.png b/files-internal/assets/brand/palette.png new file mode 100644 index 000000000..4e65164b5 Binary files /dev/null and b/files-internal/assets/brand/palette.png differ diff --git a/files-internal/assets/brand/palette.svg b/files-internal/assets/brand/palette.svg new file mode 100644 index 000000000..b344617b6 --- /dev/null +++ b/files-internal/assets/brand/palette.svg @@ -0,0 +1,2 @@ + +PrimaryTeal 300#B5DDEFTeal 500#67B2D7Teal 700#357F9ENeutrals DarkNavy 950#0F1729Navy 800#252C3DNavy 600#4B5768Neutrals LightIce 50#F7F9FBIce 100#E8EDF3Ice 300#A8B5C5AccentMint#5AC8A8Amber#F0A45BCoral#E86B6B \ No newline at end of file diff --git a/files-internal/demo/01-hello.dot b/files-internal/demo/01-hello.dot new file mode 100644 index 000000000..2d245918e --- /dev/null +++ b/files-internal/demo/01-hello.dot @@ -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 +} diff --git a/files-internal/demo/01-hello.png b/files-internal/demo/01-hello.png new file mode 100644 index 000000000..aaf188e4b Binary files /dev/null and b/files-internal/demo/01-hello.png differ diff --git a/files-internal/demo/01-hello.svg b/files-internal/demo/01-hello.svg new file mode 100644 index 000000000..3c1c2e34f --- /dev/null +++ b/files-internal/demo/01-hello.svg @@ -0,0 +1,51 @@ + + + + + + +Hello + + + +start + + + + + +Start + + + +compose + +Compose + + + +start->compose + + + + + +exit + + + + + +Exit + + + +compose->exit + + + + + diff --git a/files-internal/demo/02-tool-use.dot b/files-internal/demo/02-tool-use.dot new file mode 100644 index 000000000..a199ba49d --- /dev/null +++ b/files-internal/demo/02-tool-use.dot @@ -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 +} diff --git a/files-internal/demo/02-tool-use.png b/files-internal/demo/02-tool-use.png new file mode 100644 index 000000000..5728fce6c Binary files /dev/null and b/files-internal/demo/02-tool-use.png differ diff --git a/files-internal/demo/02-tool-use.svg b/files-internal/demo/02-tool-use.svg new file mode 100644 index 000000000..ee12bfb92 --- /dev/null +++ b/files-internal/demo/02-tool-use.svg @@ -0,0 +1,51 @@ + + + + + + +ToolUse + + + +start + + + + + +Start + + + +explore + +Explore + + + +start->explore + + + + + +exit + + + + + +Exit + + + +explore->exit + + + + + diff --git a/files-internal/demo/03-subagent.dot b/files-internal/demo/03-subagent.dot new file mode 100644 index 000000000..385fd9102 --- /dev/null +++ b/files-internal/demo/03-subagent.dot @@ -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 +} diff --git a/files-internal/demo/03-subagent.png b/files-internal/demo/03-subagent.png new file mode 100644 index 000000000..b784640b8 Binary files /dev/null and b/files-internal/demo/03-subagent.png differ diff --git a/files-internal/demo/03-subagent.svg b/files-internal/demo/03-subagent.svg new file mode 100644 index 000000000..d3be0d081 --- /dev/null +++ b/files-internal/demo/03-subagent.svg @@ -0,0 +1,51 @@ + + + + + + +SubAgent + + + +start + + + + + +Start + + + +research + +Research + + + +start->research + + + + + +exit + + + + + +Exit + + + +research->exit + + + + + diff --git a/files-internal/demo/04-pipeline.dot b/files-internal/demo/04-pipeline.dot new file mode 100644 index 000000000..b8bab95a4 --- /dev/null +++ b/files-internal/demo/04-pipeline.dot @@ -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 +} diff --git a/files-internal/demo/04-pipeline.png b/files-internal/demo/04-pipeline.png new file mode 100644 index 000000000..bac00122e Binary files /dev/null and b/files-internal/demo/04-pipeline.png differ diff --git a/files-internal/demo/04-pipeline.svg b/files-internal/demo/04-pipeline.svg new file mode 100644 index 000000000..4ead71072 --- /dev/null +++ b/files-internal/demo/04-pipeline.svg @@ -0,0 +1,75 @@ + + + + + + +Pipeline + + + +start + + + + + +Start + + + +scan + +Scan Files + + + +start->scan + + + + + +exit + + + + + +Exit + + + +analyze + +Analyze + + + +scan->analyze + + + + + +suggest + +Suggest + + + +analyze->suggest + + + + + +suggest->exit + + + + + diff --git a/files-internal/demo/05-branch-loop.dot b/files-internal/demo/05-branch-loop.dot new file mode 100644 index 000000000..b50eb1a8c --- /dev/null +++ b/files-internal/demo/05-branch-loop.dot @@ -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"] +} diff --git a/files-internal/demo/05-branch-loop.png b/files-internal/demo/05-branch-loop.png new file mode 100644 index 000000000..b2162fed3 Binary files /dev/null and b/files-internal/demo/05-branch-loop.png differ diff --git a/files-internal/demo/05-branch-loop.svg b/files-internal/demo/05-branch-loop.svg new file mode 100644 index 000000000..5bd9ca332 --- /dev/null +++ b/files-internal/demo/05-branch-loop.svg @@ -0,0 +1,95 @@ + + + + + + +BranchLoop + + + +start + + + + + +Start + + + +plan + +Plan + + + +start->plan + + + + + +exit + + + + + +Exit + + + +implement + +Implement + + + +plan->implement + + + + + +validate + +Validate + + + +implement->validate + + + + + +gate + +Tests passing? + + + +validate->gate + + + + + +gate->exit + + +Pass + + + +gate->implement + + +Fix + + + diff --git a/files-internal/demo/06-parallel.dot b/files-internal/demo/06-parallel.dot new file mode 100644 index 000000000..6ef15e16f --- /dev/null +++ b/files-internal/demo/06-parallel.dot @@ -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 +} diff --git a/files-internal/demo/06-parallel.png b/files-internal/demo/06-parallel.png new file mode 100644 index 000000000..790106bda Binary files /dev/null and b/files-internal/demo/06-parallel.png differ diff --git a/files-internal/demo/06-parallel.svg b/files-internal/demo/06-parallel.svg new file mode 100644 index 000000000..86cc01776 --- /dev/null +++ b/files-internal/demo/06-parallel.svg @@ -0,0 +1,127 @@ + + + + + + +Parallel + + + +start + + + + + +Start + + + +fork + + + +Fork Analysis + + + +start->fork + + + + + +exit + + + + + +Exit + + + +security + +Security Audit + + + +fork->security + + + + + +architecture + +Architecture Review + + + +fork->architecture + + + + + +quality + +Code Quality + + + +fork->quality + + + + + +merge + + + +Merge Findings + + + +security->merge + + + + + +architecture->merge + + + + + +quality->merge + + + + + +report + +Final Report + + + +merge->report + + + + + +report->exit + + + + + diff --git a/files-internal/demo/07-human-gate.dot b/files-internal/demo/07-human-gate.dot new file mode 100644 index 000000000..9b2082a2e --- /dev/null +++ b/files-internal/demo/07-human-gate.dot @@ -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 +} diff --git a/files-internal/demo/07-human-gate.png b/files-internal/demo/07-human-gate.png new file mode 100644 index 000000000..6de549d9f Binary files /dev/null and b/files-internal/demo/07-human-gate.png differ diff --git a/files-internal/demo/07-human-gate.svg b/files-internal/demo/07-human-gate.svg new file mode 100644 index 000000000..755ee065e --- /dev/null +++ b/files-internal/demo/07-human-gate.svg @@ -0,0 +1,95 @@ + + + + + + +HumanGate + + + +start + + + + + +Start + + + +draft + +Draft Proposal + + + +start->draft + + + + + +exit + + + + + +Exit + + + +approve + +Approve Changes? + + + +draft->approve + + + + + +apply + +Apply Changes + + + +approve->apply + + +[A] Approve + + + +skip + +Skip + + + +approve->skip + + +[S] Skip + + + +apply->exit + + + + + +skip->exit + + + + + diff --git a/files-internal/demo/08-multi-model.dot b/files-internal/demo/08-multi-model.dot new file mode 100644 index 000000000..c96c89394 --- /dev/null +++ b/files-internal/demo/08-multi-model.dot @@ -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 +} diff --git a/files-internal/demo/08-multi-model.png b/files-internal/demo/08-multi-model.png new file mode 100644 index 000000000..8f4baa77a Binary files /dev/null and b/files-internal/demo/08-multi-model.png differ diff --git a/files-internal/demo/08-multi-model.svg b/files-internal/demo/08-multi-model.svg new file mode 100644 index 000000000..752d036a7 --- /dev/null +++ b/files-internal/demo/08-multi-model.svg @@ -0,0 +1,87 @@ + + + + + + +MultiModel + + + +start + + + + + +Start + + + +spec + +Write Spec + + + +start->spec + + + + + +exit + + + + + +Exit + + + +implement + +Implement + + + +spec->implement + + + + + +test + +Write Tests + + + +implement->test + + + + + +review + +Code Review + + + +test->review + + + + + +review->exit + + + + + diff --git a/files-internal/demo/09-simplify.dot b/files-internal/demo/09-simplify.dot new file mode 100644 index 000000000..ee355c522 --- /dev/null +++ b/files-internal/demo/09-simplify.dot @@ -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 +} diff --git a/files-internal/demo/09-simplify.png b/files-internal/demo/09-simplify.png new file mode 100644 index 000000000..23ec1f5e5 Binary files /dev/null and b/files-internal/demo/09-simplify.png differ diff --git a/files-internal/demo/09-simplify.svg b/files-internal/demo/09-simplify.svg new file mode 100644 index 000000000..b417960df --- /dev/null +++ b/files-internal/demo/09-simplify.svg @@ -0,0 +1,51 @@ + + + + + + +Simplify + + + +start + + + + + +Start + + + +simplify + +Simplify + + + +start->simplify + + + + + +exit + + + + + +Exit + + + +simplify->exit + + + + + diff --git a/files-internal/demo/10-plan-implement.dot b/files-internal/demo/10-plan-implement.dot new file mode 100644 index 000000000..3a396bd47 --- /dev/null +++ b/files-internal/demo/10-plan-implement.dot @@ -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 +} diff --git a/files-internal/demo/10-plan-implement.png b/files-internal/demo/10-plan-implement.png new file mode 100644 index 000000000..3ac7d37f9 Binary files /dev/null and b/files-internal/demo/10-plan-implement.png differ diff --git a/files-internal/demo/10-plan-implement.svg b/files-internal/demo/10-plan-implement.svg new file mode 100644 index 000000000..653c7f648 --- /dev/null +++ b/files-internal/demo/10-plan-implement.svg @@ -0,0 +1,95 @@ + + + + + + +PlanImplement + + + +start + + + + + +Start + + + +plan + +Plan + + + +start->plan + + + + + +exit + + + + + +Exit + + + +approve + +Approve Plan + + + +plan->approve + + + + + +approve->plan + + +[R] Revise + + + +implement + +Implement + + + +approve->implement + + +[A] Approve + + + +simplify + +Simplify + + + +implement->simplify + + + + + +simplify->exit + + + + + diff --git a/files-internal/demo/11-ensemble.dot b/files-internal/demo/11-ensemble.dot new file mode 100644 index 000000000..efd9ab99f --- /dev/null +++ b/files-internal/demo/11-ensemble.dot @@ -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 +} diff --git a/files-internal/demo/11-ensemble.png b/files-internal/demo/11-ensemble.png new file mode 100644 index 000000000..4b579ffe9 Binary files /dev/null and b/files-internal/demo/11-ensemble.png differ diff --git a/files-internal/demo/11-ensemble.svg b/files-internal/demo/11-ensemble.svg new file mode 100644 index 000000000..ee2d58560 --- /dev/null +++ b/files-internal/demo/11-ensemble.svg @@ -0,0 +1,145 @@ + + + + + + +Ensemble + + + +start + + + + + +Start + + + +fork + + + +Fan Out + + + +start->fork + + + + + +exit + + + + + +Exit + + + +opus + +Opus + + + +fork->opus + + + + + +gemini + +Gemini + + + +fork->gemini + + + + + +codex + +Codex + + + +fork->codex + + + + + +mercury + +Mercury + + + +fork->mercury + + + + + +merge + + + +Merge + + + +opus->merge + + + + + +gemini->merge + + + + + +codex->merge + + + + + +mercury->merge + + + + + +synth + +Synthesize + + + +merge->synth + + + + + +synth->exit + + + + + diff --git a/files-internal/demo/12-web-fetch.dot b/files-internal/demo/12-web-fetch.dot new file mode 100644 index 000000000..b3fa4b800 --- /dev/null +++ b/files-internal/demo/12-web-fetch.dot @@ -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 +} diff --git a/files-internal/demo/13-web-search.dot b/files-internal/demo/13-web-search.dot new file mode 100644 index 000000000..c850e5d5f --- /dev/null +++ b/files-internal/demo/13-web-search.dot @@ -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 +} diff --git a/files-internal/demo/14-search-imagegen.dot b/files-internal/demo/14-search-imagegen.dot new file mode 100644 index 000000000..93faeaada --- /dev/null +++ b/files-internal/demo/14-search-imagegen.dot @@ -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 '' output/landmark.png\nThe imagegen command takes a text prompt and an output path."] + + start -> research -> imagine -> exit +} diff --git a/files-internal/demo/14-search-imagegen.toml b/files-internal/demo/14-search-imagegen.toml new file mode 100644 index 000000000..9ed6a11d2 --- /dev/null +++ b/files-internal/demo/14-search-imagegen.toml @@ -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/**"] diff --git a/files-internal/events-strategy.md b/files-internal/events-strategy.md new file mode 100644 index 000000000..6ff2fdfba --- /dev/null +++ b/files-internal/events-strategy.md @@ -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 | diff --git a/files-internal/logging-strategy.md b/files-internal/logging-strategy.md new file mode 100644 index 000000000..0ec690d99 --- /dev/null +++ b/files-internal/logging-strategy.md @@ -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. diff --git a/files-internal/updating-web-screenshots.md b/files-internal/updating-web-screenshots.md new file mode 100644 index 000000000..9b3e9dc80 --- /dev/null +++ b/files-internal/updating-web-screenshots.md @@ -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 `` 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 +``` diff --git a/test/docs/tutorials/plan-implement/plan-implement.dot b/test/docs/tutorials/plan-implement/plan-implement.dot index 690e9d9c8..3a396bd47 100644 --- a/test/docs/tutorials/plan-implement/plan-implement.dot +++ b/test/docs/tutorials/plan-implement/plan-implement.dot @@ -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"]