docs: sync public docs to recent runtime changes

Reflect Docker as the default sandbox provider, add `skip_clone` for
clone-based providers, document the `[run.sandbox.docker]` config
table, and update tutorial command lines from `files-internal/...` to
`docs/internal/...`. Bump the docs skill watermark to the latest synced
commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Bryan Helmkamp 2026-04-30 08:35:53 -04:00
parent d92ee47fe3
commit 157ec03a18
No known key found for this signature in database
18 changed files with 111 additions and 56 deletions

View file

@ -1 +1 @@
d2cc37c615894d56ef672d00004ce13ce3b328c2
c8185eaee6797e5f48963fcfff688838c5ff497d

View file

@ -9,7 +9,7 @@ Fabro supports three sandbox providers: `local` (no isolation), `docker` (contai
## Network access control
For cloud sandboxes (Daytona), you can control outbound network access with the `network` field in `[sandbox.daytona]`. Three modes are available: `"allow_all"` (default), `"block"`, and `{ allow_list = ["..."] }` for CIDR-based egress filtering.
For cloud sandboxes (Daytona), you can control outbound network access with the `network` field in `[run.sandbox.daytona]`. Three modes are available: `"allow_all"` (default), `"block"`, and `{ allow_list = ["..."] }` for CIDR-based egress filtering.
Server defaults in `settings.toml` apply when a run config doesn't specify `network`. Individual run configs can override the server default.

View file

@ -67,12 +67,12 @@ When the engine hits a [parallel fan-out node](/workflows/stages-and-nodes#paral
## Sandboxes
Node handlers execute tools (bash commands, file edits) inside a **sandbox**. Fabro supports six sandbox providers:
Node handlers execute tools (bash commands, file edits) inside a **sandbox**. Fabro supports three sandbox providers:
| Sandbox | Description |
|---|---|
| `local` | Tools run directly on the host machine (default) |
| `docker` | Tools run inside a Docker container |
| `docker` | Tools run inside a Docker container (default) |
| `local` | Tools run directly on the host machine |
| `daytona` | Tools run in a cloud VM with SSH access |
The sandbox is configured per-run via CLI flags (`--sandbox docker`) or the run config TOML. See [Environments](/execution/environments) for details on each provider.

View file

@ -81,8 +81,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.fabro --model claude-opus-4-6
fabro run files-internal/demo/04-pipeline.fabro --model gemini-3.1-pro-preview
fabro run docs/internal/demo/01-hello.fabro --model claude-opus-4-6
fabro run docs/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.

View file

@ -255,28 +255,30 @@ This is more reliable than a single "implement everything" node because:
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"
_version = 1
[workflow]
graph = "build-solitaire.fabro"
[llm]
model = "claude-sonnet-4-5"
[run]
goal = "Build a terminal-based solitaire (Klondike) game in Python"
[run.model]
name = "claude-sonnet-4-5"
provider = "anthropic"
fallbacks = ["openai", "gemini"]
[llm.fallbacks]
anthropic = ["openai", "gemini"]
[[run.prepare.steps]]
script = "python3 -m venv .venv && . .venv/bin/activate && pip install pytest curses"
[setup]
commands = ["python3 -m venv .venv && . .venv/bin/activate && pip install pytest curses"]
[sandbox]
[run.sandbox]
provider = "daytona"
[sandbox.daytona.snapshot]
[run.sandbox.daytona.snapshot]
name = "python-dev"
cpu = 4
memory = 8
disk = 20
memory = "8GB"
disk = "20GB"
dockerfile = "FROM python:3.12-slim\nRUN apt-get update && apt-get install -y git libncurses-dev"
```

View file

@ -18,10 +18,10 @@ The run branch is a regular Git branch that grows one commit per completed node.
### Run branch commits
After each node finishes, Fabro stages file changes and creates a commit on the run branch. Files matching `[checkpoint] exclude_globs` patterns (configured in [run.toml](/execution/run-configuration#checkpoint) or [settings.toml](/administration/server-configuration#checkpoint-section)) are excluded from staging:
After each node finishes, Fabro stages file changes and creates a commit on the run branch. Files matching `[run.checkpoint] exclude_globs` patterns (configured in [run.toml](/execution/run-configuration#runcheckpoint) or [settings.toml](/administration/server-configuration#runcheckpoint-section)) are excluded from staging:
```
fabro(01JKXYZ...): plan (success)
fabro(01JKXYZ...): plan (succeeded)
Fabro-Run: 01JKXYZ...
Fabro-Completed: 2

View file

@ -30,7 +30,7 @@ Fabro looks for `.devcontainer/devcontainer.json` in the repository root. If fou
| `onCreateCommand` | Runs inside the sandbox after it's created |
| `postCreateCommand` | Runs after `onCreateCommand` completes |
| `postStartCommand` | Runs after the sandbox starts |
| `containerEnv` | Merged into sandbox environment variables (TOML `[sandbox.env]` values take precedence on key collisions) |
| `containerEnv` | Merged into sandbox environment variables (TOML `[run.sandbox.env]` values take precedence on key collisions) |
Lifecycle commands (`onCreateCommand`, `postCreateCommand`, `postStartCommand`) execute sequentially inside the sandbox. If any command fails, the run aborts before the workflow starts.
@ -40,6 +40,6 @@ Fabro detects and reports unsupported `COPY` and `ADD` instructions in devcontai
## Interaction with other sandbox settings
When `devcontainer = true`, the devcontainer Dockerfile overrides any `snapshot.dockerfile` setting in `[sandbox.daytona]`. Other Daytona settings (`cpu`, `memory`, `disk`, `auto_stop_interval`, `labels`) still apply.
When `devcontainer = true`, the devcontainer Dockerfile overrides any `snapshot.dockerfile` setting in `[run.sandbox.daytona]`. Other Daytona settings (`cpu`, `memory`, `disk`, `auto_stop_interval`, `labels`) still apply.
Environment variables from `containerEnv` in the devcontainer config are merged with `[sandbox.env]` from the TOML config. On key collisions, the TOML config wins.
Environment variables from `containerEnv` in the devcontainer config are merged with `[run.sandbox.env]` from the TOML config. On key collisions, the TOML config wins.

View file

@ -30,11 +30,11 @@ fabro run workflow.fabro --sandbox daytona
provider = "daytona"
```
The precedence order is: CLI flag > run config TOML > server defaults > built-in default (`local`).
The precedence order is: CLI flag > run config TOML > server defaults > built-in default (`docker`).
## Local
The local sandbox runs all tool operations directly on the host machine. It's the default and the simplest option — no setup required beyond the Fabro binary itself.
The local sandbox runs all tool operations directly on the host machine. Use it for trusted workflows, local development, or runs that must operate directly on the current working tree.
### How it works
@ -53,33 +53,48 @@ The local sandbox offers no isolation. Agents can read and modify any file on th
## Docker
The Docker sandbox runs all tool operations inside a Docker container. Docker runs use a provider-owned workspace in the container; when the run has a GitHub origin, Fabro clones that repository into the workspace.
The Docker sandbox runs all tool operations inside a Docker container. Docker is the built-in default runtime provider. Docker runs use a provider-owned workspace in the container; when the run has a GitHub origin, Fabro clones that repository into the workspace.
### Prerequisites
- Docker Engine running on the host
- The configured image available locally (or `auto_pull` enabled)
- The configured image available locally, or a Docker client that can pull it
- GitHub credentials configured when cloning private repositories
### How it works
- **Container lifecycle** — On `initialize()`, Fabro pulls the image (if needed), creates a container with `sleep infinity`, and starts it. On `cleanup()`, Fabro stops and removes the container.
- **Working directory** — Docker runs use a provider-owned workspace inside the container. When a run has a GitHub origin, Fabro clones that repository into the workspace instead of bind-mounting the host source tree.
- **Git clone** — Docker and Daytona accept GitHub origins for automatic cloning. If the run has a present non-GitHub origin, set `skip_clone = true` or switch to `local`.
- **Commands** — Executed via `docker exec` with `/bin/bash -c` inside the container. Timeout and cancellation are supported.
- **File writes** — Use the Docker API's tar upload to avoid shell escaping issues with special characters.
- **Platform detection** — The container's `uname -r` is cached at startup.
### Configuration
The Docker sandbox is configured through the `DockerSandboxConfig`:
Configure Docker through `[run.sandbox.docker]`:
```toml title="run.toml"
[run.sandbox]
provider = "docker"
[run.sandbox.docker]
image = "buildpack-deps:noble"
network_mode = "bridge"
memory_limit = "4GB"
cpu_quota = 200000
skip_clone = false
```
| Setting | Default | Description |
|---|---|---|
| `image` | `fabro-agent:latest` | Docker image to use |
| `image` | `buildpack-deps:noble` | Docker image to use |
| `network_mode` | `bridge` | Docker network mode |
| `memory_limit` | unlimited | Memory limit in bytes |
| `cpu_quota` | unlimited | CPU quota (microseconds per 100ms period) |
| `auto_pull` | `true` | Pull the image if not found locally |
| `env_vars` | `[]` | Additional `KEY=VALUE` environment variables |
| `memory_limit` | `4GB` | Memory limit |
| `cpu_quota` | `200000` | CPU quota (microseconds per 100ms period) |
| `skip_clone` | `false` | Create an empty workspace instead of cloning the run's GitHub origin |
When `skip_clone = true`, the sandbox starts with an empty provider workspace. Use [prepare steps](/execution/run-configuration#runprepare) to clone or create any files the workflow needs.
### Preserving the container
@ -106,13 +121,13 @@ The Daytona sandbox runs all tool operations inside a cloud-hosted VM managed by
### Prerequisites
- A `DAYTONA_API_KEY` environment variable
- A GitHub App configured via `fabro install` (for private repository cloning)
- GitHub access configured via `fabro install` or `gh auth login` (for private repository cloning)
### How it works
- **Sandbox lifecycle** — On `initialize()`, Fabro creates a Daytona sandbox (from an image or a snapshot), clones the current git repository into it, and waits until it's ready. On `cleanup()`, the sandbox is deleted.
- **Working directory** — Fixed at `/home/daytona/workspace`. The current repository is cloned there automatically.
- **Git clone** — Fabro detects the local `origin` remote URL and current branch, converts SSH URLs to HTTPS, and clones into the sandbox. For private repositories, Fabro uses a GitHub App Installation Access Token scoped to `contents: read` on the specific repository. Public repositories are cloned without credentials. If no git repo is detected, the working directory is created empty.
- **Sandbox lifecycle** — On `initialize()`, Fabro creates a Daytona sandbox (from an image or a snapshot), clones the run's GitHub origin into it when one is available, and waits until it's ready. On `cleanup()`, the sandbox is deleted.
- **Working directory** — Fixed at `/home/daytona/workspace`. GitHub-origin runs clone the repository there automatically.
- **Git clone** — Fabro detects the run manifest's GitHub origin URL and branch, converts SSH URLs to HTTPS, and clones into the sandbox. For private repositories, Fabro uses a GitHub App Installation Access Token scoped to the specific repository. Public repositories are cloned without credentials. Set `skip_clone = true` to create an empty workspace instead.
- **Commands** — Executed via the Daytona process API. Commands are base64-encoded and piped through `sh` to support pipes, environment variables, and other shell features.
- **Ephemeral** — Sandboxes are created with `ephemeral: true` and a unique timestamped name (e.g. `fabro-20260305-142301-a3f2`).
@ -126,6 +141,7 @@ provider = "daytona"
[run.sandbox.daytona]
auto_stop_interval = 60
skip_clone = false
[run.sandbox.daytona.snapshot]
name = "rust-dev"

View file

@ -165,10 +165,35 @@ preserve = true
| Field | Description |
|---|---|
| `provider` | Sandbox mode: `local` (default), `docker`, or `daytona`. |
| `provider` | Sandbox mode: `docker` (default), `local`, or `daytona`. |
| `preserve` | When `true`, keep the sandbox alive after the run finishes. Useful for debugging. |
| `devcontainer` | When `true`, use the repo's `devcontainer.json` to configure the sandbox. See [Devcontainers](/execution/devcontainers). |
#### `[run.sandbox.docker]`
Additional settings when using the Docker sandbox:
```toml title="run.toml"
[run.sandbox]
provider = "docker"
[run.sandbox.docker]
image = "buildpack-deps:noble"
network_mode = "bridge"
memory_limit = "4GB"
cpu_quota = 200000
skip_clone = false
```
| Field | Description |
|---|---|
| `image` | Docker image used for the run container. Defaults to `buildpack-deps:noble`. |
| `network_mode` | Docker network mode. Defaults to `bridge`. |
| `memory_limit` | Memory limit using human-readable units such as `"4GB"` or `"512MiB"`. |
| `cpu_quota` | Docker CPU quota in microseconds per 100ms period. Defaults to `200000`. |
| `env_vars` | Provider-level environment variables passed to the Docker container. For workflow tool execution, prefer `[run.sandbox.env]`. |
| `skip_clone` | When `true`, start with an empty container workspace instead of cloning the run's GitHub origin. |
#### `[run.sandbox.daytona]`
Additional settings when using the Daytona cloud sandbox:
@ -201,6 +226,7 @@ dockerfile = "FROM rust:1.85-slim-bookworm\nRUN apt-get update"
| `snapshot.disk` | Disk size using the same units as `memory`. |
| `snapshot.dockerfile` | Dockerfile content (inline string) or path (`{ path = "..." }`) for building the snapshot image. Paths are resolved relative to the TOML file's directory. |
| `network` | Network access mode: `"allow_all"` (default), `"block"`, or `{ allow_list = ["..."] }`. See [Sandboxing](/administration/sandboxing#network-access-control). |
| `skip_clone` | When `true`, start with an empty Daytona workspace instead of cloning the run's GitHub origin. |
#### `[run.sandbox.local]`

View file

@ -42,6 +42,7 @@ preserve = false
[run.sandbox.daytona]
auto_stop_interval = 60
skip_clone = false
[run.sandbox.daytona.labels]
project = "fabro"
@ -83,7 +84,7 @@ Use `"block"` or a CIDR allow list when running untrusted or generated code to p
Snapshots let you pre-build an environment image so each run starts with dependencies already installed rather than installing them in setup commands every time.
```toml title="run.toml"
[sandbox.daytona.snapshot]
[run.sandbox.daytona.snapshot]
name = "my-snapshot"
cpu = 4
memory = 8
@ -99,7 +100,17 @@ If a snapshot is configured by name but doesn't exist and no `dockerfile` is pro
## Private repositories
Fabro automatically clones the current repository into the sandbox at `/home/daytona/workspace`. Public repositories work without extra configuration. Private repositories require GitHub access. In `token` mode, Fabro uses the stored token directly. In `app` mode, Fabro uses short-lived Installation Access Tokens scoped to the specific repository.
Fabro automatically clones the run's GitHub origin into the sandbox at `/home/daytona/workspace`. Public repositories work without extra configuration. Private repositories require GitHub access. In `token` mode, Fabro uses the stored token directly. In `app` mode, Fabro uses short-lived Installation Access Tokens scoped to the specific repository.
Set `skip_clone = true` in `[run.sandbox.daytona]` when a workflow should start with an empty Daytona workspace instead of cloning the run origin:
```toml title="run.toml"
[run.sandbox]
provider = "daytona"
[run.sandbox.daytona]
skip_clone = true
```
If the clone fails without GitHub access configured, Fabro suggests running the setup flow:
@ -167,7 +178,7 @@ The `DAYTONA_API_KEY` environment variable is missing or invalid. Store it with
### "Snapshot does not exist and no dockerfile provided"
The run config references a snapshot name that doesn't exist on Daytona, and no `dockerfile` is provided to create it. Either create the snapshot manually in the Daytona dashboard or add a `dockerfile` field to `[sandbox.daytona.snapshot]`.
The run config references a snapshot name that doesn't exist on Daytona, and no `dockerfile` is provided to create it. Either create the snapshot manually in the Daytona dashboard or add a `dockerfile` field to `[run.sandbox.daytona.snapshot]`.
### "Timed out waiting for snapshot to become active"

View file

@ -327,7 +327,7 @@ gate -> slow_path
Instead of inlining long prompts, reference an external file:
```dot
simplify [label="Simplify", prompt="@files-internal/prompts/simplify.md"]
simplify [label="Simplify", prompt="@prompts/simplify.md"]
```
The `@` prefix tells Fabro to load the prompt from a file path relative to the workflow file. Paths support `~` (home directory) and `..` (parent directory):

View file

@ -31,7 +31,7 @@ digraph BranchLoop {
```
```bash
fabro run files-internal/demo/05-branch-loop.fabro
fabro run docs/internal/demo/05-branch-loop.fabro
```
## Command nodes

View file

@ -52,7 +52,7 @@ digraph Ensemble {
```
```bash
fabro run files-internal/demo/11-ensemble.fabro
fabro run docs/internal/demo/11-ensemble.fabro
```
<Note>

View file

@ -34,7 +34,7 @@ digraph Hello {
Run it:
```bash
fabro run files-internal/demo/01-hello.fabro
fabro run docs/internal/demo/01-hello.fabro
```
### What's happening
@ -68,7 +68,7 @@ digraph ToolUse {
```
```bash
fabro run files-internal/demo/02-tool-use.fabro
fabro run docs/internal/demo/02-tool-use.fabro
```
### What's happening
@ -108,7 +108,7 @@ digraph SubAgent {
```
```bash
fabro run files-internal/demo/03-subagent.fabro
fabro run docs/internal/demo/03-subagent.fabro
```
### What's happening

View file

@ -36,7 +36,7 @@ digraph MultiModel {
```
```bash
fabro run files-internal/demo/08-multi-model.fabro
fabro run docs/internal/demo/08-multi-model.fabro
```
## Model stylesheets

View file

@ -40,7 +40,7 @@ digraph Parallel {
```
```bash
fabro run files-internal/demo/06-parallel.fabro
fabro run docs/internal/demo/06-parallel.fabro
```
## Fan-out with the fork node

View file

@ -22,7 +22,7 @@ digraph PlanImplement {
plan [label="Plan", prompt="Analyze the goal and codebase. Write a clear, step-by-step implementation plan to a Markdown file called plan.md. Include what files will change and why.", reasoning_effort="high"]
approve [shape=hexagon, label="Approve Plan"]
implement [label="Implement", prompt="Read plan.md and implement every step. Make all the code changes described in the plan."]
simplify [label="Simplify", prompt="@files-internal/prompts/simplify.md"]
simplify [label="Simplify", prompt="@prompts/simplify.md"]
start -> plan -> approve
approve -> implement [label="[A] Approve"]
@ -32,7 +32,7 @@ digraph PlanImplement {
```
```bash
fabro run files-internal/demo/10-plan-implement.fabro
fabro run docs/internal/demo/10-plan-implement.fabro
```
## Human gates
@ -77,10 +77,10 @@ The `plan` node sets `reasoning_effort="high"`. This tells the model to think ha
## Prompt file references
The `simplify` node uses `@files-internal/prompts/simplify.md` instead of an inline prompt string:
The `simplify` node uses `@prompts/simplify.md` instead of an inline prompt string:
```dot
simplify [label="Simplify", prompt="@files-internal/prompts/simplify.md"]
simplify [label="Simplify", prompt="@prompts/simplify.md"]
```
The `@` prefix tells Fabro to load the prompt from a Markdown file, resolved relative to the Graphviz file's location. This keeps Graphviz files concise and lets you version prompts as standalone files. See [Prompts](/agents/prompts) for details.

View file

@ -62,7 +62,7 @@ digraph SubWorkflow {
```
```bash
fabro run files-internal/demo/12-sub-workflow.fabro
fabro run docs/internal/demo/12-sub-workflow.fabro
```
## The house node