Find a file
Scott Werner d590122531
feat: chat-driven workflow builder at /playground (#450)
## Summary

Adds a new `/playground` route where users build a Fabro workflow by
chatting with Ask Fabro on the right while watching a live canvas
re-render on the left. The workflow can be downloaded as a `.fabro.zip`
or — eventually — launched as a real Fabro run; today the "Run for
real" button POSTs to `/api/v1/runs` and redirects to the resulting
`/runs/{id}` page, with a placeholder project/repo/folder picker.

The feature is built as a standalone component subtree under
`apps/fabro-web/app/components/playground/` with no `AppShell` or
`react-router` dependencies, so it can be re-embedded in other contexts
later by passing `chatEndpoint`, `authMode`, and an optional
`realRunRedirect` prop.

## What changed

**Frontend (`apps/fabro-web/`)**

- New `/playground` route + `<Playground>` component tree.
- Live SVG canvas via `@viz-js/viz` with click-to-inspect (read-only
  node detail panel), pan, zoom, fit-to-window, and a simulated walk
  through the graph driven by a Play button.
- Docked chat sidebar (assistant-ui) wired to the new
  `/api/v1/playground/chat` endpoint, with auto-retry on parse failure
  and a playground-specific tool-call summary that reads
  `Wrote workflow.fabro (N nodes, M edges)`.
- File tabs (`workflow.fabro` / `workflow.toml` / `README.md`),
  `.fabro.zip` download via `fflate`, and a "Run for real" toolbar
  button that POSTs an inline `RunManifest` to `/api/v1/runs`.
- Draft persists across page refreshes via `localStorage`.

**Backend (`lib/crates/fabro-server/`)**

- New `POST /api/v1/playground/chat` SSE endpoint. Server is stateless
  across turns: each request carries the full draft, the server runs
  the LLM with a single `write_workflow_file` tool, streams
  `StreamEvent` frames back, and lets the client own diffing/animating
  the result into the canvas.
- Request-size caps before the LLM call (50 messages, 100 nodes, 200
  edges) so a misbehaving or malicious client can't drag multi-MB
  transcripts through token billing.

**Spec / wire contract**

- OpenAPI: new `playground/chat` operation + four new schemas
  (`CreatePlaygroundChatRequest`, `PlaygroundWorkflowDraft`,
  `PlaygroundWorkflowNode`, `PlaygroundWorkflowEdge`).
- `lib/packages/fabro-api-client` not regenerated yet (the playground
  uses raw `fetch`); reviewers who want the TS client to pick up the
  new types can run `bun run generate` in that package.

## Key design decisions

1. **Single `write_workflow_file` tool, not six per-op tools.** The
   first cut exposed `add_node`/`update_node`/`connect`/etc. as
   discrete tool calls. The model would routinely add nodes without
   wiring them up, leaving the canvas in a broken half-state. Pivoted
   to a single tool that takes the full new `workflow.fabro` content;
   the browser parses the DOT, diffs it against the local draft, and
   animates the resulting reducer ops in. The model only has to "get
   the file right", and the canvas still paints node-by-node thanks
   to the client-side animator.

2. **Stateless server.** Each chat turn POSTs the full current draft;
   nothing is persisted server-side. Keeps the endpoint cheap, makes
   refresh-resumption trivial (browser owns the truth), and means the
   same endpoint can later sit behind a rate-limited anonymous variant
   without growing per-session state.

3. **Standalone component subtree.** `<Playground>` has no
   `AppShell`/router/store dependencies. All cross-cutting concerns
   flow in as props (`chatEndpoint`, `authMode`, `realRunRedirect`).
   This is the structural hook that makes future re-embedding possible
   without a refactor.

4. **Chat is the only mutation path.** Click-to-inspect on the canvas
   is read-only. Bi-directional canvas editing was explicitly cut from
   scope to keep one source of truth for "how the workflow changed."

5. **Inline `RunManifest` instead of temp-dir-then-clone.** The
   playground has no project to run against, so the `Run for real`
   modal builds a `RunManifest` that carries the full DOT and
   `workflow.toml` source inline (`workflows[key].{source, config}`).
   `cwd` is pinned to a fixed `/tmp/fabro-playground` constant — no
   LLM-controlled segment in a filesystem-looking field.

6. **React effects policy compliance.** All `useEffect` calls in
   playground component code go through the existing primitives in
   `app/hooks/effects.ts` (`useDocumentEvent`, `useInterval`) or a
   purpose-named hook (`useCanvasRender`).

## Still outstanding (planned follow-ups)

- [ ] **Actually kicking off the ad-hoc run.** "Run for real" today
      POSTs a manifest with a placeholder project/repo/folder
      fieldset. The intent is to reuse the project-picker pattern
      being introduced on the in-flight automations branch — once
      that pattern lands, the disabled inputs in
      `run-for-real-modal.tsx` become the live surface.
- [ ] **Header link to `/playground`.** No nav entry yet; users have
      to type the URL directly.
- [ ] **Live SSE-driven canvas overlay** via
      `GET /api/v1/runs/{id}/attach` — currently the modal redirects
      to the standard run-view page; the "watch it build on the
      playground canvas" experience comes when the `stage.*` events
      are wired through.
- [ ] **Regenerate `lib/packages/fabro-api-client`** so the new types
      ship to TS consumers.
- [ ] **Smoke test:** end-to-end download → unzip →
      `fabro run <name>` round-trip.
- [ ] **`scripts/build.ts` dist-symlink bug:** `pruneOldBuilds` can
      delete the directory `apps/fabro-web/dist` points at, which
      pins the dev server in 503 "build in progress" forever.
      Workaround documented; the real fix is a separate PR.

## Test plan

- [ ] `cd apps/fabro-web && bun run test app/components/playground/` —
111 tests pass
- [ ] `cd apps/fabro-web && bun run typecheck` — clean
- [ ] `cargo test -p fabro-server playground` — 6 tests pass
- [ ] Visit `/playground`; the canvas renders the welcome `start → ??? →
exit` ghost.
- [ ] Type "build me a release-notes workflow" in chat; nodes/edges
animate in; ack reads `Wrote workflow.fabro (N nodes, M edges)`.
- [ ] Click a node → inspector panel populates; click empty canvas →
deselects.
- [ ] Click `Simulate`; nodes light up `start → ... → exit` along the
resolved path.
- [ ] Click `Download .fabro`; unzip; `cd <unzipped> && fabro run
<name>` runs locally.
- [ ] Click `Run for real` → modal opens → confirm → POST succeeds →
redirected to `/runs/{id}` → run executes.
- [ ] Refresh the page; the draft persists from localStorage.
- [ ] Click `Start over` → `Yes`; canvas resets to welcome state.

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-09 11:24:56 -04:00
.ai/prompts Unify fabro run foreground to use create + start + attach (#141) 2026-03-22 22:48:09 -04:00
.cargo refactor(dev): decouple CLI reference generation 2026-05-06 12:31:02 -04:00
.claude refactor: remove devcontainer support (#433) 2026-05-27 12:48:40 -04:00
.config refactor: remove devcontainer support (#433) 2026-05-27 12:48:40 -04:00
.fabro chore: add demo workflows 2026-06-04 18:54:23 -04:00
.github ci: lock Cargo resolution and fix cancellation flake (#461) 2026-05-30 15:07:41 -04:00
apps feat: chat-driven workflow builder at /playground (#450) 2026-06-09 11:24:56 -04:00
bin/agent chore: remove legacy FABRO_JWT_* key generation script 2026-04-25 12:33:03 -04:00
docker fix(server): allow required CSP handoffs 2026-06-01 18:24:45 -04:00
docs feat: chat-driven workflow builder at /playground (#450) 2026-06-09 11:24:56 -04:00
evals/swe-bench refactor(workflow): remove retro stage (#230) 2026-05-09 10:18:20 -04:00
installer refactor(release): simplify pre-release channel to nightly only 2026-04-17 11:39:19 -04:00
lib feat: chat-driven workflow builder at /playground (#450) 2026-06-09 11:24:56 -04:00
test fix(llm): preserve raw compatible tool arguments (#448) 2026-05-28 11:52:36 -04:00
.dockerignore chore: move docker-context/ staging dir under tmp/ 2026-04-25 12:37:45 -04:00
.env.example chore: remove legacy FABRO_JWT_* key generation script 2026-04-25 12:33:03 -04:00
.gitattributes chore: mark generated fabro-api-client as linguist-generated 2026-05-16 17:17:42 -04:00
.gitignore fix(server): make --watch-web honest and fast 2026-05-06 10:36:01 -04:00
AGENTS.md refactor: remove devcontainer support (#433) 2026-05-27 12:48:40 -04:00
bun.lock feat: chat-driven workflow builder at /playground (#450) 2026-06-09 11:24:56 -04:00
Cargo.lock Bump version to 0.259.0-nightly.0 2026-06-09 10:29:35 +00:00
Cargo.toml Bump version to 0.259.0-nightly.0 2026-06-09 10:29:35 +00:00
CLAUDE.md Move CLAUDE.md to AGENTS.md with symlink for compatibility 2026-03-09 13:02:06 -04:00
clippy.toml refactor(docs): split docs/ into public/ and internal/ 2026-04-27 07:21:13 -07:00
CONTRIBUTING.md docs: offer issue-based contribution path alongside PRs 2026-05-16 13:40:23 -04:00
docker-compose.local.yaml chore(compose): drop debug log override for local docker 2026-04-30 08:25:50 -04:00
docker-compose.prod.yaml chore: move Caddyfile into docker/ 2026-04-27 11:23:02 -07:00
docker-compose.split-web.yaml Add split web Docker Compose PoC (#445) 2026-05-28 00:03:00 -04:00
docker-compose.yaml Improve Docker Compose deployment defaults 2026-05-09 18:59:25 -04:00
Dockerfile feat(server): support stdout log destination 2026-04-26 14:52:15 -04:00
install.md Move install files to apps/marketing/public, symlink from repo root 2026-03-16 13:23:58 -04:00
install.sh Move install files to apps/marketing/public, symlink from repo root 2026-03-16 13:23:58 -04:00
LICENSE.md Add README.md and MIT LICENSE 2026-03-10 14:48:58 -04:00
package.json chore: remove stale SQLite references after retirement 2026-04-05 21:29:24 -04:00
README.md docs: accept outside pull requests 2026-05-13 07:27:26 -04:00
rustfmt.toml fmt 2026-04-11 11:27:46 -04:00

Fabro

The open source dark software factory for expert engineers

AI coding agents are powerful but unpredictable. You either babysit every step or review a 50-file diff you don't trust. Fabro gives you a middle path: define the process as a graph, let agents execute it, and intervene only where it matters. Why Fabro?

Rust License: MIT docs Discord

# With Claude Code
curl -fsSL https://fabro.sh/install.md | claude

# With Codex
codex "$(curl -fsSL https://fabro.sh/install.md)"

# With Homebrew
brew install fabro-sh/tap/fabro-nightly

# With Bash
curl -fsSL https://fabro.sh/install.sh | bash

Then run fabro server start to finish setup in your browser. The server opens a web wizard, exits when the wizard completes, and starts in configured mode the next time you run it.

Fabro Runs board showing workflows across Working, Pending, Verify, and Merge stages

Use Cases

  • Extend disengagement time — Stop babysitting an agent REPL. Define a workflow with verification gates and walk away — Fabro keeps the process on track without you.
  • Leverage ensemble intelligence — Seamlessly combine models from different vendors. Use one model to implement, another to cross-critique, and a third to summarize — all in a single workflow.
  • Share best practices across your team — Collaborate on version-controlled workflows that encode your software processes as code. Review, iterate, and reuse them like any other source file.
  • Reduce token bills — Route cheap tasks to fast, inexpensive models and reserve frontier models for the steps that need them. CSS-like stylesheets make this a one-line change.
  • Improve agent security — Run agents in cloud sandboxes with full network and filesystem isolation. Keep untrusted code off your laptop and out of your production environment.
  • Run agents 24/7 — Fabro's API server queues and executes runs continuously. Close your laptop — workflows keep running and results are waiting when you return.
  • Scale infinitely — Move execution off your laptop and into cloud sandboxes. Run as many concurrent workflows as your infrastructure allows.
  • Guarantee code quality — Layer deterministic verifications — test suites, linters, type checkers, LLM-as-judge — into your workflow graph. Failures trigger fix loops automatically.
  • Inspect every run — Query durable event streams, checkpoints, conclusions, and stage outputs to understand what happened and improve the workflow.
  • Specify in natural language — Define requirements as natural-language specs and let Fabro generate — and regenerate — implementations that conform to them.

Key Features

Feature Description
🔀 Deterministic workflow graphs Define pipelines in Graphviz DOT with branching, loops, parallelism, and human gates. Diffable, reviewable, version-controlled
🙋 Human-in-the-loop Approval gates pause for human decisions. Steer running agents mid-turn. Interview steps collect structured input
🎨 Multi-model routing CSS-like stylesheets route each node to the right model and provider, with automatic fallback chains
☁️ Cloud sandboxes Run agents in isolated Daytona cloud VMs with snapshot-based setup, network controls, and automatic cleanup
🔌 SSH access and preview links Shell into running sandboxes with fabro sandbox ssh and expose ports with fabro sandbox preview for live debugging
🌲 Git checkpointing Every stage commits code changes and execution metadata to Git branches. Resume, revert, or trace any change
📊 Run observability Durable events, checkpoints, conclusions, and stage outputs make every run inspectable and exportable
Comprehensive API REST API with SSE event streaming and a React web UI. Run workflows programmatically or as a service
🦀 Single binary, no runtime One compiled Rust executable with zero dependencies. No Python, no Node, no Docker required
⚖️ Open source (MIT) Full source code, no vendor lock-in. Self-host, fork, or extend to fit your workflow

Example Workflow

A plan-approve-implement workflow where a human reviews the plan before the agent writes code:

Plan-Implement workflow graph showing Start → Plan → Approve Plan → Implement → Simplify → Exit with a Revise loop
digraph PlanImplement {
    graph [
        goal="Plan, approve, implement, and simplify a change"
        model_stylesheet="
            *        { model: claude-haiku-4-5; reasoning_effort: low; }
            .coding  { model: claude-sonnet-4-5; reasoning_effort: high; }
        "
    ]

    start [shape=Mdiamond, label="Start"]
    exit  [shape=Msquare, label="Exit"]

    plan      [label="Plan", prompt="Analyze the goal and codebase. Write a step-by-step plan.", reasoning_effort="high"]
    approve   [shape=hexagon, label="Approve Plan"]
    implement [label="Implement", class="coding", prompt="Read plan.md and implement every step."]
    simplify  [label="Simplify", class="coding", prompt="Review the changes for clarity and correctness."]

    start -> plan -> approve
    approve -> implement [label="[A] Approve"]
    approve -> plan      [label="[R] Revise"]
    implement -> simplify -> exit
}

Agents run as multi-turn LLM sessions with tool access. Human gates (hexagon) pause for approval. The stylesheet routes planning to a cheap model and coding to a frontier model. See the Graphviz DOT language reference for the full syntax.


📖 Documentation

Fabro ships with comprehensive documentation covering every feature in depth:

  • Getting Started -- Installation, first workflow, and why Fabro exists
  • Defining Workflows -- Node types, transitions, variables, stylesheets, and human gates
  • Executing Workflows -- Run configuration, sandboxes, checkpoints, observability, and failure handling
  • Tutorials -- Step-by-step guides from hello world to parallel multi-model ensembles
  • API Reference -- Full OpenAPI spec with authentication, SSE events, and client SDKs

Quick Start

Install

# With Claude Code
curl -fsSL https://fabro.sh/install.md | claude

# With Codex
codex "$(curl -fsSL https://fabro.sh/install.md)"

# With Homebrew
brew install fabro-sh/tap/fabro-nightly

# With Bash
curl -fsSL https://fabro.sh/install.sh | bash

Release binaries and the multi-arch Docker image ship with SLSA Build Provenance attestations. See Verifying Releases to check an artifact was built by our GitHub Actions workflow.

Then finish setup in your browser and initialize Fabro in your project:

fabro server start     # opens a web install wizard in your browser
                       # (server exits when the wizard finishes — start it again to run Fabro)

cd my-project
fabro repo init        # per project

For headless or scripted environments, fabro install runs the same setup as a CLI-only wizard.


Running Fabro

Fabro runs as a server. You choose where it runs:

  • On your laptop — install the CLI (above) and run fabro server start. Workflows pause when your laptop sleeps.
  • On a host (self-hosted) — deploy the Docker image with docker compose or any cloud container service (ECS, Cloud Run, Kubernetes). See Self-host with Docker.

One-click managed alternative for the same Docker image:

Deploy on Railway

See the deployment overview for the full picture.


Contributing to Fabro

Outside contributions are welcome! Whether it's a bug fix, a new feature, documentation, or a typo -- we'd love your help making Fabro better.

  • Bug fixes and small improvements -- Send a pull request directly.
  • Larger features or changes -- Open a GitHub Issue or start a Discussion first so we can align on the approach.
  • Questions -- Open a Discussion or email bryan@qlty.sh.

See CONTRIBUTING.md for build instructions and development workflow.


Help or Feedback


License

Fabro is licensed under the MIT License.