mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-08-28 05:27:41 +00:00
Update docs, frontend, marketing, and skills for .fabro extension
Update 47 MDX doc pages, OpenAPI spec, SVG diagram, language grammar, frontend demo data, marketing page, skills, and README to use .fabro extension. Add "fabro" to fileTypes in language grammars. Document stack.child_workflow alongside stack.child_dotfile. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
8cb6c4f26f
commit
35bd79a2ed
58 changed files with 323 additions and 174 deletions
|
|
@ -3,7 +3,7 @@ import type { LanguageRegistration } from "@pierre/diffs";
|
|||
export const dotLanguage: LanguageRegistration = {
|
||||
name: "dot",
|
||||
scopeName: "source.dot",
|
||||
fileTypes: ["dot", "DOT", "gv"],
|
||||
fileTypes: ["fabro", "dot", "DOT", "gv"],
|
||||
firstLineMatch: "digraph.*",
|
||||
patterns: [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -19,12 +19,12 @@ export const workflowData: Record<string, WorkflowEntry> = {
|
|||
fix_build: {
|
||||
name: "Fix Build",
|
||||
slug: "fix_build",
|
||||
filename: "fix_build.dot",
|
||||
filename: "fix_build.fabro",
|
||||
description: "Automatically diagnoses and fixes CI build failures by analyzing error logs, identifying root causes, and applying targeted code changes.",
|
||||
config: {
|
||||
version: 1,
|
||||
goal: "Diagnose and fix CI build failures",
|
||||
graph: "fix_build.dot",
|
||||
graph: "fix_build.fabro",
|
||||
llm: { model: "claude-sonnet" },
|
||||
vars: { repo_url: "https://github.com/org/service", branch: "main" },
|
||||
sandbox: {
|
||||
|
|
@ -60,12 +60,12 @@ export const workflowData: Record<string, WorkflowEntry> = {
|
|||
implement: {
|
||||
name: "Implement Feature",
|
||||
slug: "implement",
|
||||
filename: "implement.dot",
|
||||
filename: "implement.fabro",
|
||||
description: "Generates production-ready code from a technical blueprint, including tests, documentation, and a pull request ready for review.",
|
||||
config: {
|
||||
version: 1,
|
||||
goal: "Implement feature from technical blueprint",
|
||||
graph: "implement.dot",
|
||||
graph: "implement.fabro",
|
||||
llm: { model: "claude-sonnet" },
|
||||
vars: { spec_path: "specs/feature.md", test_framework: "vitest" },
|
||||
setup: { commands: ["bun install", "bun run typecheck"], timeout_ms: 120000 },
|
||||
|
|
@ -116,12 +116,12 @@ export const workflowData: Record<string, WorkflowEntry> = {
|
|||
sync_drift: {
|
||||
name: "Sync Drift",
|
||||
slug: "sync_drift",
|
||||
filename: "sync_drift.dot",
|
||||
filename: "sync_drift.fabro",
|
||||
description: "Detects configuration and code drift between environments, then generates reconciliation patches to bring everything back in sync.",
|
||||
config: {
|
||||
version: 1,
|
||||
goal: "Detect and reconcile configuration drift across environments",
|
||||
graph: "sync_drift.dot",
|
||||
graph: "sync_drift.fabro",
|
||||
llm: { model: "claude-sonnet" },
|
||||
vars: { source_env: "production", target_env: "staging", drift_threshold: "warn" },
|
||||
sandbox: {
|
||||
|
|
@ -161,12 +161,12 @@ export const workflowData: Record<string, WorkflowEntry> = {
|
|||
expand: {
|
||||
name: "Expand Product",
|
||||
slug: "expand",
|
||||
filename: "expand.dot",
|
||||
filename: "expand.fabro",
|
||||
description: "Evolves the product by analyzing usage patterns and specifications to propose and implement incremental improvements.",
|
||||
config: {
|
||||
version: 1,
|
||||
goal: "Propose and implement incremental product improvements",
|
||||
graph: "expand.dot",
|
||||
graph: "expand.fabro",
|
||||
llm: { model: "claude-sonnet" },
|
||||
vars: { analytics_window: "30d", min_confidence: "0.8" },
|
||||
sandbox: {
|
||||
|
|
|
|||
|
|
@ -232,7 +232,7 @@ const cssExample = `<span class="text-ice-300">/* Fast model for planning */</sp
|
|||
<span class="h-3 w-3 rounded-full bg-coral/70"></span>
|
||||
<span class="h-3 w-3 rounded-full bg-amber/70"></span>
|
||||
<span class="h-3 w-3 rounded-full bg-mint/70"></span>
|
||||
<span class="ml-3 text-xs text-ice-300/60 font-mono">workflow.dot</span>
|
||||
<span class="ml-3 text-xs text-ice-300/60 font-mono">workflow.fabro</span>
|
||||
</div>
|
||||
<pre class="overflow-x-auto text-sm leading-relaxed"><code class="font-mono" set:html={dotExample} /></pre>
|
||||
</div>
|
||||
|
|
@ -440,7 +440,7 @@ const cssExample = `<span class="text-ice-300">/* Fast model for planning */</sp
|
|||
Composable steps. Auditable history. Repeatable results. IaC for coding.
|
||||
</p>
|
||||
<div class="reveal reveal-d2 mt-10 inline-block rounded-lg border border-navy-800 bg-navy-900/50 px-6 py-3">
|
||||
<code class="text-sm font-mono text-teal-300">$ fabro run workflow.dot</code>
|
||||
<code class="text-sm font-mono text-teal-300">$ fabro run workflow.fabro</code>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@ A workflow that uses Playwright MCP to automate a browser inside a Daytona sandb
|
|||
```toml title="run.toml"
|
||||
version = 1
|
||||
goal = "Test the login page"
|
||||
graph = "workflow.dot"
|
||||
graph = "workflow.fabro"
|
||||
|
||||
[sandbox]
|
||||
provider = "daytona"
|
||||
|
|
|
|||
|
|
@ -184,7 +184,7 @@ Artifact data is persisted on the Git [metadata branch](/execution/checkpoints#m
|
|||
```
|
||||
refs/fabro/{run_id}
|
||||
manifest.json
|
||||
graph.dot
|
||||
graph.fabro
|
||||
checkpoint.json
|
||||
artifacts/
|
||||
response.plan.json
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ The `@` prefix tells the engine to read the file contents and use them as the pr
|
|||
|
||||
Prompts support `$variable` placeholders that expand at runtime. Currently the only built-in variable is `$goal`, which resolves to the graph-level `goal` attribute:
|
||||
|
||||
```dot title="pipeline.dot"
|
||||
```dot title="pipeline.fabro"
|
||||
digraph Pipeline {
|
||||
graph [goal="Add a /health endpoint to the API server"]
|
||||
|
||||
|
|
|
|||
|
|
@ -2924,7 +2924,7 @@ components:
|
|||
filename:
|
||||
type: string
|
||||
description: DOT graph filename.
|
||||
example: fix_build.dot
|
||||
example: fix_build.fabro
|
||||
last_run:
|
||||
$ref: "#/components/schemas/WorkflowLastRun"
|
||||
schedule:
|
||||
|
|
@ -2952,7 +2952,7 @@ components:
|
|||
filename:
|
||||
type: string
|
||||
description: DOT graph filename.
|
||||
example: fix_build.dot
|
||||
example: fix_build.fabro
|
||||
description:
|
||||
type: string
|
||||
description: Prose description of what the workflow does.
|
||||
|
|
@ -4007,7 +4007,7 @@ components:
|
|||
graph:
|
||||
type: string
|
||||
description: DOT graph filename.
|
||||
example: fix_build.dot
|
||||
example: fix_build.fabro
|
||||
directory:
|
||||
type: string
|
||||
description: Working directory for the run.
|
||||
|
|
|
|||
|
|
@ -8,9 +8,9 @@ date: "2026-02-23"
|
|||
Run AI workflows from the command line with `fabro run start`, validate DOT workflow definitions with `fabro validate`, and step through dry-runs to test logic before committing real LLM calls.
|
||||
|
||||
```bash
|
||||
fabro run start spec-dod-multimodel.dot
|
||||
fabro validate my-workflow.dot
|
||||
fabro run start --dry-run my-workflow.dot
|
||||
fabro run start spec-dod-multimodel.fabro
|
||||
fabro validate my-workflow.fabro
|
||||
fabro run start --dry-run my-workflow.fabro
|
||||
```
|
||||
|
||||
The CLI streams LLM responses in real time and supports interactive tool approval — each tool call pauses for you to approve or reject via arrow-key prompts, giving fine-grained control over what the agent does.
|
||||
|
|
@ -20,7 +20,7 @@ The CLI streams LLM responses in real time and supports interactive tool approva
|
|||
Agent tool execution can now run inside Docker containers, so workflows can safely run shell commands, edit files, and install dependencies without affecting your host machine.
|
||||
|
||||
```bash
|
||||
fabro run start --docker my-workflow.dot
|
||||
fabro run start --docker my-workflow.fabro
|
||||
```
|
||||
|
||||
The container is shared across all stages in a run, so tools have access to the same filesystem throughout the workflow.
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ date: "2026-02-26"
|
|||
Workflows can now execute in Daytona cloud environments — full dev containers with SSH access, persistent storage, and network isolation. Previously, Docker was the only sandbox option, which meant running everything locally. Daytona moves execution to the cloud, freeing up your machine and providing a more production-like environment.
|
||||
|
||||
```bash
|
||||
fabro run start --execution-env daytona my-workflow.dot
|
||||
fabro run start --execution-env daytona my-workflow.fabro
|
||||
```
|
||||
|
||||
## TOML run configuration
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ Verifications run after each workflow completes and report pass/fail status, so
|
|||
After each run, an LLM-powered retro agent analyzes what happened and generates a structured summary — what worked, what didn't, timing breakdown, cost, and improvement suggestions. The retro prints inline in your terminal after the run completes, rendered as Markdown.
|
||||
|
||||
```bash
|
||||
fabro run start my-workflow.dot
|
||||
fabro run start my-workflow.fabro
|
||||
# ... run executes ...
|
||||
# === Retro ===
|
||||
# The run completed in 4m 32s across 6 stages...
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ Previously, starting too many runs at once could overwhelm the machine. Now exce
|
|||
Use `--ssh` to get SSH access into running Daytona sandboxes for live debugging while the workflow executes. When something goes wrong mid-run, you can drop into the sandbox, inspect the filesystem, and understand the problem without waiting for the run to finish.
|
||||
|
||||
```bash
|
||||
fabro run start --ssh my-workflow.dot
|
||||
fabro run start --ssh my-workflow.fabro
|
||||
```
|
||||
|
||||
Use `--preserve-sandbox` to keep sandboxes alive after a run completes for post-mortem inspection.
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ image = "my-custom-image:latest"
|
|||
```
|
||||
|
||||
```bash
|
||||
fabro run --ssh my-workflow.dot
|
||||
fabro run --ssh my-workflow.fabro
|
||||
```
|
||||
|
||||
## `fabro cp` — copy files to and from sandboxes
|
||||
|
|
@ -68,7 +68,7 @@ fabro system df
|
|||
<Accordion title="CLI">
|
||||
- PRs are now created as drafts by default; opt out with `draft = false` in `[pull_request]` config
|
||||
- Added `[sandbox.local] worktree_mode` config (`always`/`clean`/`dirty`/`never`) for controlling when git worktrees are created
|
||||
- Added `[pull_request]` config section in `cli.toml` so auto-PR works with `.dot` files
|
||||
- Added `[pull_request]` config section in `cli.toml` so auto-PR works with `.fabro` files
|
||||
- Added version info (semver, git SHA, build date) to `fabro --version`
|
||||
- Run summary now shows Run ID, logs path, base commit, branch, and PR URL
|
||||
- Workflow run output now shows local time instead of UTC
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ Both modes parse the same DOT files, use the same execution engine, and support
|
|||
|
||||
You provide three inputs:
|
||||
|
||||
1. **Workflow graph** (`.dot`) — A Graphviz DOT file defining nodes, edges, and their attributes. This is the core of what Fabro executes. See [Workflows](/core-concepts/workflows).
|
||||
1. **Workflow graph** (`.fabro`) — A Graphviz DOT file defining nodes, edges, and their attributes. This is the core of what Fabro executes. See [Workflows](/core-concepts/workflows).
|
||||
2. **Run config** (`.toml`, optional) — Overrides for the default model, sandbox provider, setup commands, and variables. See [Run Configuration](/execution/run-configuration).
|
||||
3. **API keys** (`.env`) — Provider credentials for LLM APIs. See [Quick Start](/getting-started/quick-start).
|
||||
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ When no model is specified, the `fabro exec` command uses a default model based
|
|||
|
||||
Assign models to workflow nodes using [model stylesheets](/workflows/stylesheets), which use a CSS-like syntax:
|
||||
|
||||
```dot title="example.dot"
|
||||
```dot title="example.fabro"
|
||||
digraph Example {
|
||||
graph [
|
||||
model_stylesheet="
|
||||
|
|
@ -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 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
|
||||
fabro run files-internal/demo/01-hello.fabro --model claude-opus-4-6
|
||||
fabro run files-internal/demo/04-pipeline.fabro --model gemini-3.1-pro-preview
|
||||
```
|
||||
|
||||
These flags set the default model for all nodes that don't have an explicit model assigned via a stylesheet. The provider is automatically inferred from the model catalog — you only need `--provider` for models not in the catalog or to force a specific provider.
|
||||
|
|
@ -92,7 +92,7 @@ For repeatable runs, set the model in a run config file:
|
|||
```toml title="run.toml"
|
||||
version = 1
|
||||
goal = "Implement the feature"
|
||||
graph = "implement.dot"
|
||||
graph = "implement.fabro"
|
||||
|
||||
[llm]
|
||||
model = "claude-sonnet-4-5"
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ Both modes use the same workflow engine, the same DOT files, and the same sandbo
|
|||
|
||||
| | Standalone | Server |
|
||||
|---|---|---|
|
||||
| **Command** | `fabro run workflow.dot` | `fabro serve` |
|
||||
| **Command** | `fabro run workflow.fabro` | `fabro serve` |
|
||||
| **Best for** | Local development, one-off runs, CI/CD | Production, team use, running at scale |
|
||||
| **Execution** | Synchronous, one run per process | Asynchronous, queued with configurable concurrency |
|
||||
| **Human-in-the-loop** | Terminal prompts | Web UI or HTTP endpoints |
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ Every workflow is a `digraph` with a `goal`, a `start` node, an `exit` node, and
|
|||
<img src="/images/anatomy-workflow.svg" alt="Simple workflow: Start → Scan Files → Analyze → Exit" />
|
||||
</Frame>
|
||||
|
||||
```dot title="my-workflow.dot"
|
||||
```dot title="my-workflow.fabro"
|
||||
digraph MyWorkflow {
|
||||
graph [goal="Describe the project"]
|
||||
rankdir=LR
|
||||
|
|
@ -112,7 +112,7 @@ validate [label="Validate", prompt="Run the test suite and verify all tests pass
|
|||
From the CLI:
|
||||
|
||||
```bash
|
||||
fabro run workflow.dot
|
||||
fabro run workflow.fabro
|
||||
```
|
||||
|
||||
Or from a [run config TOML](/execution/run-configuration) for repeatable, parameterized runs:
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ description: "Build an entire application from a detailed specification using de
|
|||
|
||||
The Clone Substack workflow takes a detailed specification document and autonomously builds a complete, working application — in this case, a Substack-like newsletter creation tool. It uses ensemble planning (two independent plans debated into one), a multi-stage verification chain, parallel code review with consensus, and a postmortem repair loop that feeds failures back into the next iteration.
|
||||
|
||||
This pattern is adapted from [Kilroy](https://github.com/danshapiro/kilroy)'s `substack-spec-v01.dot`, which builds a full React application from a natural language spec with acceptance criteria.
|
||||
This pattern is adapted from [Kilroy](https://github.com/danshapiro/kilroy)'s `substack-spec-v01.fabro`, which builds a full React application from a natural language spec with acceptance criteria.
|
||||
|
||||
## When to use this
|
||||
|
||||
|
|
@ -20,7 +20,7 @@ This pattern is adapted from [Kilroy](https://github.com/danshapiro/kilroy)'s `s
|
|||
<img src="/images/example-clone-substack.svg" alt="Clone Substack workflow: Start → Bootstrap → Plan Fan-Out → Plan A and Plan B → Debate → Implement → Verify Chain → Review Fan-Out → Review A and Review B → Consensus → Exit, with Fix loop from Verify back to Implement, Rejected path from Consensus to Postmortem, and Replan loop from Postmortem back to Plan Fan-Out" />
|
||||
</Frame>
|
||||
|
||||
```dot title="clone-substack.dot"
|
||||
```dot title="clone-substack.fabro"
|
||||
digraph CloneSubstack {
|
||||
graph [
|
||||
goal="Build the Substack Creator Newsletter Engine — a pure React frontend \
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ This pattern is useful when you have detailed specs with acceptance criteria (De
|
|||
|
||||
The simpler variant uses one model throughout, with sequential audits across multiple specs:
|
||||
|
||||
```dot title="spec-dod.dot"
|
||||
```dot title="spec-dod.fabro"
|
||||
digraph SpecDoD {
|
||||
graph [
|
||||
goal="Satisfy every Definition of Done checkbox across both specs (unified-llm-spec.md, coding-agent-loop-spec.md). The implementation is in Rust under crates/. Do NOT modify the spec files. Only modify implementation code.",
|
||||
|
|
@ -278,7 +278,7 @@ Otherwise set preferred_next_label to \"more_work_needed\"."
|
|||
|
||||
The multi-model variant applies the same audit-triage-fix-verify structure but uses independent assessments from two models (Claude Opus and GPT-5.2) at each phase, with cross-critique and consensus merging. This catches blind spots that a single model might miss.
|
||||
|
||||
```dot title="spec-dod-multimodel.dot"
|
||||
```dot title="spec-dod-multimodel.fabro"
|
||||
digraph SpecDoDMultiModel {
|
||||
graph [
|
||||
goal="Satisfy every Definition of Done checkbox across both specs (unified-llm-spec.md, coding-agent-loop-spec.md). The implementation is in Rust under crates/. Do NOT modify the spec files. Only modify implementation code. Uses multi-model consensus: Opus 4.6 and GPT-5.2 compete on audits and planning, GPT-5.2-codex and Opus 4.6 alternate on implementation.",
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ The NLSpec Conformance pattern gives an agent a detailed specification document,
|
|||
<img src="/images/nlspec-conformance.svg" alt="NLSpec Conformance workflow: Start → Plan → Implement → Quick Tests → Quick passing? → Full Tests → All passing? → Exit, with Fix Failures loop" />
|
||||
</Frame>
|
||||
|
||||
```dot title="n-l-spec-conformance.dot"
|
||||
```dot title="n-l-spec-conformance.fabro"
|
||||
digraph NLSpecConformance {
|
||||
graph [
|
||||
goal="Implement a conformant system from a natural language specification",
|
||||
|
|
@ -67,7 +67,7 @@ digraph NLSpecConformance {
|
|||
```
|
||||
|
||||
```bash
|
||||
fabro run workflows/nlspec-conformance.dot
|
||||
fabro run workflows/nlspec-conformance.fabro
|
||||
```
|
||||
|
||||
## How it works
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ This pattern is useful when you maintain a downstream implementation (e.g., a Go
|
|||
<img src="/images/example-semantic-port.svg" alt="Semantic Port workflow: Start → Fetch → Analyze → Plan → Implement → Validate → Tests pass? → Finalize → loops back to Fetch, with Skip shortcut from Analyze back to Fetch, Fix loop from gate back to Validate, and Done exit from Fetch" />
|
||||
</Frame>
|
||||
|
||||
```dot title="semantic-port.dot"
|
||||
```dot title="semantic-port.fabro"
|
||||
digraph SemanticPort {
|
||||
graph [
|
||||
goal="Port semantic changes from upstream Python repository to our Go implementation",
|
||||
|
|
@ -216,7 +216,7 @@ Pair the workflow with a run config TOML for repeatable execution:
|
|||
```toml title="run.toml"
|
||||
version = 1
|
||||
goal = "Port semantic changes from upstream openai-agents-python to our Go SDK"
|
||||
graph = "semport.dot"
|
||||
graph = "semport.fabro"
|
||||
|
||||
[llm]
|
||||
model = "claude-sonnet-4-5"
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ This pattern is useful when you want an agent to build something non-trivial fro
|
|||
<img src="/images/example-solitaire.svg" alt="Build Solitaire workflow: Start → Spec → Setup → OK? → Data → OK? → Logic → OK? → UI → OK? → Integrate → OK? → Review → OK? → Exit, with Retry arcs from each gate back to its phase, and a Fix arc from the review gate back to UI" />
|
||||
</Frame>
|
||||
|
||||
```dot title="build-solitaire.dot"
|
||||
```dot title="build-solitaire.fabro"
|
||||
digraph BuildSolitaire {
|
||||
graph [
|
||||
goal="Build a terminal-based solitaire (Klondike) game in Python",
|
||||
|
|
@ -257,7 +257,7 @@ Pair the workflow with a run config for repeatable execution:
|
|||
```toml title="run.toml"
|
||||
version = 1
|
||||
goal = "Build a terminal-based solitaire (Klondike) game in Python"
|
||||
graph = "build-solitaire.dot"
|
||||
graph = "build-solitaire.fabro"
|
||||
|
||||
[llm]
|
||||
model = "claude-sonnet-4-5"
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ The `Fabro-Checkpoint` trailer links each run branch commit to its metadata bran
|
|||
The metadata branch (`refs/fabro/{run_id}`) is an orphan branch that stores structured run data using Git's object storage directly (via `git2`). It is initialized at run start with:
|
||||
|
||||
- **`manifest.json`** — Run metadata: run ID, graph name, node/edge counts, base SHA, and branch name
|
||||
- **`graph.dot`** — The workflow DOT source as it was parsed
|
||||
- **`graph.fabro`** — The workflow DOT source as it was parsed
|
||||
|
||||
After each node, the metadata branch is updated with:
|
||||
|
||||
|
|
@ -98,7 +98,7 @@ There are two ways to resume an interrupted run:
|
|||
Resume from a `checkpoint.json` saved in the run directory:
|
||||
|
||||
```bash
|
||||
fabro run workflow.dot --resume path/to/logs/checkpoint.json
|
||||
fabro run workflow.fabro --resume path/to/logs/checkpoint.json
|
||||
```
|
||||
|
||||
Fabro loads the checkpoint, restores the context and execution state, and continues from the next node after the checkpoint.
|
||||
|
|
@ -115,7 +115,7 @@ This reads the checkpoint, manifest, and graph DOT from the metadata branch (`re
|
|||
|
||||
<Accordion title="What happens during resume">
|
||||
1. Fabro reads `checkpoint.json` from the metadata branch
|
||||
2. Reads `manifest.json` and `graph.dot` to reconstruct the workflow
|
||||
2. Reads `manifest.json` and `graph.fabro` to reconstruct the workflow
|
||||
3. Creates a fresh worktree attached to the existing run branch
|
||||
4. Restores the full context, completed node list, retry counts, and failure signatures
|
||||
5. If the checkpointed node used `full` fidelity, downgrades the first resumed node to `summary:high` (since the original conversation thread no longer exists in memory)
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ Set `devcontainer = true` in the `[sandbox]` section of your run config:
|
|||
|
||||
```toml title="run.toml"
|
||||
version = 1
|
||||
graph = "workflow.dot"
|
||||
graph = "workflow.fabro"
|
||||
|
||||
[sandbox]
|
||||
provider = "daytona"
|
||||
|
|
|
|||
|
|
@ -22,11 +22,11 @@ Set the sandbox provider via CLI flag, [run config TOML](/execution/run-configur
|
|||
|
||||
```bash
|
||||
# CLI flag
|
||||
fabro run workflow.dot --sandbox local
|
||||
fabro run workflow.dot --sandbox docker
|
||||
fabro run workflow.dot --sandbox daytona
|
||||
fabro run workflow.dot --sandbox ssh
|
||||
fabro run workflow.dot --sandbox exe
|
||||
fabro run workflow.fabro --sandbox local
|
||||
fabro run workflow.fabro --sandbox docker
|
||||
fabro run workflow.fabro --sandbox daytona
|
||||
fabro run workflow.fabro --sandbox ssh
|
||||
fabro run workflow.fabro --sandbox exe
|
||||
```
|
||||
|
||||
```toml title="run.toml"
|
||||
|
|
@ -93,7 +93,7 @@ The Docker sandbox is configured through the `DockerSandboxConfig`:
|
|||
By default, the container is destroyed when the run finishes. To keep it alive for debugging:
|
||||
|
||||
```bash
|
||||
fabro run workflow.dot --sandbox docker --preserve-sandbox
|
||||
fabro run workflow.fabro --sandbox docker --preserve-sandbox
|
||||
```
|
||||
|
||||
Or in the run config:
|
||||
|
|
@ -170,7 +170,7 @@ When using server defaults, labels are merged — run config labels override def
|
|||
Connect to a running Daytona sandbox via SSH for live debugging:
|
||||
|
||||
```bash
|
||||
fabro run workflow.dot --sandbox daytona --ssh
|
||||
fabro run workflow.fabro --sandbox daytona --ssh
|
||||
```
|
||||
|
||||
This creates temporary SSH credentials (valid for 60 minutes) and prints the connection command.
|
||||
|
|
@ -180,7 +180,7 @@ This creates temporary SSH credentials (valid for 60 minutes) and prints the con
|
|||
Like Docker, Daytona sandboxes are destroyed on cleanup by default. Use `--preserve-sandbox` to keep them alive:
|
||||
|
||||
```bash
|
||||
fabro run workflow.dot --sandbox daytona --preserve-sandbox
|
||||
fabro run workflow.fabro --sandbox daytona --preserve-sandbox
|
||||
```
|
||||
|
||||
Fabro prints the sandbox name so you can find it in the [Daytona dashboard](https://app.daytona.io/dashboard/sandboxes).
|
||||
|
|
@ -306,7 +306,7 @@ image = "my-custom-image:latest"
|
|||
Connect to a running exe.dev sandbox via SSH for live debugging:
|
||||
|
||||
```bash
|
||||
fabro run workflow.dot --sandbox exe --ssh
|
||||
fabro run workflow.fabro --sandbox exe --ssh
|
||||
```
|
||||
|
||||
This prints the SSH connection command so you can connect to the VM while the workflow runs.
|
||||
|
|
|
|||
|
|
@ -132,7 +132,7 @@ Fabro has two independent mechanisms for detecting stuck loops: **node visit lim
|
|||
|
||||
The `max_node_visits` graph attribute sets the maximum number of times any single node can execute before the run is terminated:
|
||||
|
||||
```dot title="example.dot"
|
||||
```dot title="example.fabro"
|
||||
digraph Example {
|
||||
graph [max_node_visits="20"]
|
||||
// ...
|
||||
|
|
@ -155,7 +155,7 @@ node "verify" visited 20 times (graph limit 20); run is stuck in a cycle
|
|||
|
||||
You can set `max_visits` on individual nodes to override the graph-level limit for that node:
|
||||
|
||||
```dot title="example.dot"
|
||||
```dot title="example.fabro"
|
||||
digraph Example {
|
||||
graph [max_node_visits="20"]
|
||||
fix [max_visits=3]
|
||||
|
|
@ -231,7 +231,7 @@ When a goal gate is unsatisfied at the exit node, Fabro looks for a **retry targ
|
|||
3. Graph-level `retry_target` attribute
|
||||
4. Graph-level `fallback_retry_target` attribute
|
||||
|
||||
```dot title="example.dot"
|
||||
```dot title="example.fabro"
|
||||
digraph Example {
|
||||
graph [retry_target="plan"]
|
||||
verify [shape=box, goal_gate="true", retry_target="implement"]
|
||||
|
|
@ -255,7 +255,7 @@ Fabro runs a background watchdog that monitors event activity. If no events are
|
|||
| `stall_timeout` | 1800 seconds (30 minutes) |
|
||||
| Set to `0` | Disables the watchdog |
|
||||
|
||||
```dot title="example.dot"
|
||||
```dot title="example.fabro"
|
||||
digraph Example {
|
||||
graph [stall_timeout="300"] // 5 minutes
|
||||
}
|
||||
|
|
|
|||
|
|
@ -140,7 +140,7 @@ cat ~/.fabro/runs/01JKXYZ.../live.json
|
|||
Fabro uses the `tracing` crate to write structured logs to `~/.fabro/logs/YYYY-MM-DD.log`. Control the log level with the `FABRO_LOG` environment variable:
|
||||
|
||||
```bash
|
||||
FABRO_LOG=debug fabro run workflow.dot
|
||||
FABRO_LOG=debug fabro run workflow.fabro
|
||||
```
|
||||
|
||||
| Level | What's logged |
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ Retro: smooth — Successfully implemented the feature
|
|||
To skip retro generation for a single run, pass `--no-retro`:
|
||||
|
||||
```bash
|
||||
fabro run workflow.dot --no-retro
|
||||
fabro run workflow.fabro --no-retro
|
||||
```
|
||||
|
||||
To disable retros project-wide, set `retro = false` in your `fabro.toml`:
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ A run config requires two fields:
|
|||
|
||||
```toml title="run.toml"
|
||||
version = 1
|
||||
graph = "workflow.dot"
|
||||
graph = "workflow.fabro"
|
||||
goal = "Implement the login feature"
|
||||
```
|
||||
|
||||
|
|
@ -32,7 +32,7 @@ Goal precedence: CLI `--goal` > TOML `goal` > DOT graph attribute.
|
|||
```toml title="run.toml"
|
||||
version = 1
|
||||
goal = "Run the CI pipeline for $repo_name"
|
||||
graph = "fabro/workflows/ci.dot"
|
||||
graph = "fabro/workflows/ci.fabro"
|
||||
directory = "/tmp/workdir"
|
||||
|
||||
[llm]
|
||||
|
|
@ -286,7 +286,7 @@ language = "rust"
|
|||
|
||||
Variables can be used anywhere in the DOT file with `$name` syntax:
|
||||
|
||||
```dot title="c-i.dot"
|
||||
```dot title="c-i.fabro"
|
||||
digraph CI {
|
||||
graph [goal="Run tests for $repo_name"]
|
||||
clone [shape=parallelogram, script="git clone $repo_url repo"]
|
||||
|
|
@ -394,8 +394,8 @@ The `graph` path is resolved relative to the TOML file's parent directory, not t
|
|||
```
|
||||
project/
|
||||
runs/
|
||||
ci.toml # graph = "ci.dot"
|
||||
ci.dot
|
||||
ci.toml # graph = "ci.fabro"
|
||||
ci.fabro
|
||||
```
|
||||
|
||||
Absolute paths are used as-is.
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ Workflows are defined in Graphviz DOT, a simple graph description language. Here
|
|||
<img src="/images/plan-implement-workflow.svg" alt="Plan-Implement workflow graph" />
|
||||
</Frame>
|
||||
|
||||
```dot title="plan-implement.dot"
|
||||
```dot title="plan-implement.fabro"
|
||||
digraph PlanImplement {
|
||||
graph [goal="Plan, approve, implement, and simplify a change"]
|
||||
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ The `Interviewer` trait has a simple interface — `ask(question) → answer`
|
|||
The default for CLI runs. On a TTY, the console interviewer uses interactive widgets (arrow-key selection, checkbox multi-select, confirm prompts) via `dialoguer`. When stdin is piped (non-TTY), it falls back to a line-based reader with numbered options.
|
||||
|
||||
```bash
|
||||
fabro run workflow.dot
|
||||
fabro run workflow.fabro
|
||||
# At a human gate:
|
||||
# ? Approve Plan
|
||||
# [1] A - [A] Approve
|
||||
|
|
@ -97,7 +97,7 @@ For fully automated runs or CI pipelines, the auto-approve interviewer answers e
|
|||
Enable it with the `--auto-approve` flag:
|
||||
|
||||
```bash
|
||||
fabro run workflow.dot --auto-approve
|
||||
fabro run workflow.fabro --auto-approve
|
||||
```
|
||||
|
||||
## Timeouts
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ fabro ssh <run-id> --ttl 120
|
|||
Pass the `--ssh` flag to `fabro run` to create SSH credentials at the start of the run:
|
||||
|
||||
```bash
|
||||
fabro run workflow.dot --sandbox daytona --ssh
|
||||
fabro run workflow.fabro --sandbox daytona --ssh
|
||||
```
|
||||
|
||||
After the sandbox is created, Fabro generates temporary SSH credentials (valid for 60 minutes) and prints the connection command:
|
||||
|
|
@ -46,7 +46,7 @@ Copy and run the `ssh` command in a separate terminal to connect.
|
|||
By default, Daytona sandboxes are destroyed when the workflow finishes. To keep the sandbox running after the workflow completes — so you can continue debugging — combine `--ssh` with `--preserve-sandbox`:
|
||||
|
||||
```bash
|
||||
fabro run workflow.dot --sandbox daytona --ssh --preserve-sandbox
|
||||
fabro run workflow.fabro --sandbox daytona --ssh --preserve-sandbox
|
||||
```
|
||||
|
||||
Without `--preserve-sandbox`, the SSH session is terminated when the run ends and the sandbox is cleaned up.
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ VS Code remote access requires [SSH access](/human-tools/ssh-access), which is o
|
|||
1. Start a workflow with SSH access and a preserved sandbox:
|
||||
|
||||
```bash
|
||||
fabro run workflow.dot --sandbox daytona --ssh --preserve-sandbox
|
||||
fabro run workflow.fabro --sandbox daytona --ssh --preserve-sandbox
|
||||
```
|
||||
|
||||
2. Fabro prints the SSH connection command:
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@
|
|||
<polyline fill="none" stroke="#357f9e" points="71.5,-380 71.5,-374"/>
|
||||
<polyline fill="none" stroke="#357f9e" points="77.5,-374 71.5,-374"/>
|
||||
<text xml:space="preserve" text-anchor="middle" x="47" y="-363.55" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#1a1a1a">Workflow</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="47" y="-351.55" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#1a1a1a">(.dot)</text>
|
||||
<text xml:space="preserve" text-anchor="middle" x="47" y="-351.55" font-family="Helvetica,sans-Serif" font-size="11.00" fill="#1a1a1a">(.fabro)</text>
|
||||
</g>
|
||||
<!-- parse -->
|
||||
<g id="node4" class="node">
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
|
@ -56,7 +56,7 @@ A workflow that researches a topic before writing about it:
|
|||
<img src="/images/brave-search-research.svg" alt="Research workflow: Start → Research → Summarize → Exit" />
|
||||
</Frame>
|
||||
|
||||
```dot title="research.dot"
|
||||
```dot title="research.fabro"
|
||||
digraph Research {
|
||||
graph [goal="Research and summarize a topic"]
|
||||
rankdir=LR
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ description: "Run Fabro workflows in sandboxed Daytona cloud environments"
|
|||
Set the sandbox provider in your run config TOML or via CLI flag:
|
||||
|
||||
```bash
|
||||
fabro run workflow.dot --sandbox daytona
|
||||
fabro run workflow.fabro --sandbox daytona
|
||||
```
|
||||
|
||||
```toml title="run.toml"
|
||||
|
|
@ -114,7 +114,7 @@ for your organization.
|
|||
Connect to a running Daytona sandbox via SSH for live debugging:
|
||||
|
||||
```bash
|
||||
fabro run workflow.dot --sandbox daytona --ssh
|
||||
fabro run workflow.fabro --sandbox daytona --ssh
|
||||
```
|
||||
|
||||
This creates temporary SSH credentials (valid for 60 minutes) and prints the connection command:
|
||||
|
|
@ -136,7 +136,7 @@ Each sandbox gets a unique timestamped name (e.g. `fabro-20260307-143022-a3f2`)
|
|||
To keep a sandbox alive for debugging:
|
||||
|
||||
```bash
|
||||
fabro run workflow.dot --sandbox daytona --preserve-sandbox
|
||||
fabro run workflow.fabro --sandbox daytona --preserve-sandbox
|
||||
```
|
||||
|
||||
Or in the run config:
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ The exe.dev sandbox provider is **under development**. It requires building Fabr
|
|||
## Configuration
|
||||
|
||||
```bash
|
||||
fabro run workflow.dot --sandbox exe
|
||||
fabro run workflow.fabro --sandbox exe
|
||||
```
|
||||
|
||||
```toml title="run.toml"
|
||||
|
|
@ -41,7 +41,7 @@ image = "my-custom-image:latest"
|
|||
Connect to a running exe.dev sandbox via SSH for live debugging:
|
||||
|
||||
```bash
|
||||
fabro run workflow.dot --sandbox exe --ssh
|
||||
fabro run workflow.fabro --sandbox exe --ssh
|
||||
```
|
||||
|
||||
This prints the SSH connection command so you can connect to the VM while the workflow runs.
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ The Sprites sandbox provider is **in progress** and not yet available for use. T
|
|||
## Configuration
|
||||
|
||||
```bash
|
||||
fabro run workflow.dot --sandbox sprites
|
||||
fabro run workflow.fabro --sandbox sprites
|
||||
```
|
||||
|
||||
```toml title="run.toml"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "dot",
|
||||
"scopeName": "source.dot",
|
||||
"fileTypes": ["dot", "DOT", "gv"],
|
||||
"fileTypes": ["dot", "DOT", "gv", "fabro"],
|
||||
"firstLineMatch": "digraph.*",
|
||||
"patterns": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ At the core of both modes is the `WorkflowRunEngine`. It parses the DOT graph, w
|
|||
## CLI mode
|
||||
|
||||
```bash
|
||||
fabro run workflow.dot --goal "Implement the login feature"
|
||||
fabro run workflow.fabro --goal "Implement the login feature"
|
||||
```
|
||||
|
||||
The CLI parses the workflow, creates the engine with a `ConsoleInterviewer`, and executes synchronously. Events are printed to stderr, progress is shown with terminal indicators, and human-in-the-loop questions are answered via interactive terminal prompts. When the run finishes, the process exits.
|
||||
|
|
|
|||
|
|
@ -185,7 +185,7 @@ ca = "~/.fabro/tls/ca.crt"
|
|||
|
||||
## `[pull_request]`
|
||||
|
||||
Enable auto-PR globally so workflows open a GitHub pull request on successful completion — even when running with a `.dot` file instead of a `run.toml`.
|
||||
Enable auto-PR globally so workflows open a GitHub pull request on successful completion — even when running with a `.fabro` file instead of a `run.toml`.
|
||||
|
||||
```toml title="cli.toml"
|
||||
[pull_request]
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ CLI flags always override `cli.toml` values, which override hardcoded defaults.
|
|||
|
||||
## `fabro run`
|
||||
|
||||
Launch a workflow from a `.dot` workflow file or `.toml` task config.
|
||||
Launch a workflow from a `.fabro` workflow file or `.toml` task config.
|
||||
|
||||
```bash
|
||||
fabro run <WORKFLOW>
|
||||
|
|
@ -46,7 +46,7 @@ fabro run --run-branch arc/run/abc123
|
|||
|
||||
| Argument / Flag | Description |
|
||||
|---|---|
|
||||
| `<WORKFLOW>` | Path to a `.dot` workflow file or `.toml` task config. Not required when using `--run-branch`. |
|
||||
| `<WORKFLOW>` | Path to a `.fabro` workflow file or `.toml` task config. Not required when using `--run-branch`. |
|
||||
| `--run-dir <DIR>` | Run output directory |
|
||||
| `--dry-run` | Execute with a simulated LLM backend |
|
||||
| `--preflight` | Validate run configuration without executing |
|
||||
|
|
@ -343,14 +343,14 @@ fabro system df -v
|
|||
Render a workflow DOT graph as SVG or PNG. Requires [Graphviz](https://graphviz.org) (`dot`) to be installed.
|
||||
|
||||
```bash
|
||||
fabro graph workflow.dot
|
||||
fabro graph workflow.dot --format png -o diagram.png
|
||||
fabro graph workflow.fabro
|
||||
fabro graph workflow.fabro --format png -o diagram.png
|
||||
fabro graph run.toml --format svg
|
||||
```
|
||||
|
||||
| Argument / Flag | Description |
|
||||
|---|---|
|
||||
| `<WORKFLOW>` | Path to a `.dot` workflow file or `.toml` run config (required) |
|
||||
| `<WORKFLOW>` | Path to a `.fabro` workflow file or `.toml` run config (required) |
|
||||
| `--format <FORMAT>` | Output format: `svg` (default) or `png` |
|
||||
| `-o, --output <FILE>` | Output file path. Defaults to stdout. |
|
||||
|
||||
|
|
@ -374,28 +374,28 @@ fabro skill install --for user --dir claude
|
|||
|
||||
## `fabro validate`
|
||||
|
||||
Validate a workflow file without executing it. Reports errors and warnings. Accepts both `.dot` workflow files and `.toml` run configs.
|
||||
Validate a workflow file without executing it. Reports errors and warnings. Accepts both `.fabro` workflow files and `.toml` run configs.
|
||||
|
||||
```bash
|
||||
fabro validate workflow.dot
|
||||
fabro validate workflow.fabro
|
||||
fabro validate run.toml
|
||||
```
|
||||
|
||||
| Argument | Description |
|
||||
|---|---|
|
||||
| `<WORKFLOW>` | Path to a `.dot` workflow file or `.toml` run config (required) |
|
||||
| `<WORKFLOW>` | Path to a `.fabro` workflow file or `.toml` run config (required) |
|
||||
|
||||
## `fabro parse`
|
||||
|
||||
Parse a DOT file and print its AST as JSON. Useful for debugging workflow definitions.
|
||||
|
||||
```bash
|
||||
fabro parse workflow.dot
|
||||
fabro parse workflow.fabro
|
||||
```
|
||||
|
||||
| Argument | Description |
|
||||
|---|---|
|
||||
| `<WORKFLOW>` | Path to the `.dot` workflow file (required) |
|
||||
| `<WORKFLOW>` | Path to the `.fabro` workflow file (required) |
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -409,7 +409,7 @@ fabro init
|
|||
|
||||
The command must be run inside a git repository. It creates:
|
||||
- `fabro.toml` — project configuration with comments and a link to docs
|
||||
- `fabro/workflows/hello/workflow.dot` — a simple greeting workflow
|
||||
- `fabro/workflows/hello/workflow.fabro` — a simple greeting workflow
|
||||
- `fabro/workflows/hello/workflow.toml` — run config for the hello workflow
|
||||
|
||||
## `fabro diff`
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ Fabro workflows are written in a subset of the [Graphviz DOT language](https://g
|
|||
|
||||
Every workflow is a `digraph` (directed graph) with a name and a body of statements:
|
||||
|
||||
```dot title="my-workflow.dot"
|
||||
```dot title="my-workflow.fabro"
|
||||
digraph MyWorkflow {
|
||||
graph [goal="Describe the project"]
|
||||
rankdir=LR
|
||||
|
|
@ -345,7 +345,7 @@ Fabro validates workflows at parse time and reports diagnostics. Key rules:
|
|||
|
||||
## Complete example
|
||||
|
||||
```dot title="implement-feature.dot"
|
||||
```dot title="implement-feature.fabro"
|
||||
digraph ImplementFeature {
|
||||
graph [
|
||||
goal="Implement a feature with tests and code review",
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ The naming format is `YYYYMMDD-{run_id}`, where `run_id` is the ULID assigned to
|
|||
| File | Format | When written | Description |
|
||||
|---|---|---|---|
|
||||
| `manifest.json` | JSON | Run start | Run metadata — `run_id`, `workflow_name`, `goal`, `start_time`, `node_count`, `edge_count`, `run_branch`, `base_sha`, `labels` |
|
||||
| `graph.dot` | DOT | Run start | Copy of the workflow graph |
|
||||
| `graph.fabro` | DOT | Run start | Copy of the workflow graph |
|
||||
| `run.pid` | Text | Run start | Process ID of the running CLI process. Presence indicates the run is active; an orphaned file indicates a crash. |
|
||||
| `run.toml` | TOML | Run start | Copy of the original workflow file (only when the workflow is defined in TOML) |
|
||||
| `progress.jsonl` | JSONL | Continuous | Event stream — one JSON object per line for every significant event (stage starts, completions, tool calls, retries, etc.). See [Observability](/execution/observability) for the full event catalog. |
|
||||
|
|
@ -82,7 +82,7 @@ fabro ps --filter workflow=my-workflow
|
|||
~/.fabro/runs/
|
||||
├── 20260307-01JQXYZ123ABC456DEF789/ # One directory per run
|
||||
│ ├── manifest.json
|
||||
│ ├── graph.dot
|
||||
│ ├── graph.fabro
|
||||
│ ├── run.pid
|
||||
│ ├── run.toml
|
||||
│ ├── progress.jsonl
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ This tutorial builds an implement-test-fix loop — the agent writes code, runs
|
|||
<img src="/images/tutorial-branch-loop.svg" alt="Branch-Loop workflow: Start → Plan → Implement → Validate → Tests passing? → Pass to Exit or Fix back to Implement" />
|
||||
</Frame>
|
||||
|
||||
```dot title="branch-loop.dot"
|
||||
```dot title="branch-loop.fabro"
|
||||
digraph BranchLoop {
|
||||
graph [goal="Create a Python script that passes its test suite"]
|
||||
rankdir=LR
|
||||
|
|
@ -31,7 +31,7 @@ digraph BranchLoop {
|
|||
```
|
||||
|
||||
```bash
|
||||
fabro run files-internal/demo/05-branch-loop.dot
|
||||
fabro run files-internal/demo/05-branch-loop.fabro
|
||||
```
|
||||
|
||||
## Command nodes
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ This tutorial combines parallel execution with multi-model routing to get indepe
|
|||
<img src="/images/tutorial-ensemble.svg" alt="Ensemble workflow: Start → Fan Out → Opus, Gemini, Codex, Mercury → Merge → Synthesize → Exit" />
|
||||
</Frame>
|
||||
|
||||
```dot title="ensemble.dot"
|
||||
```dot title="ensemble.fabro"
|
||||
digraph Ensemble {
|
||||
graph [
|
||||
goal="Get independent opinions from multiple providers, then synthesize",
|
||||
|
|
@ -52,7 +52,7 @@ digraph Ensemble {
|
|||
```
|
||||
|
||||
```bash
|
||||
fabro run files-internal/demo/11-ensemble.dot
|
||||
fabro run files-internal/demo/11-ensemble.fabro
|
||||
```
|
||||
|
||||
<Note>
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ The simplest possible workflow has one node that sends a prompt to an LLM and ex
|
|||
<img src="/images/tutorial-hello.svg" alt="Hello World workflow: Start → Compose → Exit" />
|
||||
</Frame>
|
||||
|
||||
```dot title="hello.dot"
|
||||
```dot title="hello.fabro"
|
||||
digraph Hello {
|
||||
graph [goal="Write a haiku about software workflows"]
|
||||
rankdir=LR
|
||||
|
|
@ -34,7 +34,7 @@ digraph Hello {
|
|||
Run it:
|
||||
|
||||
```bash
|
||||
fabro run files-internal/demo/01-hello.dot
|
||||
fabro run files-internal/demo/01-hello.fabro
|
||||
```
|
||||
|
||||
### What's happening
|
||||
|
|
@ -53,7 +53,7 @@ An **agent node** (the default `box` shape) runs an LLM in a loop with access to
|
|||
<img src="/images/tutorial-tool-use.svg" alt="Tool Use workflow: Start → Explore → Exit" />
|
||||
</Frame>
|
||||
|
||||
```dot title="tool-use.dot"
|
||||
```dot title="tool-use.fabro"
|
||||
digraph ToolUse {
|
||||
graph [goal="Explore the current directory using shell tools"]
|
||||
rankdir=LR
|
||||
|
|
@ -68,7 +68,7 @@ digraph ToolUse {
|
|||
```
|
||||
|
||||
```bash
|
||||
fabro run files-internal/demo/02-tool-use.dot
|
||||
fabro run files-internal/demo/02-tool-use.fabro
|
||||
```
|
||||
|
||||
### What's happening
|
||||
|
|
@ -93,7 +93,7 @@ An agent can spawn **sub-agents** to delegate work. Sub-agents run in their own
|
|||
<img src="/images/tutorial-subagent.svg" alt="Sub-agent workflow: Start → Research → Exit" />
|
||||
</Frame>
|
||||
|
||||
```dot title="sub-agent.dot"
|
||||
```dot title="sub-agent.fabro"
|
||||
digraph SubAgent {
|
||||
graph [goal="Research and summarize using a sub-agent"]
|
||||
rankdir=LR
|
||||
|
|
@ -108,7 +108,7 @@ digraph SubAgent {
|
|||
```
|
||||
|
||||
```bash
|
||||
fabro run files-internal/demo/03-subagent.dot
|
||||
fabro run files-internal/demo/03-subagent.fabro
|
||||
```
|
||||
|
||||
### What's happening
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ This tutorial assigns different models to different tasks in a single workflow
|
|||
<img src="/images/tutorial-multi-model.svg" alt="Multi-Model workflow: Start → Write Spec (Haiku) → Implement (Sonnet) → Write Tests (Sonnet) → Code Review (Sonnet) → Exit" />
|
||||
</Frame>
|
||||
|
||||
```dot title="multi-model.dot"
|
||||
```dot title="multi-model.fabro"
|
||||
digraph MultiModel {
|
||||
graph [
|
||||
goal="Build and review a utility function using multiple models",
|
||||
|
|
@ -36,7 +36,7 @@ digraph MultiModel {
|
|||
```
|
||||
|
||||
```bash
|
||||
fabro run files-internal/demo/08-multi-model.dot
|
||||
fabro run files-internal/demo/08-multi-model.fabro
|
||||
```
|
||||
|
||||
## Model stylesheets
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ This tutorial runs three code review perspectives in parallel — security, arch
|
|||
<img src="/images/tutorial-parallel-review.svg" alt="Parallel Review workflow: Start → Fan Out → Security Audit, Architecture Review, Code Quality → Merge → Final Report → Exit" />
|
||||
</Frame>
|
||||
|
||||
```dot title="parallel.dot"
|
||||
```dot title="parallel.fabro"
|
||||
digraph Parallel {
|
||||
graph [goal="Perform a multi-perspective code review"]
|
||||
rankdir=LR
|
||||
|
|
@ -40,7 +40,7 @@ digraph Parallel {
|
|||
```
|
||||
|
||||
```bash
|
||||
fabro run files-internal/demo/06-parallel.dot
|
||||
fabro run files-internal/demo/06-parallel.fabro
|
||||
```
|
||||
|
||||
## Fan-out with the fork node
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ This tutorial builds a plan-approve-implement workflow where a human reviews the
|
|||
<img src="/images/tutorial-plan-implement.svg" alt="Plan-Implement workflow: Start → Plan → Approve Plan → Implement → Simplify → Exit, with Revise loop back to Plan" />
|
||||
</Frame>
|
||||
|
||||
```dot title="plan-implement.dot"
|
||||
```dot title="plan-implement.fabro"
|
||||
digraph PlanImplement {
|
||||
graph [goal="Plan, approve, implement, and simplify a change"]
|
||||
rankdir=LR
|
||||
|
|
@ -32,7 +32,7 @@ digraph PlanImplement {
|
|||
```
|
||||
|
||||
```bash
|
||||
fabro run files-internal/demo/10-plan-implement.dot
|
||||
fabro run files-internal/demo/10-plan-implement.fabro
|
||||
```
|
||||
|
||||
## Human gates
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ First, create a standalone implement-and-test workflow. This is a normal workflo
|
|||
<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"
|
||||
```dot title="implement-and-test.fabro"
|
||||
digraph ImplementAndTest {
|
||||
graph [goal="Implement the plan and make tests pass"]
|
||||
rankdir=LR
|
||||
|
|
@ -45,7 +45,7 @@ Now create a parent workflow that delegates to the child:
|
|||
<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"
|
||||
```dot title="sub-workflow.fabro"
|
||||
digraph SubWorkflow {
|
||||
graph [goal="Create a Python module (tempconv.py) that converts between Celsius, Fahrenheit, and Kelvin, with pytest tests"]
|
||||
rankdir=LR
|
||||
|
|
@ -54,7 +54,7 @@ digraph SubWorkflow {
|
|||
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]
|
||||
impl [label="Implement & Test", shape=house, stack.child_workflow="implement-and-test.fabro", 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
|
||||
|
|
@ -62,15 +62,15 @@ digraph SubWorkflow {
|
|||
```
|
||||
|
||||
```bash
|
||||
fabro run files-internal/demo/12-sub-workflow.dot
|
||||
fabro run files-internal/demo/12-sub-workflow.fabro
|
||||
```
|
||||
|
||||
## 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:
|
||||
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 workflow file:
|
||||
|
||||
```dot
|
||||
impl [label="Implement & Test", shape=house, stack.child_dotfile="implement-and-test.dot", manager.max_cycles=50]
|
||||
impl [label="Implement & Test", shape=house, stack.child_workflow="implement-and-test.fabro", 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.
|
||||
|
|
@ -79,13 +79,14 @@ The child workflow runs through its own start → implement → validate → gat
|
|||
|
||||
| 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`) |
|
||||
| `stack.child_workflow` | Path to the child workflow file (resolved relative to the parent). Preferred. |
|
||||
| `stack.child_dotfile` | Backward-compatible alias for `stack.child_workflow`. |
|
||||
| `stack.child_dot_source` | Inline child DOT source (alternative to `child_workflow`) |
|
||||
| `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.
|
||||
Use `stack.child_workflow` 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
|
||||
|
||||
|
|
@ -102,7 +103,7 @@ For long-running child workflows, you can set a stop condition that cancels the
|
|||
|
||||
```dot
|
||||
impl [shape=house,
|
||||
stack.child_dotfile="implement-and-test.dot",
|
||||
stack.child_workflow="implement-and-test.fabro",
|
||||
manager.stop_condition="context.deploy_ready=true",
|
||||
manager.max_cycles=100]
|
||||
```
|
||||
|
|
@ -122,7 +123,7 @@ For simpler cases, just add more nodes to a single workflow. Sub-workflows add a
|
|||
## 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
|
||||
- **`stack.child_workflow`** references an external workflow file for reuse (`stack.child_dotfile` is also supported for backward compatibility)
|
||||
- **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
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ If the timeout elapses without a response, the workflow continues to the default
|
|||
For testing or fully automated runs, pass `--auto-approve` to skip all human gates:
|
||||
|
||||
```bash
|
||||
fabro run workflow.dot --auto-approve
|
||||
fabro run workflow.fabro --auto-approve
|
||||
```
|
||||
|
||||
Auto-approve selects `Yes` for yes/no gates and the first option for multiple-choice gates.
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ Model stylesheets let you assign LLM models, providers, and settings to workflow
|
|||
|
||||
Stylesheets are set in the `model_stylesheet` graph attribute:
|
||||
|
||||
```dot title="example.dot"
|
||||
```dot title="example.fabro"
|
||||
digraph Example {
|
||||
graph [
|
||||
goal="Build and review a utility function",
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ Define variables in the `[vars]` section of a run config TOML file:
|
|||
```toml title="run.toml"
|
||||
version = 1
|
||||
goal = "Run tests for $repo_name"
|
||||
graph = "check.dot"
|
||||
graph = "check.fabro"
|
||||
|
||||
[vars]
|
||||
repo_name = "fabro"
|
||||
|
|
@ -22,7 +22,7 @@ language = "rust"
|
|||
|
||||
These variables are expanded into the DOT source **before** the graph is parsed. You can use `$variable` anywhere in the DOT file — goals, prompts, labels, scripts, or any other attribute:
|
||||
|
||||
```dot title="check.dot"
|
||||
```dot title="check.fabro"
|
||||
digraph Check {
|
||||
graph [goal="Run tests for $repo_name"]
|
||||
|
||||
|
|
@ -56,7 +56,7 @@ This produces `The env var is $HOME` without treating `$HOME` as a variable refe
|
|||
|
||||
Inside agent and prompt node prompts, Fabro automatically expands `$goal` to the workflow's `goal` attribute. This happens at runtime, after graph parsing:
|
||||
|
||||
```dot title="example.dot"
|
||||
```dot title="example.fabro"
|
||||
digraph Example {
|
||||
graph [goal="Implement the login feature"]
|
||||
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ Bootstrap product context from raw inputs. Run once per product.
|
|||
|
||||

|
||||
|
||||
[seed.dot](seed.dot) | Prompts: [ingest](prompts/seed/ingest.md), [draft](prompts/seed/draft.md)
|
||||
[seed.fabro](seed.fabro) | Prompts: [ingest](prompts/seed/ingest.md), [draft](prompts/seed/draft.md)
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -63,7 +63,7 @@ Define what a feature does in implementation-agnostic terms. Run once per featur
|
|||
|
||||

|
||||
|
||||
[specify.dot](specify.dot) | Prompts: [clarify](prompts/specify/clarify.md), [require](prompts/specify/require.md)
|
||||
[specify.fabro](specify.fabro) | Prompts: [clarify](prompts/specify/clarify.md), [require](prompts/specify/require.md)
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -76,7 +76,7 @@ Translate approved requirements into a technical blueprint. Run once per feature
|
|||
|
||||

|
||||
|
||||
[architect.dot](architect.dot) | Prompts: [blueprint](prompts/architect/blueprint.md), [diagram](prompts/architect/diagram.md)
|
||||
[architect.fabro](architect.fabro) | Prompts: [blueprint](prompts/architect/blueprint.md), [diagram](prompts/architect/diagram.md)
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -89,7 +89,7 @@ Generate working code from a feature blueprint. Run once per feature, after Arch
|
|||
|
||||

|
||||
|
||||
[implement.dot](implement.dot) | Prompts: [plan](prompts/implement/plan.md), [implement](prompts/implement/implement.md), [validate](prompts/implement/validate.md), [fix](prompts/implement/fix.md)
|
||||
[implement.fabro](implement.fabro) | Prompts: [plan](prompts/implement/plan.md), [implement](prompts/implement/implement.md), [validate](prompts/implement/validate.md), [fix](prompts/implement/fix.md)
|
||||
|
||||
All nodes share `fidelity="full"` with `thread_id="impl"` so the agent maintains full context across the loop. `goal_gate=true` on Validate ensures the pipeline cannot exit until all acceptance criteria pass.
|
||||
|
||||
|
|
@ -104,7 +104,7 @@ Detect and resolve drift between the three layers. Run continuously (after merge
|
|||
|
||||

|
||||
|
||||
[sync.dot](sync.dot) | Prompts: [detect](prompts/sync/detect.md), [propose](prompts/sync/propose.md), [apply](prompts/sync/apply.md)
|
||||
[sync.fabro](sync.fabro) | Prompts: [detect](prompts/sync/detect.md), [propose](prompts/sync/propose.md), [apply](prompts/sync/apply.md)
|
||||
|
||||
Short-circuits to Exit when no drift is detected, avoiding unnecessary human interaction.
|
||||
|
||||
|
|
@ -119,7 +119,7 @@ Evolve the product by adding, modifying, or removing features. Run as needed.
|
|||
|
||||

|
||||
|
||||
[expand.dot](expand.dot) | Prompts: [propose](prompts/expand/propose.md), [execute](prompts/expand/execute.md)
|
||||
[expand.fabro](expand.fabro) | Prompts: [propose](prompts/expand/propose.md), [execute](prompts/expand/execute.md)
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -90,6 +90,14 @@ pub fn resolve_workflow_arg(arg: &Path) -> anyhow::Result<PathBuf> {
|
|||
}
|
||||
|
||||
fn resolve_workflow_arg_from(arg: &Path, start_dir: &Path) -> anyhow::Result<PathBuf> {
|
||||
resolve_workflow_arg_impl(arg, start_dir, user_workflows_dir().as_deref())
|
||||
}
|
||||
|
||||
fn resolve_workflow_arg_impl(
|
||||
arg: &Path,
|
||||
start_dir: &Path,
|
||||
user_workflows: Option<&Path>,
|
||||
) -> anyhow::Result<PathBuf> {
|
||||
if arg.extension().is_some() {
|
||||
tracing::debug!(arg = %arg.display(), "Workflow arg has extension, returning as-is");
|
||||
return Ok(arg.to_path_buf());
|
||||
|
|
@ -99,32 +107,48 @@ fn resolve_workflow_arg_from(arg: &Path, start_dir: &Path) -> anyhow::Result<Pat
|
|||
match discover_project_config(start_dir) {
|
||||
Ok(Some((config_path, config))) => {
|
||||
let fabro_root = resolve_fabro_root(&config_path, &config);
|
||||
let candidate = fabro_root
|
||||
let project_candidate = fabro_root
|
||||
.join("workflows")
|
||||
.join(&*name)
|
||||
.join("workflow.toml");
|
||||
if candidate.is_file() {
|
||||
tracing::debug!(arg = %arg.display(), resolved = %candidate.display(), "Resolved workflow name via project config");
|
||||
Ok(candidate)
|
||||
} else {
|
||||
let available = list_available_workflows(&fabro_root);
|
||||
if available.is_empty() {
|
||||
bail!(
|
||||
"Unknown workflow '{name}'\n\nNo workflows found in {}",
|
||||
fabro_root.join("workflows").display()
|
||||
);
|
||||
}
|
||||
let mut msg = format!(
|
||||
"Unknown workflow '{name}'\n\nAvailable workflows: {}",
|
||||
available.join(", ")
|
||||
);
|
||||
if let Some(suggestion) = find_closest_match(&name, &available) {
|
||||
msg.push_str(&format!("\n\nDid you mean '{suggestion}'?"));
|
||||
}
|
||||
bail!("{msg}");
|
||||
if project_candidate.is_file() {
|
||||
tracing::debug!(arg = %arg.display(), resolved = %project_candidate.display(), "Resolved workflow name via project config");
|
||||
return Ok(project_candidate);
|
||||
}
|
||||
|
||||
if let Some(user_wf) = user_workflows {
|
||||
let user_candidate = user_wf.join(&*name).join("workflow.toml");
|
||||
if user_candidate.is_file() {
|
||||
tracing::debug!(arg = %arg.display(), resolved = %user_candidate.display(), "Resolved workflow name via user workflows");
|
||||
return Ok(user_candidate);
|
||||
}
|
||||
}
|
||||
|
||||
let project_wf_dir = fabro_root.join("workflows");
|
||||
let available = list_available_workflows(Some(&project_wf_dir), user_workflows);
|
||||
if available.is_empty() {
|
||||
bail!(
|
||||
"Unknown workflow '{name}'\n\nNo workflows found in {}",
|
||||
project_wf_dir.display()
|
||||
);
|
||||
}
|
||||
let mut msg = format!(
|
||||
"Unknown workflow '{name}'\n\nAvailable workflows: {}",
|
||||
available.join(", ")
|
||||
);
|
||||
if let Some(suggestion) = find_closest_match(&name, &available) {
|
||||
msg.push_str(&format!("\n\nDid you mean '{suggestion}'?"));
|
||||
}
|
||||
bail!("{msg}");
|
||||
}
|
||||
Ok(None) => {
|
||||
if let Some(user_wf) = user_workflows {
|
||||
let user_candidate = user_wf.join(&*name).join("workflow.toml");
|
||||
if user_candidate.is_file() {
|
||||
tracing::debug!(arg = %arg.display(), resolved = %user_candidate.display(), "Resolved workflow name via user workflows (no project config)");
|
||||
return Ok(user_candidate);
|
||||
}
|
||||
}
|
||||
tracing::debug!(arg = %arg.display(), "No project config found, returning literal");
|
||||
Ok(arg.to_path_buf())
|
||||
}
|
||||
|
|
@ -135,13 +159,17 @@ fn resolve_workflow_arg_from(arg: &Path, start_dir: &Path) -> anyhow::Result<Pat
|
|||
}
|
||||
}
|
||||
|
||||
/// List workflow names by scanning `{fabro_root}/workflows/` for dirs containing `workflow.toml`.
|
||||
fn list_available_workflows(fabro_root: &Path) -> Vec<String> {
|
||||
let workflows_dir = fabro_root.join("workflows");
|
||||
let Ok(entries) = std::fs::read_dir(&workflows_dir) else {
|
||||
/// Return the user-level workflows directory (`~/.fabro/workflows/`).
|
||||
fn user_workflows_dir() -> Option<PathBuf> {
|
||||
dirs::home_dir().map(|h| h.join(".fabro").join("workflows"))
|
||||
}
|
||||
|
||||
/// List workflow names in a single directory by scanning for subdirs containing `workflow.toml`.
|
||||
fn list_workflows_in(workflows_dir: &Path) -> Vec<String> {
|
||||
let Ok(entries) = std::fs::read_dir(workflows_dir) else {
|
||||
return Vec::new();
|
||||
};
|
||||
let mut names: Vec<String> = entries
|
||||
entries
|
||||
.filter_map(|entry| {
|
||||
let entry = entry.ok()?;
|
||||
let path = entry.path();
|
||||
|
|
@ -151,7 +179,28 @@ fn list_available_workflows(fabro_root: &Path) -> Vec<String> {
|
|||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// List workflow names by scanning project and user workflow directories.
|
||||
/// Project workflows appear first; user workflows are deduplicated.
|
||||
fn list_available_workflows(
|
||||
project_workflows_dir: Option<&Path>,
|
||||
user_workflows_dir: Option<&Path>,
|
||||
) -> Vec<String> {
|
||||
let mut names: Vec<String> = Vec::new();
|
||||
|
||||
if let Some(dir) = project_workflows_dir {
|
||||
names.extend(list_workflows_in(dir));
|
||||
}
|
||||
if let Some(dir) = user_workflows_dir {
|
||||
for name in list_workflows_in(dir) {
|
||||
if !names.contains(&name) {
|
||||
names.push(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
names.sort();
|
||||
names
|
||||
}
|
||||
|
|
@ -489,4 +538,103 @@ mod tests {
|
|||
assert_eq!(dot_path, expected_dot);
|
||||
assert!(cfg.is_none(), "expected None for .fabro path");
|
||||
}
|
||||
|
||||
/// Helper: create a workflow dir with workflow.toml + workflow.fabro inside `base/workflows/{name}/`
|
||||
fn create_workflow_in(base: &Path, name: &str) {
|
||||
let wf_dir = base.join("workflows").join(name);
|
||||
fs::create_dir_all(&wf_dir).unwrap();
|
||||
fs::write(
|
||||
wf_dir.join("workflow.toml"),
|
||||
"version = 1\ngraph = \"workflow.fabro\"\n",
|
||||
)
|
||||
.unwrap();
|
||||
fs::write(
|
||||
wf_dir.join("workflow.fabro"),
|
||||
"digraph G { start [shape=Mdiamond]; exit [shape=Msquare]; start -> exit }",
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_workflow_arg_user_workflow_found() {
|
||||
let project_dir = TempDir::new().unwrap();
|
||||
// No fabro.toml in project_dir
|
||||
let user_dir = TempDir::new().unwrap();
|
||||
create_workflow_in(user_dir.path(), "my-wf");
|
||||
|
||||
let result = resolve_workflow_arg_impl(
|
||||
Path::new("my-wf"),
|
||||
project_dir.path(),
|
||||
Some(user_dir.path().join("workflows").as_path()),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
result,
|
||||
user_dir.path().join("workflows/my-wf/workflow.toml")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_workflow_arg_project_takes_precedence() {
|
||||
let project_dir = TempDir::new().unwrap();
|
||||
fs::write(project_dir.path().join("fabro.toml"), "version = 1\n").unwrap();
|
||||
create_workflow_in(project_dir.path(), "shared");
|
||||
|
||||
let user_dir = TempDir::new().unwrap();
|
||||
create_workflow_in(user_dir.path(), "shared");
|
||||
|
||||
let result = resolve_workflow_arg_impl(
|
||||
Path::new("shared"),
|
||||
project_dir.path(),
|
||||
Some(user_dir.path().join("workflows").as_path()),
|
||||
)
|
||||
.unwrap();
|
||||
// Should resolve to project, not user
|
||||
assert_eq!(
|
||||
result,
|
||||
project_dir.path().join("workflows/shared/workflow.toml")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_workflow_arg_user_fallback_when_project_missing() {
|
||||
let project_dir = TempDir::new().unwrap();
|
||||
fs::write(project_dir.path().join("fabro.toml"), "version = 1\n").unwrap();
|
||||
// Project has a different workflow
|
||||
create_workflow_in(project_dir.path(), "other");
|
||||
|
||||
let user_dir = TempDir::new().unwrap();
|
||||
create_workflow_in(user_dir.path(), "my-wf");
|
||||
|
||||
let result = resolve_workflow_arg_impl(
|
||||
Path::new("my-wf"),
|
||||
project_dir.path(),
|
||||
Some(user_dir.path().join("workflows").as_path()),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
result,
|
||||
user_dir.path().join("workflows/my-wf/workflow.toml")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_workflow_arg_user_workflow_listed_in_error() {
|
||||
let project_dir = TempDir::new().unwrap();
|
||||
fs::write(project_dir.path().join("fabro.toml"), "version = 1\n").unwrap();
|
||||
create_workflow_in(project_dir.path(), "proj-wf");
|
||||
|
||||
let user_dir = TempDir::new().unwrap();
|
||||
create_workflow_in(user_dir.path(), "user-wf");
|
||||
|
||||
let err = resolve_workflow_arg_impl(
|
||||
Path::new("nonexistent"),
|
||||
project_dir.path(),
|
||||
Some(user_dir.path().join("workflows").as_path()),
|
||||
)
|
||||
.unwrap_err();
|
||||
let msg = err.to_string();
|
||||
assert!(msg.contains("proj-wf"), "expected proj-wf in: {msg}");
|
||||
assert!(msg.contains("user-wf"), "expected user-wf in: {msg}");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
---
|
||||
name: fabro-create-workflow
|
||||
description: Create Fabro workflow DOT graphs and TOML run configurations from natural language requirements. Use when the user wants to create a new workflow, build a pipeline, design a multi-step agent process, or write a .dot or .toml file for Fabro. Covers topology selection, node types, model assignment, edge routing, and run configuration.
|
||||
description: Create Fabro workflow DOT graphs and TOML run configurations from natural language requirements. Use when the user wants to create a new workflow, build a pipeline, design a multi-step agent process, or write a .fabro or .toml file for Fabro. Covers topology selection, node types, model assignment, edge routing, and run configuration.
|
||||
---
|
||||
|
||||
# Fabro Create Workflow
|
||||
|
||||
Turn requirements into a runnable Fabro workflow: a `.dot` graph file defining the pipeline structure and an optional `.toml` run configuration.
|
||||
Turn requirements into a runnable Fabro workflow: a `.fabro` graph file defining the pipeline structure and an optional `.toml` run configuration.
|
||||
|
||||
## Workflow
|
||||
|
||||
|
|
@ -103,7 +103,7 @@ graph [model_stylesheet="
|
|||
|
||||
See `references/run-configuration.md` for the full reference.
|
||||
|
||||
A TOML file is optional for simple workflows (you can run `fabro run workflow.dot` directly). Create one when you need:
|
||||
A TOML file is optional for simple workflows (you can run `fabro run workflow.fabro` directly). Create one when you need:
|
||||
- Sandbox configuration (provider, environment variables)
|
||||
- Setup commands (install dependencies)
|
||||
- Variable definitions
|
||||
|
|
@ -115,7 +115,7 @@ Minimal TOML:
|
|||
|
||||
```toml
|
||||
version = 1
|
||||
graph = "workflow.dot"
|
||||
graph = "workflow.fabro"
|
||||
```
|
||||
|
||||
Common additions:
|
||||
|
|
@ -133,7 +133,7 @@ NODE_ENV = "test"
|
|||
|
||||
### Step 7: Validate
|
||||
|
||||
Run `fabro run --preflight workflow.toml` (or `fabro run --preflight workflow.dot`) to validate without executing.
|
||||
Run `fabro run --preflight workflow.toml` (or `fabro run --preflight workflow.fabro`) to validate without executing.
|
||||
|
||||
If validation fails, fix the reported errors and re-validate.
|
||||
|
||||
|
|
@ -156,7 +156,7 @@ Place workflow files together in a directory:
|
|||
|
||||
```
|
||||
my-workflow/
|
||||
workflow.dot # the graph
|
||||
workflow.fabro # the graph
|
||||
workflow.toml # run configuration (optional)
|
||||
prompts/ # external prompt files (optional)
|
||||
implement.md
|
||||
|
|
@ -166,14 +166,14 @@ my-workflow/
|
|||
## Running Workflows
|
||||
|
||||
```bash
|
||||
fabro run workflow.dot # run DOT directly
|
||||
fabro run workflow.fabro # run graph directly
|
||||
fabro run workflow.toml # run with TOML config
|
||||
fabro run workflow.toml --dry-run # simulated LLM backend
|
||||
fabro run workflow.toml --no-retro # skip retro (faster for testing)
|
||||
fabro run workflow.toml --auto-approve # auto-approve human gates
|
||||
fabro run workflow.toml --model claude-opus-4-6 # override model
|
||||
fabro run workflow.toml --sandbox local # override sandbox
|
||||
fabro validate workflow.dot # validate only
|
||||
fabro validate workflow.fabro # validate only
|
||||
```
|
||||
|
||||
## References
|
||||
|
|
|
|||
|
|
@ -255,7 +255,7 @@ Paired TOML:
|
|||
|
||||
```toml
|
||||
version = 1
|
||||
graph = "workflow.dot"
|
||||
graph = "workflow.fabro"
|
||||
|
||||
[sandbox]
|
||||
provider = "local"
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ The TOML file configures how a workflow is executed. It is separate from the DOT
|
|||
|
||||
```toml
|
||||
version = 1
|
||||
graph = "workflow.dot"
|
||||
graph = "workflow.fabro"
|
||||
goal = "Implement the login feature"
|
||||
```
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue