diff --git a/.claude/skills/docs/watermark b/.claude/skills/docs/watermark
index 020636661..05531e77e 100644
--- a/.claude/skills/docs/watermark
+++ b/.claude/skills/docs/watermark
@@ -1 +1 @@
-69e7f415d8a31368612cd803ac1e3b90a11055fa
+ec0a612ea531fcf53383afb15ad23561a7bbe6ae
diff --git a/docs/agents/prompts.mdx b/docs/agents/prompts.mdx
index 477e8f2b2..e3ea9f2ec 100644
--- a/docs/agents/prompts.mdx
+++ b/docs/agents/prompts.mdx
@@ -43,6 +43,8 @@ review [label="Review", prompt="@prompts/implement/review.md"]
The `@` prefix tells the engine to read the file contents and use them as the prompt text. This keeps DOT files concise and lets you version prompts as standalone Markdown.
+File references are resolved relative to the DOT file's directory first, then fall back to `~/.fabro/`. This lets you keep shared prompts in your user-level config and reference them from any project.
+
### Variable expansion
Prompts support `$variable` placeholders that expand at runtime. Currently the only built-in variable is `$goal`, which resolves to the graph-level `goal` attribute:
diff --git a/docs/execution/observability.mdx b/docs/execution/observability.mdx
index edb7679b0..a3a3898d7 100644
--- a/docs/execution/observability.mdx
+++ b/docs/execution/observability.mdx
@@ -21,8 +21,8 @@ Events fall into several categories:
| Event | Key fields | Description |
|---|---|---|
-| `WorkflowRunStarted` | `name`, `run_id`, `base_sha`, `run_branch` | Run begins |
-| `WorkflowRunCompleted` | `duration_ms`, `artifact_count`, `total_cost` | Run finishes successfully |
+| `WorkflowRunStarted` | `name`, `run_id`, `base_sha`, `run_branch`, `goal` | Run begins |
+| `WorkflowRunCompleted` | `duration_ms`, `artifact_count`, `total_cost`, `status`, `usage` | Run finishes successfully |
| `WorkflowRunFailed` | `error`, `duration_ms` | Run terminates with an error |
**Stage lifecycle** — events for each node execution:
@@ -56,7 +56,7 @@ Events fall into several categories:
| Event | Key fields | Description |
|---|---|---|
-| `EdgeSelected` | `from_node`, `to_node`, `label`, `condition` | Transition between nodes |
+| `EdgeSelected` | `from_node`, `to_node`, `label`, `condition`, `reason`, `stage_status` | Transition between nodes |
| `LoopRestart` | `from_node`, `to_node` | Loop restart edge taken |
| `CheckpointCompleted` | `node_id`, `git_commit_sha` (optional) | Checkpoint saved (with git SHA when git is enabled) |
| `GitCommit` | `node_id`, `sha` | Git commit created |
@@ -67,6 +67,9 @@ Events fall into several categories:
| `GitFetch` | `branch`, `success` | Git fetch attempted |
| `GitReset` | `sha` | Git reset executed |
| `Failover` | `stage`, `from_provider`, `to_provider`, `error` | LLM provider failover |
+| `RetroStarted` | — | Retrospective generation begins |
+| `RetroCompleted` | `duration_ms` | Retrospective generation finished |
+| `RetroFailed` | `error`, `duration_ms` | Retrospective generation failed |
**Parallel execution:**
diff --git a/docs/execution/run-configuration.mdx b/docs/execution/run-configuration.mdx
index 12fb4ba17..27c330da9 100644
--- a/docs/execution/run-configuration.mdx
+++ b/docs/execution/run-configuration.mdx
@@ -352,6 +352,21 @@ draft = true
| `enabled` | When `true`, Fabro creates a PR from the agent's working branch after a successful run. Default: `false`. |
| `draft` | When `true`, the PR is created as a draft pull request. Default: `true`. |
+### `[github]`
+
+Request a scoped GitHub Installation Access Token and inject it into the sandbox as `GITHUB_TOKEN`. The token is minted from the configured [GitHub App](/integrations/github) with only the permissions you specify.
+
+```toml title="run.toml"
+[github]
+permissions = { contents = "write", pull_requests = "read" }
+```
+
+| Field | Description |
+|---|---|
+| `permissions` | Map of GitHub API permission names to access levels (`"read"` or `"write"`). Only the listed permissions are requested. |
+
+This requires a GitHub App to be configured. If the app is missing or the repository doesn't have an installation, the run logs a warning and continues without injecting the token.
+
### `[[hooks]]`
Define hooks that run in response to lifecycle events. Each hook is a TOML array entry:
@@ -409,14 +424,37 @@ Settings can come from multiple sources. Fabro resolves them in this order (firs
| Node-level [stylesheet](/workflows/stylesheets) | Highest |
| Run config TOML | |
| CLI flags (`--model`, `--provider`, `--sandbox`) | |
+| Project defaults (`fabro.toml`) | |
| Server defaults (`~/.fabro/server.toml`) | |
| DOT graph attributes (`default_model`, `default_provider`) | |
| Built-in defaults | Lowest |
-For model and provider specifically, the precedence is: CLI flags > TOML config > server defaults > DOT graph attributes > built-in defaults. Stylesheet rules on individual nodes always take priority over all of these.
+For model and provider specifically, the precedence is: CLI flags > TOML config > project defaults > server defaults > DOT graph attributes > built-in defaults. Stylesheet rules on individual nodes always take priority over all of these.
+### Project defaults (`fabro.toml`)
+
+The `fabro.toml` project config can set default values for `[llm]`, `[setup]`, `[sandbox]`, `[vars]`, `[checkpoint]`, `[pull_request]`, `[github]`, `[assets]`, `[[hooks]]`, and `[mcp_servers]`. These defaults apply to all runs in the project unless the run config overrides them:
+
+```toml title="fabro.toml"
+version = 1
+
+[llm]
+model = "claude-sonnet-4-5"
+
+[sandbox]
+provider = "daytona"
+
+[sandbox.daytona.snapshot]
+name = "my-project-snapshot"
+
+[github]
+permissions = { contents = "write" }
+```
+
+Project defaults are merged with run config values using the same rules as server defaults — run config wins on key collisions.
+
### Server defaults
When running via `fabro serve`, the server config at `~/.fabro/server.toml` can set default values for `[llm]`, `[setup]`, `[sandbox]`, and `[vars]`. These defaults are applied to every run unless the run config overrides them.
diff --git a/docs/integrations/github.mdx b/docs/integrations/github.mdx
index 2f54eebd4..8b2e26478 100644
--- a/docs/integrations/github.mdx
+++ b/docs/integrations/github.mdx
@@ -13,6 +13,7 @@ Fabro uses a [GitHub App](https://docs.github.com/en/apps/overview) to authentic
| **Private repo cloning** | Daytona and Docker sandboxes clone private repositories using short-lived Installation Access Tokens |
| **Checkpoint pushing** | After each workflow stage, Fabro pushes the run branch and metadata branch back to origin from inside the sandbox |
| **Auto-PR** | When `[pull_request] enabled = true` in the [run config](/execution/run-configuration#pull_request), Fabro opens a PR from the agent's working branch after a successful run |
+| **Sandbox GITHUB_TOKEN** | When `[github] permissions` are declared in the run config, Fabro mints a scoped Installation Access Token and injects it as `GITHUB_TOKEN` in the sandbox |
## Setup
@@ -134,6 +135,19 @@ When a workflow runs in a remote sandbox (Daytona or Docker), Fabro clones the c
For public repositories, the clone works without credentials. The token is still generated because it's needed for pushing checkpoints.
+### GITHUB_TOKEN injection
+
+When a run config declares `[github] permissions`, Fabro mints a scoped Installation Access Token at startup and injects it into the sandbox as the `GITHUB_TOKEN` environment variable. Agents running inside the sandbox can use this token for GitHub API calls, cloning additional private repos, or pushing to branches.
+
+```toml title="run.toml"
+[github]
+permissions = { contents = "write", pull_requests = "write" }
+```
+
+Only the listed permissions are requested — the token is scoped to the minimum access needed. If the GitHub App isn't configured or the repository lacks an installation, the run logs a warning and continues without the token.
+
+This also works in `fabro.toml` as a project-level default, so all workflows in the project automatically get a `GITHUB_TOKEN` without repeating the config in each run TOML.
+
### Checkpoint pushing
After each workflow stage, Fabro [checkpoints](/execution/checkpoints) by pushing the run branch and metadata branch to origin. Inside remote sandboxes, the git remote URL is configured with the Installation Access Token for authenticated pushing.
diff --git a/docs/reference/cli.mdx b/docs/reference/cli.mdx
index 08651103d..4b5a77a56 100644
--- a/docs/reference/cli.mdx
+++ b/docs/reference/cli.mdx
@@ -63,29 +63,49 @@ fabro run --run-branch fabro/run/abc123
| `--no-retro` | Skip retro generation after the run |
| `--ssh` | Create SSH access to the sandbox (Daytona or exe.dev) and print the connection command |
| `--preserve-sandbox` | Keep the sandbox alive after the run finishes (for debugging) |
+| `-d, --detach` | Fork the workflow as a background process and print the run ID. Reconnect later with `fabro logs -f`. |
-`--preflight` conflicts with `--resume`, `--run-branch`, and `--dry-run`. `--run-branch` conflicts with `--resume`.
+`--preflight` conflicts with `--resume`, `--run-branch`, `--dry-run`, and `--detach`. `--run-branch` conflicts with `--resume`.
## `fabro ps`
-List workflow runs stored in `~/.fabro/runs`.
+List workflow runs. By default, shows only active (running) runs — similar to `docker ps`. Use `-a` to include completed runs.
```bash
-fabro ps
+fabro ps # active runs only
+fabro ps -a # all runs including completed
fabro ps --workflow deploy --label env=prod
fabro ps --json
```
+The table shows run ID, status, workflow name, goal, and timing.
+
| Flag | Description |
|---|---|
+| `-a, --all` | Show all runs, not just active ones |
| `--before ` | Only show runs started before this date (YYYY-MM-DD prefix match) |
| `--workflow ` | Filter by workflow name (substring match) |
| `--label ` | Filter by label (repeatable, AND semantics) |
| `--orphans` | Include orphan directories (no `manifest.json`) |
| `--json` | Output as JSON |
+## `fabro rm`
+
+Remove one or more workflow runs by ID or workflow name. Cleans up both the run directory and any associated sandbox.
+
+```bash
+fabro rm ...
+fabro rm abc123
+fabro rm my-workflow --force
+```
+
+| Argument / Flag | Description |
+|---|---|
+| `...` | Run IDs or workflow names to remove (required, repeatable) |
+| `-f, --force` | Force removal of active runs |
+
## `fabro system prune`
Delete old workflow runs. Dry-run by default — pass `--yes` to actually delete.
@@ -404,6 +424,37 @@ fabro run --run-branch fabro/run/
See [Checkpoints](/execution/checkpoints#rewinding-to-an-earlier-checkpoint) for background on how checkpointing works.
+## `fabro logs`
+
+View the event log of a workflow run. Runs can be referenced by ID prefix or workflow name (uses the most recent run).
+
+```bash
+fabro logs my-workflow
+fabro logs abc123 --pretty
+fabro logs -f my-workflow -p
+```
+
+| Argument / Flag | Description |
+|---|---|
+| `` | Run ID prefix or workflow name (required) |
+| `-f, --follow` | Follow log output in real time |
+| `-p, --pretty` | Formatted colored output with rendered assistant messages and tool calls |
+| `--since ` | Show logs since a timestamp or relative duration (e.g. `42m`, `2h`, `2026-01-02T13:00:00Z`) |
+| `-n, --tail ` | Show only the last N lines |
+
+## `fabro inspect`
+
+Show detailed JSON data for a workflow run, including its manifest, conclusion, checkpoint, and sandbox record.
+
+```bash
+fabro inspect
+fabro inspect abc123
+```
+
+| Argument | Description |
+|---|---|
+| `` | Run ID prefix or workflow name (required) |
+
## `fabro workflow list`
List all available workflows in the current project. Shows workflows from both the project directory and user-level `~/.fabro/workflows/`, grouped by source with descriptions pulled from each workflow's goal.
@@ -412,6 +463,22 @@ List all available workflows in the current project. Shows workflows from both t
fabro workflow list
```
+## `fabro workflow create`
+
+Scaffold a new workflow in the project's workflows directory. Creates a `workflow.fabro` graph file and a `workflow.toml` run config.
+
+```bash
+fabro workflow create my-workflow
+fabro workflow create my-workflow --goal "Run the CI pipeline"
+```
+
+| Argument / Flag | Description |
+|---|---|
+| `` | Name of the workflow (required) |
+| `-g, --goal ` | Goal description for the workflow |
+
+Requires a `fabro.toml` project config in the current directory or a parent.
+
---
## `fabro validate`