diff --git a/apps/fabro-web/app/data/dot-grammar.ts b/apps/fabro-web/app/data/dot-grammar.ts index 96f2a799a..b2cb3b4da 100644 --- a/apps/fabro-web/app/data/dot-grammar.ts +++ b/apps/fabro-web/app/data/dot-grammar.ts @@ -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: [ { diff --git a/apps/fabro-web/app/routes/workflow-detail.tsx b/apps/fabro-web/app/routes/workflow-detail.tsx index 34fbd70af..569c8da2c 100644 --- a/apps/fabro-web/app/routes/workflow-detail.tsx +++ b/apps/fabro-web/app/routes/workflow-detail.tsx @@ -19,12 +19,12 @@ export const workflowData: Record = { 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 = { 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 = { 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 = { 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: { diff --git a/apps/marketing/src/pages/index.astro b/apps/marketing/src/pages/index.astro index 9eeb56299..74d898d9c 100644 --- a/apps/marketing/src/pages/index.astro +++ b/apps/marketing/src/pages/index.astro @@ -232,7 +232,7 @@ const cssExample = `/* Fast model for planning */ - workflow.dot + workflow.fabro
@@ -440,7 +440,7 @@ const cssExample = `/* Fast model for planning */
- $ fabro run workflow.dot + $ fabro run workflow.fabro
diff --git a/docs/agents/mcp.mdx b/docs/agents/mcp.mdx index a14b2c0a9..0bc01313d 100644 --- a/docs/agents/mcp.mdx +++ b/docs/agents/mcp.mdx @@ -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" diff --git a/docs/agents/outputs.mdx b/docs/agents/outputs.mdx index 15bdd2aba..9e7e7ae29 100644 --- a/docs/agents/outputs.mdx +++ b/docs/agents/outputs.mdx @@ -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 diff --git a/docs/agents/prompts.mdx b/docs/agents/prompts.mdx index 8ed3b1656..477e8f2b2 100644 --- a/docs/agents/prompts.mdx +++ b/docs/agents/prompts.mdx @@ -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"] diff --git a/docs/api-reference/fabro-api.yaml b/docs/api-reference/fabro-api.yaml index a670b4f95..0f97def97 100644 --- a/docs/api-reference/fabro-api.yaml +++ b/docs/api-reference/fabro-api.yaml @@ -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. diff --git a/docs/changelog/2026-02-23.mdx b/docs/changelog/2026-02-23.mdx index 0504d5e88..0e6e8b4fb 100644 --- a/docs/changelog/2026-02-23.mdx +++ b/docs/changelog/2026-02-23.mdx @@ -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. diff --git a/docs/changelog/2026-02-26.mdx b/docs/changelog/2026-02-26.mdx index c73a4cabc..96cef428b 100644 --- a/docs/changelog/2026-02-26.mdx +++ b/docs/changelog/2026-02-26.mdx @@ -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 diff --git a/docs/changelog/2026-03-01.mdx b/docs/changelog/2026-03-01.mdx index b7489c422..4ba2f4d44 100644 --- a/docs/changelog/2026-03-01.mdx +++ b/docs/changelog/2026-03-01.mdx @@ -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... diff --git a/docs/changelog/2026-03-04.mdx b/docs/changelog/2026-03-04.mdx index aad0cb557..bcf148209 100644 --- a/docs/changelog/2026-03-04.mdx +++ b/docs/changelog/2026-03-04.mdx @@ -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. diff --git a/docs/changelog/2026-03-09.mdx b/docs/changelog/2026-03-09.mdx index 06f1b4cf5..68b64b27f 100644 --- a/docs/changelog/2026-03-09.mdx +++ b/docs/changelog/2026-03-09.mdx @@ -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 - 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 diff --git a/docs/core-concepts/how-fabro-works.mdx b/docs/core-concepts/how-fabro-works.mdx index 04b596094..fc9bf4313 100644 --- a/docs/core-concepts/how-fabro-works.mdx +++ b/docs/core-concepts/how-fabro-works.mdx @@ -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). diff --git a/docs/core-concepts/models.mdx b/docs/core-concepts/models.mdx index 94a8bbb65..1e1400c8e 100644 --- a/docs/core-concepts/models.mdx +++ b/docs/core-concepts/models.mdx @@ -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" diff --git a/docs/core-concepts/server-mode.mdx b/docs/core-concepts/server-mode.mdx index 5bd156c52..c31fa3d51 100644 --- a/docs/core-concepts/server-mode.mdx +++ b/docs/core-concepts/server-mode.mdx @@ -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 | diff --git a/docs/core-concepts/workflows.mdx b/docs/core-concepts/workflows.mdx index 624d62bd6..680f9daf7 100644 --- a/docs/core-concepts/workflows.mdx +++ b/docs/core-concepts/workflows.mdx @@ -13,7 +13,7 @@ Every workflow is a `digraph` with a `goal`, a `start` node, an `exit` node, and Simple workflow: Start → Scan Files → Analyze → Exit -```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: diff --git a/docs/examples/clone-substack.mdx b/docs/examples/clone-substack.mdx index 1343ce5ad..8999b0ff7 100644 --- a/docs/examples/clone-substack.mdx +++ b/docs/examples/clone-substack.mdx @@ -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 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 -```dot title="clone-substack.dot" +```dot title="clone-substack.fabro" digraph CloneSubstack { graph [ goal="Build the Substack Creator Newsletter Engine — a pure React frontend \ diff --git a/docs/examples/definition-of-done.mdx b/docs/examples/definition-of-done.mdx index 9fc8975e5..c1df84855 100644 --- a/docs/examples/definition-of-done.mdx +++ b/docs/examples/definition-of-done.mdx @@ -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.", diff --git a/docs/examples/nlspec-conformance.mdx b/docs/examples/nlspec-conformance.mdx index e14a5805d..855392a73 100644 --- a/docs/examples/nlspec-conformance.mdx +++ b/docs/examples/nlspec-conformance.mdx @@ -17,7 +17,7 @@ The NLSpec Conformance pattern gives an agent a detailed specification document, NLSpec Conformance workflow: Start → Plan → Implement → Quick Tests → Quick passing? → Full Tests → All passing? → Exit, with Fix Failures loop -```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 diff --git a/docs/examples/semantic-port.mdx b/docs/examples/semantic-port.mdx index 2c12d1fcb..5ca419e36 100644 --- a/docs/examples/semantic-port.mdx +++ b/docs/examples/semantic-port.mdx @@ -13,7 +13,7 @@ This pattern is useful when you maintain a downstream implementation (e.g., a Go 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 -```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" diff --git a/docs/examples/solitaire.mdx b/docs/examples/solitaire.mdx index bcda3ef5f..fb7e8a472 100644 --- a/docs/examples/solitaire.mdx +++ b/docs/examples/solitaire.mdx @@ -13,7 +13,7 @@ This pattern is useful when you want an agent to build something non-trivial fro 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 -```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" diff --git a/docs/execution/checkpoints.mdx b/docs/execution/checkpoints.mdx index 4848d20df..caddbb9a6 100644 --- a/docs/execution/checkpoints.mdx +++ b/docs/execution/checkpoints.mdx @@ -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 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) diff --git a/docs/execution/devcontainers.mdx b/docs/execution/devcontainers.mdx index 29b23e075..6db34fb0f 100644 --- a/docs/execution/devcontainers.mdx +++ b/docs/execution/devcontainers.mdx @@ -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" diff --git a/docs/execution/environments.mdx b/docs/execution/environments.mdx index fecaa8254..c216e56fe 100644 --- a/docs/execution/environments.mdx +++ b/docs/execution/environments.mdx @@ -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. diff --git a/docs/execution/failures.mdx b/docs/execution/failures.mdx index 3ac2ccabe..b48cd59fb 100644 --- a/docs/execution/failures.mdx +++ b/docs/execution/failures.mdx @@ -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 } diff --git a/docs/execution/observability.mdx b/docs/execution/observability.mdx index e1c2c9c59..8cf9713d4 100644 --- a/docs/execution/observability.mdx +++ b/docs/execution/observability.mdx @@ -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 | diff --git a/docs/execution/retros.mdx b/docs/execution/retros.mdx index ae4a53cfd..2ac396474 100644 --- a/docs/execution/retros.mdx +++ b/docs/execution/retros.mdx @@ -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`: diff --git a/docs/execution/run-configuration.mdx b/docs/execution/run-configuration.mdx index a4b72172d..12fb4ba17 100644 --- a/docs/execution/run-configuration.mdx +++ b/docs/execution/run-configuration.mdx @@ -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. diff --git a/docs/getting-started/why-fabro.mdx b/docs/getting-started/why-fabro.mdx index e0acbbeee..e2880af67 100644 --- a/docs/getting-started/why-fabro.mdx +++ b/docs/getting-started/why-fabro.mdx @@ -52,7 +52,7 @@ Workflows are defined in Graphviz DOT, a simple graph description language. Here Plan-Implement workflow graph -```dot title="plan-implement.dot" +```dot title="plan-implement.fabro" digraph PlanImplement { graph [goal="Plan, approve, implement, and simplify a change"] diff --git a/docs/human-tools/interviews.mdx b/docs/human-tools/interviews.mdx index ab40d9963..ae269f85b 100644 --- a/docs/human-tools/interviews.mdx +++ b/docs/human-tools/interviews.mdx @@ -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 diff --git a/docs/human-tools/ssh-access.mdx b/docs/human-tools/ssh-access.mdx index 98ccfa7e8..b466ff5fa 100644 --- a/docs/human-tools/ssh-access.mdx +++ b/docs/human-tools/ssh-access.mdx @@ -29,7 +29,7 @@ fabro ssh --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. diff --git a/docs/human-tools/vs-code.mdx b/docs/human-tools/vs-code.mdx index 0a4669714..533b63141 100644 --- a/docs/human-tools/vs-code.mdx +++ b/docs/human-tools/vs-code.mdx @@ -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: diff --git a/docs/images/how-fabro-works.svg b/docs/images/how-fabro-works.svg index 111850e5f..a8ec6a045 100644 --- a/docs/images/how-fabro-works.svg +++ b/docs/images/how-fabro-works.svg @@ -38,7 +38,7 @@ Workflow -(.dot) +(.fabro) diff --git a/docs/integrations/brave-search.mdx b/docs/integrations/brave-search.mdx index 516d41717..263a9de93 100644 --- a/docs/integrations/brave-search.mdx +++ b/docs/integrations/brave-search.mdx @@ -56,7 +56,7 @@ A workflow that researches a topic before writing about it: Research workflow: Start → Research → Summarize → Exit -```dot title="research.dot" +```dot title="research.fabro" digraph Research { graph [goal="Research and summarize a topic"] rankdir=LR diff --git a/docs/integrations/daytona.mdx b/docs/integrations/daytona.mdx index cb579586d..2c3f6a77b 100644 --- a/docs/integrations/daytona.mdx +++ b/docs/integrations/daytona.mdx @@ -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: diff --git a/docs/integrations/exe-dev.mdx b/docs/integrations/exe-dev.mdx index dee3948e2..64522dab6 100644 --- a/docs/integrations/exe-dev.mdx +++ b/docs/integrations/exe-dev.mdx @@ -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. diff --git a/docs/integrations/sprites.mdx b/docs/integrations/sprites.mdx index d2994fa24..b4d72767f 100644 --- a/docs/integrations/sprites.mdx +++ b/docs/integrations/sprites.mdx @@ -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" diff --git a/docs/languages/dot.json b/docs/languages/dot.json index 18d9081c4..5221c71ab 100644 --- a/docs/languages/dot.json +++ b/docs/languages/dot.json @@ -1,7 +1,7 @@ { "name": "dot", "scopeName": "source.dot", - "fileTypes": ["dot", "DOT", "gv"], + "fileTypes": ["dot", "DOT", "gv", "fabro"], "firstLineMatch": "digraph.*", "patterns": [ { diff --git a/docs/reference/architecture.mdx b/docs/reference/architecture.mdx index 5ce5e5f0d..4de7d7aec 100644 --- a/docs/reference/architecture.mdx +++ b/docs/reference/architecture.mdx @@ -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. diff --git a/docs/reference/cli-configuration.mdx b/docs/reference/cli-configuration.mdx index 3c8e5e7fa..a1404936b 100644 --- a/docs/reference/cli-configuration.mdx +++ b/docs/reference/cli-configuration.mdx @@ -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] diff --git a/docs/reference/cli.mdx b/docs/reference/cli.mdx index 0f17b957c..7491bd966 100644 --- a/docs/reference/cli.mdx +++ b/docs/reference/cli.mdx @@ -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 @@ -46,7 +46,7 @@ fabro run --run-branch arc/run/abc123 | Argument / Flag | Description | |---|---| -| `` | Path to a `.dot` workflow file or `.toml` task config. Not required when using `--run-branch`. | +| `` | Path to a `.fabro` workflow file or `.toml` task config. Not required when using `--run-branch`. | | `--run-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 | |---|---| -| `` | Path to a `.dot` workflow file or `.toml` run config (required) | +| `` | Path to a `.fabro` workflow file or `.toml` run config (required) | | `--format ` | Output format: `svg` (default) or `png` | | `-o, --output ` | 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 | |---|---| -| `` | Path to a `.dot` workflow file or `.toml` run config (required) | +| `` | 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 | |---|---| -| `` | Path to the `.dot` workflow file (required) | +| `` | 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` diff --git a/docs/reference/dot-language.mdx b/docs/reference/dot-language.mdx index a38063c3a..b006e599d 100644 --- a/docs/reference/dot-language.mdx +++ b/docs/reference/dot-language.mdx @@ -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", diff --git a/docs/reference/run-directory.mdx b/docs/reference/run-directory.mdx index 28c3d0ec4..c7755d1ee 100644 --- a/docs/reference/run-directory.mdx +++ b/docs/reference/run-directory.mdx @@ -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 diff --git a/docs/tutorials/branch-loop.mdx b/docs/tutorials/branch-loop.mdx index a02b93079..e76c5965c 100644 --- a/docs/tutorials/branch-loop.mdx +++ b/docs/tutorials/branch-loop.mdx @@ -11,7 +11,7 @@ This tutorial builds an implement-test-fix loop — the agent writes code, runs Branch-Loop workflow: Start → Plan → Implement → Validate → Tests passing? → Pass to Exit or Fix back to Implement -```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 diff --git a/docs/tutorials/ensemble.mdx b/docs/tutorials/ensemble.mdx index e7033a1af..24f80a2e7 100644 --- a/docs/tutorials/ensemble.mdx +++ b/docs/tutorials/ensemble.mdx @@ -11,7 +11,7 @@ This tutorial combines parallel execution with multi-model routing to get indepe Ensemble workflow: Start → Fan Out → Opus, Gemini, Codex, Mercury → Merge → Synthesize → Exit -```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 ``` diff --git a/docs/tutorials/hello-world.mdx b/docs/tutorials/hello-world.mdx index cbae7678e..aff11e48a 100644 --- a/docs/tutorials/hello-world.mdx +++ b/docs/tutorials/hello-world.mdx @@ -17,7 +17,7 @@ The simplest possible workflow has one node that sends a prompt to an LLM and ex Hello World workflow: Start → Compose → Exit -```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 Tool Use workflow: Start → Explore → Exit -```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 Sub-agent workflow: Start → Research → Exit -```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 diff --git a/docs/tutorials/multi-model.mdx b/docs/tutorials/multi-model.mdx index 790a9e7ac..de19cd186 100644 --- a/docs/tutorials/multi-model.mdx +++ b/docs/tutorials/multi-model.mdx @@ -11,7 +11,7 @@ This tutorial assigns different models to different tasks in a single workflow Multi-Model workflow: Start → Write Spec (Haiku) → Implement (Sonnet) → Write Tests (Sonnet) → Code Review (Sonnet) → Exit -```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 diff --git a/docs/tutorials/parallel-review.mdx b/docs/tutorials/parallel-review.mdx index 0d718eb0e..afda3c47e 100644 --- a/docs/tutorials/parallel-review.mdx +++ b/docs/tutorials/parallel-review.mdx @@ -11,7 +11,7 @@ This tutorial runs three code review perspectives in parallel — security, arch Parallel Review workflow: Start → Fan Out → Security Audit, Architecture Review, Code Quality → Merge → Final Report → Exit -```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 diff --git a/docs/tutorials/plan-implement.mdx b/docs/tutorials/plan-implement.mdx index 053946966..da683ceab 100644 --- a/docs/tutorials/plan-implement.mdx +++ b/docs/tutorials/plan-implement.mdx @@ -11,7 +11,7 @@ This tutorial builds a plan-approve-implement workflow where a human reviews the Plan-Implement workflow: Start → Plan → Approve Plan → Implement → Simplify → Exit, with Revise loop back to Plan -```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 diff --git a/docs/tutorials/sub-workflow.mdx b/docs/tutorials/sub-workflow.mdx index 58c7e7970..eb9faa3c7 100644 --- a/docs/tutorials/sub-workflow.mdx +++ b/docs/tutorials/sub-workflow.mdx @@ -17,7 +17,7 @@ First, create a standalone implement-and-test workflow. This is a normal workflo Child workflow: Start → Implement → Validate → Tests passing? → Pass to Exit or Fix back to Implement -```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: Sub-Workflow: Start → Plan (agent) → Implement & Test (house) → Review (agent) → Exit -```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 diff --git a/docs/workflows/human-in-the-loop.mdx b/docs/workflows/human-in-the-loop.mdx index df03dddb3..9d1438ac2 100644 --- a/docs/workflows/human-in-the-loop.mdx +++ b/docs/workflows/human-in-the-loop.mdx @@ -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. diff --git a/docs/workflows/stylesheets.mdx b/docs/workflows/stylesheets.mdx index d1ca4c552..c77f223db 100644 --- a/docs/workflows/stylesheets.mdx +++ b/docs/workflows/stylesheets.mdx @@ -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", diff --git a/docs/workflows/variables.mdx b/docs/workflows/variables.mdx index 605eed14d..3184e6497 100644 --- a/docs/workflows/variables.mdx +++ b/docs/workflows/variables.mdx @@ -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"] diff --git a/fabro/workflows/factory/README.md b/fabro/workflows/factory/README.md index 2be4fd769..05c435e41 100644 --- a/fabro/workflows/factory/README.md +++ b/fabro/workflows/factory/README.md @@ -50,7 +50,7 @@ Bootstrap product context from raw inputs. Run once per product. ![Seed pipeline](seed.png) -[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 pipeline](specify.png) -[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 pipeline](architect.png) -[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 pipeline](implement.png) -[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 pipeline](sync.png) -[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 pipeline](expand.png) -[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) --- diff --git a/lib/crates/fabro-workflows/src/cli/project_config.rs b/lib/crates/fabro-workflows/src/cli/project_config.rs index 3fec1dabf..a25576a7c 100644 --- a/lib/crates/fabro-workflows/src/cli/project_config.rs +++ b/lib/crates/fabro-workflows/src/cli/project_config.rs @@ -90,6 +90,14 @@ pub fn resolve_workflow_arg(arg: &Path) -> anyhow::Result { } fn resolve_workflow_arg_from(arg: &Path, start_dir: &Path) -> anyhow::Result { + 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 { 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 { 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 Vec { - 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 { + 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 { + let Ok(entries) = std::fs::read_dir(workflows_dir) else { return Vec::new(); }; - let mut names: Vec = 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 { 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 { + let mut names: Vec = 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}"); + } } diff --git a/skills/fabro-create-workflow/SKILL.md b/skills/fabro-create-workflow/SKILL.md index aebdb9a7a..28b30c5b8 100644 --- a/skills/fabro-create-workflow/SKILL.md +++ b/skills/fabro-create-workflow/SKILL.md @@ -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 diff --git a/skills/fabro-create-workflow/references/example-workflows.md b/skills/fabro-create-workflow/references/example-workflows.md index e5eacf328..7ec1761f1 100644 --- a/skills/fabro-create-workflow/references/example-workflows.md +++ b/skills/fabro-create-workflow/references/example-workflows.md @@ -255,7 +255,7 @@ Paired TOML: ```toml version = 1 -graph = "workflow.dot" +graph = "workflow.fabro" [sandbox] provider = "local" diff --git a/skills/fabro-create-workflow/references/run-configuration.md b/skills/fabro-create-workflow/references/run-configuration.md index ab5f7b38e..a00738bf9 100644 --- a/skills/fabro-create-workflow/references/run-configuration.md +++ b/skills/fabro-create-workflow/references/run-configuration.md @@ -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" ```