mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-08-28 05:27:41 +00:00
Update docs tutorials, examples, and add sub-workflow tutorial
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
72c1e0759f
commit
131b71aea9
11 changed files with 409 additions and 30 deletions
|
|
@ -7,7 +7,7 @@ digraph BranchLoop {
|
|||
|
||||
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="python -m pytest test_fizzbuzz.py -v 2>&1 || true"]
|
||||
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
|
||||
|
|
|
|||
|
|
@ -78,6 +78,7 @@
|
|||
"pages": [
|
||||
"reference/dot-language",
|
||||
"reference/cli",
|
||||
"reference/cli-configuration",
|
||||
"reference/architecture",
|
||||
"administration/advanced-setup",
|
||||
"administration/security"
|
||||
|
|
@ -98,7 +99,8 @@
|
|||
"tutorials/branch-loop",
|
||||
"tutorials/parallel-review",
|
||||
"tutorials/multi-model",
|
||||
"tutorials/ensemble"
|
||||
"tutorials/ensemble",
|
||||
"tutorials/sub-workflow"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -167,7 +169,7 @@
|
|||
"pages": [
|
||||
"GET /runs/{id}/compare",
|
||||
"GET /runs/{id}/usage",
|
||||
"GET /runs/{id}/verifications",
|
||||
"GET /runs/{id}/verification",
|
||||
{
|
||||
"group": "Run Internals",
|
||||
"icon": "microchip",
|
||||
|
|
@ -192,11 +194,13 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"group": "Verifications",
|
||||
"group": "Verification",
|
||||
"icon": "shield-check",
|
||||
"pages": [
|
||||
"GET /verifications",
|
||||
"GET /verifications/{slug}"
|
||||
"GET /verification/criteria",
|
||||
"GET /verification/criteria/{id}",
|
||||
"GET /verification/controls",
|
||||
"GET /verification/controls/{id}"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -22,9 +22,9 @@ digraph BuildSolitaire {
|
|||
retry_target="impl_setup",
|
||||
fallback_retry_target="impl_game_logic",
|
||||
model_stylesheet="
|
||||
* { llm_model: claude-sonnet-4-5; llm_provider: anthropic; }
|
||||
.hard { llm_model: claude-opus-4-6; llm_provider: anthropic; }
|
||||
.verify { llm_model: claude-haiku-4-5; llm_provider: anthropic; }
|
||||
* { llm_model: claude-sonnet; llm_provider: anthropic; }
|
||||
.hard { llm_model: claude-opus; llm_provider: anthropic; }
|
||||
.verify { llm_model: claude-haiku; llm_provider: anthropic; }
|
||||
"
|
||||
]
|
||||
|
||||
|
|
|
|||
90
docs/images/tutorial-sub-workflow-child.svg
Normal file
90
docs/images/tutorial-sub-workflow-child.svg
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
<?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: ImplementAndTest Pages: 1 -->
|
||||
<svg width="642pt" height="79pt"
|
||||
viewBox="0.00 0.00 642.00 79.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<style>
|
||||
@media (prefers-color-scheme: dark) {
|
||||
[fill="#1a1a1a"] { fill: #e5e5e5; }
|
||||
[fill="#666666"] { fill: #aaaaaa; }
|
||||
[stroke="#666666"] { stroke: #aaaaaa; }
|
||||
[stroke="#999999"] { stroke: #777777; }
|
||||
}
|
||||
</style>
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 75.12)">
|
||||
<title>ImplementAndTest</title>
|
||||
<!-- start -->
|
||||
<g id="node1" class="node">
|
||||
<title>start</title>
|
||||
<polygon fill="none" stroke="#357f9e" points="35.26,-36.12 0,-18.13 35.26,-0.13 70.52,-18.12 35.26,-36.12"/>
|
||||
<polyline fill="none" stroke="#357f9e" points="10.69,-23.58 10.69,-12.67"/>
|
||||
<polyline fill="none" stroke="#357f9e" points="24.57,-5.58 45.95,-5.58"/>
|
||||
<polyline fill="none" stroke="#357f9e" points="59.83,-12.67 59.83,-23.58"/>
|
||||
<polyline fill="none" stroke="#357f9e" points="45.95,-30.67 24.57,-30.67"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="35.26" y="-13.1" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#1a1a1a">Start</text>
|
||||
</g>
|
||||
<!-- implement -->
|
||||
<g id="node3" class="node">
|
||||
<title>implement</title>
|
||||
<ellipse fill="none" stroke="#357f9e" cx="151.27" cy="-18.12" rx="43.75" ry="18"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="151.27" y="-13.1" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#1a1a1a">Implement</text>
|
||||
</g>
|
||||
<!-- start->implement -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>start->implement</title>
|
||||
<path fill="none" stroke="#666666" d="M71.4,-18.12C79.22,-18.12 87.7,-18.12 96.09,-18.12"/>
|
||||
<polygon fill="#666666" stroke="#666666" points="95.91,-21.63 105.91,-18.13 95.91,-14.63 95.91,-21.63"/>
|
||||
</g>
|
||||
<!-- exit -->
|
||||
<g id="node2" class="node">
|
||||
<title>exit</title>
|
||||
<polygon fill="none" stroke="#357f9e" points="634.45,-36.25 598.2,-36.25 598.2,0 634.45,0 634.45,-36.25"/>
|
||||
<polyline fill="none" stroke="#357f9e" points="610.2,-36.25 598.2,-24.25"/>
|
||||
<polyline fill="none" stroke="#357f9e" points="598.2,-12 610.2,0"/>
|
||||
<polyline fill="none" stroke="#357f9e" points="622.45,0 634.45,-12"/>
|
||||
<polyline fill="none" stroke="#357f9e" points="634.45,-24.25 622.45,-36.25"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="616.33" y="-13.1" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#1a1a1a">Exit</text>
|
||||
</g>
|
||||
<!-- validate -->
|
||||
<g id="node4" class="node">
|
||||
<title>validate</title>
|
||||
<polygon fill="none" stroke="#357f9e" points="337.43,-71.12 253.59,-71.12 232.02,-35.13 315.86,-35.12 337.43,-71.12"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="284.72" y="-48.1" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#1a1a1a">Validate</text>
|
||||
</g>
|
||||
<!-- implement->validate -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>implement->validate</title>
|
||||
<path fill="none" stroke="#666666" d="M188.54,-27.78C199.48,-30.69 211.79,-33.97 223.84,-37.18"/>
|
||||
<polygon fill="#666666" stroke="#666666" points="222.75,-40.51 233.32,-39.7 224.55,-33.75 222.75,-40.51"/>
|
||||
</g>
|
||||
<!-- gate -->
|
||||
<g id="node5" class="node">
|
||||
<title>gate</title>
|
||||
<polygon fill="none" stroke="#357f9e" points="457.07,-36.12 374.43,-18.13 457.07,-0.13 539.7,-18.12 457.07,-36.12"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="457.07" y="-13.1" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#1a1a1a">Tests passing?</text>
|
||||
</g>
|
||||
<!-- validate->gate -->
|
||||
<g id="edge3" class="edge">
|
||||
<title>validate->gate</title>
|
||||
<path fill="none" stroke="#666666" d="M322.27,-45.62C345.43,-40.86 375.81,-34.62 401.76,-29.28"/>
|
||||
<polygon fill="#666666" stroke="#666666" points="402.38,-32.73 411.48,-27.29 400.98,-25.87 402.38,-32.73"/>
|
||||
</g>
|
||||
<!-- gate->exit -->
|
||||
<g id="edge4" class="edge">
|
||||
<title>gate->exit</title>
|
||||
<path fill="none" stroke="#666666" d="M541.76,-18.12C557.88,-18.12 573.72,-18.12 586.54,-18.12"/>
|
||||
<polygon fill="#666666" stroke="#666666" points="586.31,-21.63 596.31,-18.13 586.31,-14.63 586.31,-21.63"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="568.95" y="-19.88" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#666666">Pass</text>
|
||||
</g>
|
||||
<!-- gate->implement -->
|
||||
<g id="edge5" class="edge">
|
||||
<title>gate->implement</title>
|
||||
<path fill="none" stroke="#666666" d="M382.91,-15.78C339.13,-14.69 282.39,-13.85 232.02,-14.88 223.73,-15.04 214.97,-15.31 206.39,-15.62"/>
|
||||
<polygon fill="#666666" stroke="#666666" points="206.26,-12.12 196.4,-16.01 206.53,-19.12 206.26,-12.12"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="284.72" y="-16.62" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#666666">Fix</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.8 KiB |
82
docs/images/tutorial-sub-workflow.svg
Normal file
82
docs/images/tutorial-sub-workflow.svg
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
<?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: SubWorkflow Pages: 1 -->
|
||||
<svg width="545pt" height="44pt"
|
||||
viewBox="0.00 0.00 545.00 44.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<style>
|
||||
@media (prefers-color-scheme: dark) {
|
||||
[fill="#1a1a1a"] { fill: #e5e5e5; }
|
||||
[fill="#666666"] { fill: #aaaaaa; }
|
||||
[stroke="#666666"] { stroke: #aaaaaa; }
|
||||
[stroke="#999999"] { stroke: #777777; }
|
||||
}
|
||||
</style>
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 40.25)">
|
||||
<title>SubWorkflow</title>
|
||||
<!-- start -->
|
||||
<g id="node1" class="node">
|
||||
<title>start</title>
|
||||
<polygon fill="none" stroke="#357f9e" points="35.26,-36.12 0,-18.13 35.26,-0.13 70.52,-18.12 35.26,-36.12"/>
|
||||
<polyline fill="none" stroke="#357f9e" points="10.69,-23.58 10.69,-12.67"/>
|
||||
<polyline fill="none" stroke="#357f9e" points="24.57,-5.58 45.95,-5.58"/>
|
||||
<polyline fill="none" stroke="#357f9e" points="59.83,-12.67 59.83,-23.58"/>
|
||||
<polyline fill="none" stroke="#357f9e" points="45.95,-30.67 24.57,-30.67"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="35.26" y="-13.1" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#1a1a1a">Start</text>
|
||||
</g>
|
||||
<!-- plan -->
|
||||
<g id="node3" class="node">
|
||||
<title>plan</title>
|
||||
<ellipse fill="none" stroke="#357f9e" cx="133.52" cy="-18.12" rx="27" ry="18"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="133.52" y="-13.1" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#1a1a1a">Plan</text>
|
||||
</g>
|
||||
<!-- start->plan -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>start->plan</title>
|
||||
<path fill="none" stroke="#666666" d="M71.56,-18.12C79.18,-18.12 87.27,-18.12 94.98,-18.12"/>
|
||||
<polygon fill="#666666" stroke="#666666" points="94.76,-21.63 104.76,-18.13 94.76,-14.63 94.76,-21.63"/>
|
||||
</g>
|
||||
<!-- exit -->
|
||||
<g id="node2" class="node">
|
||||
<title>exit</title>
|
||||
<polygon fill="none" stroke="#357f9e" points="536.52,-36.25 500.27,-36.25 500.27,0 536.52,0 536.52,-36.25"/>
|
||||
<polyline fill="none" stroke="#357f9e" points="512.27,-36.25 500.27,-24.25"/>
|
||||
<polyline fill="none" stroke="#357f9e" points="500.27,-12 512.27,0"/>
|
||||
<polyline fill="none" stroke="#357f9e" points="524.52,0 536.52,-12"/>
|
||||
<polyline fill="none" stroke="#357f9e" points="536.52,-24.25 524.52,-36.25"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="518.39" y="-13.1" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#1a1a1a">Exit</text>
|
||||
</g>
|
||||
<!-- impl -->
|
||||
<g id="node4" class="node">
|
||||
<title>impl</title>
|
||||
<polygon fill="none" stroke="#357f9e" points="359.13,-23.69 277.83,-36.12 196.52,-23.69 196.6,-3.56 359.05,-3.56 359.13,-23.69"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="277.83" y="-13.1" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#1a1a1a">Implement & Test</text>
|
||||
</g>
|
||||
<!-- plan->impl -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>plan->impl</title>
|
||||
<path fill="none" stroke="#666666" d="M160.95,-18.12C168.05,-18.12 176.15,-18.12 184.69,-18.12"/>
|
||||
<polygon fill="#666666" stroke="#666666" points="184.61,-21.63 194.61,-18.13 184.61,-14.63 184.61,-21.63"/>
|
||||
</g>
|
||||
<!-- review -->
|
||||
<g id="node5" class="node">
|
||||
<title>review</title>
|
||||
<ellipse fill="none" stroke="#357f9e" cx="429.7" cy="-18.12" rx="34.57" ry="18"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="429.7" y="-13.1" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#1a1a1a">Review</text>
|
||||
</g>
|
||||
<!-- impl->review -->
|
||||
<g id="edge3" class="edge">
|
||||
<title>impl->review</title>
|
||||
<path fill="none" stroke="#666666" d="M359.46,-18.12C367.65,-18.12 375.74,-18.12 383.35,-18.12"/>
|
||||
<polygon fill="#666666" stroke="#666666" points="383.34,-21.63 393.34,-18.13 383.34,-14.63 383.34,-21.63"/>
|
||||
</g>
|
||||
<!-- review->exit -->
|
||||
<g id="edge4" class="edge">
|
||||
<title>review->exit</title>
|
||||
<path fill="none" stroke="#666666" d="M464.47,-18.12C472.45,-18.12 480.9,-18.12 488.64,-18.12"/>
|
||||
<polygon fill="#666666" stroke="#666666" points="488.54,-21.63 498.54,-18.13 488.54,-14.63 488.54,-21.63"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.1 KiB |
69
docs/reference/cli-configuration.mdx
Normal file
69
docs/reference/cli-configuration.mdx
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
---
|
||||
title: "CLI Configuration"
|
||||
description: "Configure default settings for the Arc CLI with cli.toml"
|
||||
---
|
||||
|
||||
Arc loads CLI defaults from `~/.arc/cli.toml` so you don't have to pass common flags every time. The file is optional — if it doesn't exist, built-in defaults are used.
|
||||
|
||||
## File location
|
||||
|
||||
The default path is `~/.arc/cli.toml`. Arc silently skips loading if the file is missing.
|
||||
|
||||
## Precedence
|
||||
|
||||
CLI flags always take the highest priority:
|
||||
|
||||
1. **CLI flags** — always win
|
||||
2. **`cli.toml`** — used when no flag is provided
|
||||
3. **Built-in defaults** — used when neither flag nor config is set
|
||||
|
||||
## Full example
|
||||
|
||||
```toml
|
||||
[agent]
|
||||
provider = "anthropic"
|
||||
model = "claude-opus-4-6"
|
||||
permissions = "read-write"
|
||||
output_format = "text"
|
||||
|
||||
[llm]
|
||||
model = "claude-sonnet-4-5"
|
||||
```
|
||||
|
||||
All fields are optional. You can include just the sections and keys you want to override.
|
||||
|
||||
## `[agent]` section
|
||||
|
||||
Defaults for `arc agent` sessions.
|
||||
|
||||
| Key | Description | Values | Default |
|
||||
|---|---|---|---|
|
||||
| `provider` | LLM provider | `"anthropic"`, `"openai"`, `"gemini"`, etc. | `"anthropic"` |
|
||||
| `model` | Model name | Any model ID from `arc models list` | Per provider |
|
||||
| `permissions` | Tool permission level | `"read-only"`, `"read-write"`, `"full"` | `"read-write"` |
|
||||
| `output_format` | Output format | `"text"`, `"json"` | `"text"` |
|
||||
|
||||
### Permission levels
|
||||
|
||||
- **`read-only`** — auto-approves read tools (`read_file`, `grep`, `glob`, `list_dir`) and subagent tools
|
||||
- **`read-write`** — adds write tools (`write_file`, `edit_file`, `apply_patch`)
|
||||
- **`full`** — allows all tools including shell commands
|
||||
|
||||
Tools outside the permission level are interactively prompted (if a TTY is present) or denied (with `--auto-approve`).
|
||||
|
||||
### Output formats
|
||||
|
||||
- **`text`** — human-readable terminal output
|
||||
- **`json`** — NDJSON event stream
|
||||
|
||||
## `[llm]` section
|
||||
|
||||
Defaults for `arc llm prompt` and `arc llm chat`.
|
||||
|
||||
| Key | Description | Values | Default |
|
||||
|---|---|---|---|
|
||||
| `model` | Model name | Any model ID from `arc models list` | Per provider |
|
||||
|
||||
<Note>
|
||||
The `[llm]` section only sets the default model. Use `[agent]` to configure provider, permissions, and output format for `arc agent`.
|
||||
</Note>
|
||||
|
|
@ -21,7 +21,7 @@ digraph BranchLoop {
|
|||
|
||||
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="python -m pytest test_fizzbuzz.py -v 2>&1 || true"]
|
||||
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
|
||||
|
|
@ -39,7 +39,7 @@ arc run start demo/05-branch-loop.dot
|
|||
The `validate` node has `shape=parallelogram`, making it a **command node**. It runs a shell script and captures the output:
|
||||
|
||||
```dot
|
||||
validate [label="Validate", shape=parallelogram, script="python -m pytest test_fizzbuzz.py -v 2>&1 || true"]
|
||||
validate [label="Validate", shape=parallelogram, script="python3 -m pytest test_fizzbuzz.py -v 2>&1 || true"]
|
||||
```
|
||||
|
||||
The `|| true` ensures the command always exits successfully — this way the node itself doesn't fail even when tests fail. The test output is captured as `command.output` in the [run context](/execution/context) for downstream nodes to use.
|
||||
|
|
|
|||
|
|
@ -111,19 +111,8 @@ The tradeoff is cost and latency — you're making 4x the LLM calls. Use single-
|
|||
- A **synthesis node** compares perspectives and produces a unified result
|
||||
- Combine parallel execution and model routing for diverse, independent analysis
|
||||
|
||||
## Further reading
|
||||
## Next
|
||||
|
||||
<Columns cols={2}>
|
||||
<Card title="Models" icon="microchip" href="/core-concepts/models">
|
||||
Available models, providers, and fallback configuration.
|
||||
</Card>
|
||||
<Card title="Stylesheets" icon="palette" href="/workflows/stylesheets">
|
||||
Full stylesheet syntax and specificity rules.
|
||||
</Card>
|
||||
<Card title="Nodes & Stages" icon="shapes" href="/workflows/stages-and-nodes">
|
||||
Complete reference for all node types.
|
||||
</Card>
|
||||
<Card title="Transitions" icon="route" href="/workflows/transitions">
|
||||
Edge conditions, routing directives, and tiebreaking.
|
||||
</Card>
|
||||
</Columns>
|
||||
<Card title="Sub-Workflows" icon="arrow-right" href="/tutorials/sub-workflow">
|
||||
Delegate to reusable child workflows with the supervisor pattern.
|
||||
</Card>
|
||||
|
|
|
|||
145
docs/tutorials/sub-workflow.mdx
Normal file
145
docs/tutorials/sub-workflow.mdx
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
---
|
||||
title: "Sub-Workflows"
|
||||
description: "Reusable child workflows, context flow, and the supervisor pattern"
|
||||
---
|
||||
|
||||
This tutorial uses a **sub-workflow node** to delegate part of a parent workflow to a separate, reusable child workflow. The parent plans a feature, hands off implementation to a child workflow that runs its own implement-test loop, then reviews the result.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Complete the [Branch & Loop](/tutorials/branch-loop) tutorial — the child workflow in this tutorial reuses that pattern.
|
||||
|
||||
## The child workflow
|
||||
|
||||
First, create a standalone implement-and-test workflow. This is a normal workflow that can run on its own or be invoked as a sub-workflow by a parent.
|
||||
|
||||
<Frame>
|
||||
<img src="/images/tutorial-sub-workflow-child.svg" alt="Child workflow: Start → Implement → Validate → Tests passing? → Pass to Exit or Fix back to Implement" />
|
||||
</Frame>
|
||||
|
||||
```dot title="implement-and-test.dot"
|
||||
digraph ImplementAndTest {
|
||||
graph [goal="Implement the plan and make tests pass"]
|
||||
rankdir=LR
|
||||
|
||||
start [shape=Mdiamond, label="Start"]
|
||||
exit [shape=Msquare, label="Exit"]
|
||||
|
||||
implement [label="Implement", prompt="Read plan.md and implement every step. Write the code and tests to disk."]
|
||||
validate [label="Validate", shape=parallelogram, script="python3 -m pytest -v 2>&1 || true"]
|
||||
gate [shape=diamond, label="Tests passing?"]
|
||||
|
||||
start -> implement -> validate -> gate
|
||||
gate -> exit [label="Pass", condition="outcome=success"]
|
||||
gate -> implement [label="Fix", max_visits=5]
|
||||
}
|
||||
```
|
||||
|
||||
This is the same implement-test-fix loop from the [Branch & Loop](/tutorials/branch-loop) tutorial, packaged as its own file.
|
||||
|
||||
## The parent workflow
|
||||
|
||||
Now create a parent workflow that delegates to the child:
|
||||
|
||||
<Frame>
|
||||
<img src="/images/tutorial-sub-workflow.svg" alt="Sub-Workflow: Start → Plan (agent) → Implement & Test (house) → Review (agent) → Exit" />
|
||||
</Frame>
|
||||
|
||||
```dot title="sub-workflow.dot"
|
||||
digraph SubWorkflow {
|
||||
graph [goal="Create a Python module (tempconv.py) that converts between Celsius, Fahrenheit, and Kelvin, with pytest tests"]
|
||||
rankdir=LR
|
||||
|
||||
start [shape=Mdiamond, label="Start"]
|
||||
exit [shape=Msquare, label="Exit"]
|
||||
|
||||
plan [label="Plan", prompt="Analyze the goal. List the functions needed, their signatures, and edge cases. Write the plan to plan.md."]
|
||||
impl [label="Implement & Test", shape=house, stack.child_dotfile="implement-and-test.dot", manager.max_cycles=50]
|
||||
review [label="Review", prompt="Read every file the child workflow created. Run the tests yourself with 'python3 -m pytest -v'. Verify the implementation matches plan.md and all tests pass. Write a short verdict to review.md."]
|
||||
|
||||
start -> plan -> impl -> review -> exit
|
||||
}
|
||||
```
|
||||
|
||||
```bash
|
||||
arc run start demo/12-sub-workflow.dot
|
||||
```
|
||||
|
||||
## The house node
|
||||
|
||||
The `impl` node has `shape=house`, which makes it a **sub-workflow node**. Instead of running an LLM or a script, it launches an entirely separate workflow engine to execute the child DOT file:
|
||||
|
||||
```dot
|
||||
impl [label="Implement & Test", shape=house, stack.child_dotfile="implement-and-test.dot", manager.max_cycles=50]
|
||||
```
|
||||
|
||||
The child workflow runs through its own start → implement → validate → gate → exit sequence. When it finishes, execution returns to the parent and continues to the `review` node.
|
||||
|
||||
### Sub-workflow attributes
|
||||
|
||||
| Attribute | Description |
|
||||
|---|---|
|
||||
| `stack.child_dotfile` | Path to the child DOT file (resolved relative to the parent) |
|
||||
| `stack.child_dot_source` | Inline child DOT source (alternative to `child_dotfile`) |
|
||||
| `manager.max_cycles` | Safety limit on poll cycles before the child is cancelled (default: 1000) |
|
||||
| `manager.poll_interval` | How often to check for completion or stop conditions (default: `45s`) |
|
||||
| `manager.stop_condition` | Condition expression that, when true, cancels the child early |
|
||||
|
||||
Use `stack.child_dotfile` when you want to reuse the child workflow across multiple parents. Use `stack.child_dot_source` for one-off child workflows that are specific to the parent.
|
||||
|
||||
## Context flow
|
||||
|
||||
Context flows bidirectionally between parent and child:
|
||||
|
||||
1. **Parent → child:** The child receives a clone of the parent's context. In this example, the `plan` node writes `plan.md` to disk and the child's `implement` node reads it.
|
||||
2. **Child → parent:** When the child finishes, any context values it added or changed are merged back into the parent. The `review` node sees the results of the child's work.
|
||||
|
||||
Only the _diff_ is merged — values the child didn't touch remain unchanged in the parent.
|
||||
|
||||
## Stop conditions
|
||||
|
||||
For long-running child workflows, you can set a stop condition that cancels the child early based on the parent's context:
|
||||
|
||||
```dot
|
||||
impl [shape=house,
|
||||
stack.child_dotfile="implement-and-test.dot",
|
||||
manager.stop_condition="context.deploy_ready=true",
|
||||
manager.max_cycles=100]
|
||||
```
|
||||
|
||||
The parent polls at `manager.poll_interval` (default 45 seconds). On each poll, it evaluates the stop condition against the current context. If the condition is true, the child is cancelled and the parent continues. This is useful when an external process (another branch, a webhook, a human gate) signals that the child's work is no longer needed.
|
||||
|
||||
## When to use sub-workflows
|
||||
|
||||
Sub-workflows are most valuable when:
|
||||
|
||||
- **Reusability** — the same child workflow is used by multiple parents (e.g., a standard test-and-fix loop, a deploy pipeline, a review checklist)
|
||||
- **Encapsulation** — the child runs its own engine with isolated logs and checkpoints, keeping the parent's execution trace clean
|
||||
- **Supervisor patterns** — the parent needs to monitor or cancel a complex child process based on external conditions
|
||||
|
||||
For simpler cases, just add more nodes to a single workflow. Sub-workflows add a layer of indirection — use them when the benefits of reuse or encapsulation justify it.
|
||||
|
||||
## What you've learned
|
||||
|
||||
- **Sub-workflow nodes** (`shape=house`) run a child workflow inside a parent
|
||||
- **`stack.child_dotfile`** references an external DOT file for reuse
|
||||
- **Context flows** from parent to child and back via diff merging
|
||||
- **`manager.max_cycles`** prevents runaway child workflows
|
||||
- **`manager.stop_condition`** cancels the child when an external signal arrives
|
||||
|
||||
## Further reading
|
||||
|
||||
<Columns cols={2}>
|
||||
<Card title="Nodes & Stages" icon="shapes" href="/workflows/stages-and-nodes">
|
||||
Complete reference for all node types.
|
||||
</Card>
|
||||
<Card title="DOT Language" icon="code" href="/reference/dot-language">
|
||||
Full syntax reference including sub-workflow attributes.
|
||||
</Card>
|
||||
<Card title="Context" icon="database" href="/execution/context">
|
||||
How context flows between nodes and across workflow boundaries.
|
||||
</Card>
|
||||
<Card title="Branch & Loop" icon="rotate" href="/tutorials/branch-loop">
|
||||
The implement-test-fix pattern used in the child workflow.
|
||||
</Card>
|
||||
</Columns>
|
||||
|
|
@ -6,9 +6,9 @@ digraph BuildSolitaire {
|
|||
retry_target="impl_setup",
|
||||
fallback_retry_target="impl_game_logic",
|
||||
model_stylesheet="
|
||||
* { llm_model: claude-sonnet-4-5; llm_provider: anthropic; }
|
||||
.hard { llm_model: claude-opus-4-6; llm_provider: anthropic; }
|
||||
.verify { llm_model: claude-haiku-4-5; llm_provider: anthropic; }
|
||||
* { llm_model: claude-sonnet; llm_provider: anthropic; }
|
||||
.hard { llm_model: claude-opus; llm_provider: anthropic; }
|
||||
.verify { llm_model: claude-haiku; llm_provider: anthropic; }
|
||||
"
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ digraph BranchLoop {
|
|||
|
||||
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="python -m pytest test_fizzbuzz.py -v 2>&1 || true"]
|
||||
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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue