diff --git a/.claude/skills/changelog/watermark b/.claude/skills/changelog/watermark index 186f2ae9f..343e9427d 100644 --- a/.claude/skills/changelog/watermark +++ b/.claude/skills/changelog/watermark @@ -1 +1 @@ -074f90c3915b477486210e429cfc4581815e034b +678e75e2f35ae90e70c4b369f74ec202b168982d diff --git a/.claude/skills/docs/watermark b/.claude/skills/docs/watermark index 186f2ae9f..8afd61986 100644 --- a/.claude/skills/docs/watermark +++ b/.claude/skills/docs/watermark @@ -1 +1 @@ -074f90c3915b477486210e429cfc4581815e034b +7ad164c45de64e7bafdadb26f519f6e0c8a00a42 diff --git a/docs/public/administration/server-configuration.mdx b/docs/public/administration/server-configuration.mdx index fcffad86f..4499651dc 100644 --- a/docs/public/administration/server-configuration.mdx +++ b/docs/public/administration/server-configuration.mdx @@ -253,6 +253,16 @@ configuration path. If you need MinIO, R2, or another S3-compatible backend, con `[server.slatedb]` and `[server.artifacts]` directly in `settings.toml`. The runtime still honors those hand-edited values even though the browser wizard does not manage them. +### SQLite state and migration backups + +Shared relational state, including vault entries and server-managed definitions, lives at `/db/fabro.sqlite3`. Run events continue to use the `[server.slatedb]` object store. + +Before applying pending SQLite migrations, Fabro creates `/db/fabro.sqlite3.pre-migration.bak` with SQLite's `VACUUM INTO`. Each migration run replaces the previous snapshot, so only the most recent pre-migration backup is retained. + +On the first compatible startup after upgrading from the file-backed vault, Fabro imports `/vaults/default/secrets.json` into SQLite. Existing SQLite rows win on name conflicts. After a successful import, the source file is renamed to `secrets.json.imported-.bak`. This temporary compatibility importer is scheduled for removal after 2026-10-11. + +Vault values and their migration backups retain Fabro's plaintext-at-rest behavior. Protect the storage volume and backup file with the same access controls as the secrets they contain. + ### Run defaults The `[run.*]` sections in `settings.toml` act as defaults for every run. @@ -339,7 +349,7 @@ Configure checkpoint behavior for all runs. Fabro splits server-runtime secrets into two scopes: - Bootstrap secrets live in process env or `/server.env` and resolve with precedence `process env -> server.env`. -- Optional integration secrets live in `/vaults/default/secrets.json` (the vault). Anything stored in the vault may be used by workflows. +- Optional integration secrets live in the server vault in the shared SQLite database. Anything stored in the vault may be used by workflows. `server.env` is only for bootstrap/runtime values the server may need before optional integrations are loaded: @@ -453,7 +463,7 @@ GitHub App mode stores these secrets in the vault. `fabro install` writes them a ### Slack integration (optional) -Slack credentials are server-level secrets. Add `[server.integrations.slack]` to enable one Slack connection that is shared by human interview prompts and run lifecycle notifications. `server.integrations.slack.default_channel` is optional and is used only as the default destination for interview prompts; lifecycle notifications use `[run.notifications..slack].channel` in run or workflow configuration. +Slack credentials are server-level secrets. Add `[server.integrations.slack]` to enable one Slack connection that is shared by human interview prompts and run lifecycle notifications. `server.integrations.slack.default_channel` is an optional literal channel name used only as the default destination for interview prompts; it does not interpolate `{{ env.* }}`. Lifecycle notifications use `[run.notifications..slack].channel` in run or workflow configuration. Fabro resolves these from the vault only. When `[server.integrations.slack]` is present and both credentials are present, startup logs `Slack integration enabled` and then the Slack Socket Mode connection status. If the Slack config table is absent or `enabled = false`, startup logs `Slack integration disabled by server configuration`. If the table is present but either credential is missing or empty, startup logs `Slack integration disabled; missing credentials` with the missing variable names. diff --git a/docs/public/administration/troubleshooting.mdx b/docs/public/administration/troubleshooting.mdx index a228c5ab8..25086ff1d 100644 --- a/docs/public/administration/troubleshooting.mdx +++ b/docs/public/administration/troubleshooting.mdx @@ -14,9 +14,10 @@ fabro doctor --server https://fabro.example.com/api/v1 ``` It checks: + - Local user config and storage directory health - Server-reported LLM provider connectivity, with configured providers probed concurrently -- GitHub App, sandbox, and Brave Search credentials +- GitHub App, sandbox, and Brave Search credentials, plus Docker daemon reachability when the Docker sandbox provider is enabled - Server authentication and crypto configuration LLM provider probe failures are reported as errors. Use `--verbose` to see the underlying provider error chain when a key, network route, or model endpoint fails. @@ -31,7 +32,7 @@ LLM provider probe failures are reported as errors. Use `--verbose` to see the u **Stall watchdog timeouts** — If runs are cancelled unexpectedly, the agent may be stuck or the LLM provider may be slow. Check `FABRO_LOG=debug` output for `Agent.LlmRetry` events. Increase `stall_timeout` in the graph if needed, or add [fallback providers](/core-concepts/models) to handle outages. -**Sandbox creation failures** — For Docker: ensure the Docker daemon is running and the configured image exists. For Daytona: verify `DAYTONA_API_KEY` is stored in the server vault, includes `write:snapshots`, `delete:snapshots`, `write:sandboxes`, and `delete:sandboxes`, and that GitHub access is configured. For Exe: verify your SSH keys are configured for `exe.dev` and that `ssh exe.dev` connects successfully. +**Sandbox creation failures** — For Docker: run `fabro doctor`; when Docker is enabled, it pings the daemon and reports whether to start Docker, fix socket permissions, or disable the provider. Also ensure the configured image exists. For Daytona: verify `DAYTONA_API_KEY` is stored in the server vault, includes `write:snapshots`, `delete:snapshots`, `write:sandboxes`, and `delete:sandboxes`, and that GitHub access is configured. For Exe: verify your SSH keys are configured for `exe.dev` and that `ssh exe.dev` connects successfully. **Port already in use** — Change the port with `fabro server start --port 3001` or stop the conflicting process. diff --git a/docs/public/agents/mcp.mdx b/docs/public/agents/mcp.mdx index 76fb68054..48b5c4304 100644 --- a/docs/public/agents/mcp.mdx +++ b/docs/public/agents/mcp.mdx @@ -121,7 +121,7 @@ Each server entry specifies a transport type and optional timeouts. The server n ### Server-managed catalog -Fabro servers can also manage a shared MCP catalog through the MCP servers REST API. Workflows reference a catalog definition by id instead of repeating its transport configuration: +Fabro servers can also manage a shared MCP catalog from **Settings → MCP servers** in the web UI or through the MCP servers REST API. Workflows reference a catalog definition by id instead of repeating its transport configuration: ```toml [run.agent.mcps.sentry] @@ -134,6 +134,28 @@ When upgrading an installation that stored definitions as `mcps/*.toml` next to Transport env/header values preserve their existing plaintext-at-rest behavior in SQLite. Prefer `{{ secrets.NAME }}` interpolation over literal credentials where possible. +Set `enabled = false` to keep an inline server or catalog reference in configuration without connecting to it: + +```toml +[run.agent.mcps.sentry] +id = "sentry" +enabled = false +``` + +## Runtime interpolation + +Inline transport fields can interpolate values at the run boundary: + +| Syntax | Resolution time | +|---|---| +| `{{ vars.NAME }}` | When the server creates the run, using that run's variable snapshot | +| `{{ env.NAME }}` | When the worker launches the MCP transport | +| `{{ secrets.NAME }}` | When the worker launches the MCP transport, using a token secret from the server vault | + +Interpolation applies to stdio and sandbox commands and env values, plus HTTP URLs and headers. Variable tokens are replaced in the created run configuration. Worker-time environment and secret expressions remain in persisted configuration, while resolved secret values do not. A missing environment variable, missing secret, or non-token secret fails MCP startup instead of passing an unresolved token to the transport. + +Standalone `fabro exec` can resolve `{{ env.* }}` from its process environment, but it has no server vault. A `{{ secrets.* }}` reference therefore fails with an explicit error in standalone execution. + ## Transports ### Stdio @@ -153,6 +175,7 @@ NODE_ENV = "production" | Field | Description | Default | |---|---|---| +| `enabled` | Whether to connect to this server. | `true` | | `type` | Must be `"stdio"`. | — | | `command` | Array: the executable followed by its arguments. | — | | `script` | Shell script alternative to `command`. | — | @@ -175,6 +198,7 @@ Authorization = "Bearer sk-xxx" | Field | Description | Default | |---|---|---| +| `enabled` | Whether to connect to this server. | `true` | | `type` | Must be `"http"`. | — | | `protocol` | HTTP MCP protocol: `"streamable_http"` or legacy `"sse"`. | `"streamable_http"` | | `url` | The MCP server endpoint URL. | — | @@ -198,6 +222,7 @@ tool_timeout = "2m" | Field | Description | Default | |---|---|---| +| `enabled` | Whether to connect to this server. | `true` | | `type` | Must be `"sandbox"`. | — | | `protocol` | HTTP MCP protocol exposed by the sandbox server: `"streamable_http"` or legacy `"sse"`. | `"streamable_http"` | | `command` | Array: the command to run inside the sandbox. Must include a flag that makes the server listen on `port`. | — | diff --git a/docs/public/changelog/2026-06-11.mdx b/docs/public/changelog/2026-06-11.mdx new file mode 100644 index 000000000..b44302d47 --- /dev/null +++ b/docs/public/changelog/2026-06-11.mdx @@ -0,0 +1,10 @@ +--- +title: "Completion cost estimates" +date: "2026-06-11" +--- + +## More + + +- Completion responses now include catalog-estimated `cost_usd` and `cost_source` fields, including when a model alias is used + diff --git a/docs/public/changelog/2026-06-12.mdx b/docs/public/changelog/2026-06-12.mdx new file mode 100644 index 000000000..610d087af --- /dev/null +++ b/docs/public/changelog/2026-06-12.mdx @@ -0,0 +1,13 @@ +--- +title: "OpenRouter support" +date: "2026-06-12" +--- + +## OpenRouter support + +Using OpenRouter previously required custom provider configuration, and aggregator-specific cache and cost details were lost. OpenRouter is now an opt-in built-in provider with a curated model catalog, cached and reasoning token accounting, and authoritative provider-reported costs for blocking and streaming responses. + +```toml title="settings.toml" +[llm.providers.openrouter] +enabled = true +``` diff --git a/docs/public/changelog/2026-06-13.mdx b/docs/public/changelog/2026-06-13.mdx new file mode 100644 index 000000000..f5fead43a --- /dev/null +++ b/docs/public/changelog/2026-06-13.mdx @@ -0,0 +1,12 @@ +--- +title: "Environment management in Settings" +date: "2026-06-13" +--- + + +**Environment `volumes` settings were removed.** Remove `volumes` from managed environment definitions; the field is no longer accepted or applied. + + +## Environment management in Settings + +Server-managed environments previously required API or file-level administration. The new **Settings → Environments** pages let you create, edit, and delete environments while configuring images, resources, environment variables, networking, and lifecycle behavior. Built-in environments are seeded during installation instead of every server start, and `default` is now an ordinary environment that you can delete. diff --git a/docs/public/changelog/2026-06-14.mdx b/docs/public/changelog/2026-06-14.mdx new file mode 100644 index 000000000..0aadcd3eb --- /dev/null +++ b/docs/public/changelog/2026-06-14.mdx @@ -0,0 +1,14 @@ +--- +title: "Local environment working directories" +date: "2026-06-14" +--- + +## More + + +- Added `cwd` to server-managed local environments so remote servers can choose an absolute host working directory; Docker and Daytona ignore this setting + + + +- Local runs now keep a submitted source directory only when it exists on the server and otherwise report how to configure environment `cwd` instead of trying to recreate a client-only path + diff --git a/docs/public/changelog/2026-06-15.mdx b/docs/public/changelog/2026-06-15.mdx new file mode 100644 index 000000000..10f4f4d1a --- /dev/null +++ b/docs/public/changelog/2026-06-15.mdx @@ -0,0 +1,10 @@ +--- +title: "Graph rendering for templated comments" +date: "2026-06-15" +--- + +## More + + +- Fixed workflow graph rendering when a leading DOT comment contains template braces such as `{{ goal }}` + diff --git a/docs/public/changelog/2026-06-16.mdx b/docs/public/changelog/2026-06-16.mdx new file mode 100644 index 000000000..3c604b5c6 --- /dev/null +++ b/docs/public/changelog/2026-06-16.mdx @@ -0,0 +1,18 @@ +--- +title: "Amazon Bedrock support" +date: "2026-06-16" +--- + + +**Identifier and path settings no longer interpolate template tokens.** Model/provider selectors, Git author fields, SCM owner/repository fields, CLI target addresses, and `run.working_dir` are now literal strings. Replace any `{{ vars.* }}` or `{{ env.* }}` tokens in those fields with explicit values. + + +## Amazon Bedrock support + +Running AWS-hosted models previously required a custom gateway. Fabro now includes an opt-in Amazon Bedrock provider for the Converse API, with streaming, tools, prompt caching, reasoning, and models from Anthropic, Amazon, Meta, Mistral, DeepSeek, and other Bedrock families. Authentication supports the AWS default credential chain with per-request SigV4 refresh or a Bedrock API key. + +```toml title="settings.toml" +[llm.providers.bedrock] +enabled = true +base_url = "https://bedrock-runtime.us-east-1.amazonaws.com" +``` diff --git a/docs/public/changelog/2026-06-18.mdx b/docs/public/changelog/2026-06-18.mdx new file mode 100644 index 000000000..ec767eaaf --- /dev/null +++ b/docs/public/changelog/2026-06-18.mdx @@ -0,0 +1,22 @@ +--- +title: "MCP transport interpolation" +date: "2026-06-18" +--- + + +**Control-plane settings no longer interpolate template tokens.** Server listen, API/web URL, storage, object-store, and GitHub App identity fields are now literal strings. Use native overrides such as `FABRO_WEB_URL` and `FABRO_STORAGE_DIR` where deployment-time values are required. + + + +**A workflow goal can no longer reference itself.** A `{{ goal }}` token inside the graph goal now fails validation instead of passing through as literal text. Prompts may continue to reference the rendered goal. + + +## MCP transport interpolation + +Environment-dependent MCP commands, URLs, headers, and environment values previously carried unresolved tokens into the launched server. `fabro run` and `fabro exec` now resolve `{{ env.NAME }}` at the boundary where the MCP server starts, and a missing variable fails clearly instead of leaking the token as text. + +```toml +[run.agent.mcps.search] +type = "http" +url = "{{ env.SEARCH_MCP_URL }}" +``` diff --git a/docs/public/changelog/2026-06-24.mdx b/docs/public/changelog/2026-06-24.mdx new file mode 100644 index 000000000..974c352c1 --- /dev/null +++ b/docs/public/changelog/2026-06-24.mdx @@ -0,0 +1,25 @@ +--- +title: "Variables in workflow prompts and goals" +date: "2026-06-24" +--- + +## Variables in workflow prompts and goals + +Stored run variables previously worked in settings but were unavailable inside DOT prompts and goals. Node prompts, graph goals, imported subgraphs, and `@file` prompt/goal contents can now reference `{{ vars.NAME }}`; Fabro snapshots the server variable store when the run is created. + +```dot +digraph Deploy { + graph [goal="Deploy {{ vars.SERVICE }}"] + work [prompt="Deploy to {{ vars.REGION }}"] +} +``` + +## More + + +- `{{ inputs.* }}` outside prompts and goals now reports a specific error explaining that inputs are template-only + + + +- Inline MCP entries now honor `enabled = false`, an explicitly empty `cli.exec.agent.mcps` set no longer falls back to run MCPs, and per-server `tool_timeout` values now apply + diff --git a/docs/public/changelog/2026-06-25.mdx b/docs/public/changelog/2026-06-25.mdx new file mode 100644 index 000000000..79e8a47db --- /dev/null +++ b/docs/public/changelog/2026-06-25.mdx @@ -0,0 +1,10 @@ +--- +title: "OpenRouter branding in model settings" +date: "2026-06-25" +--- + +## More + + +- OpenRouter now displays its provider logo in **Settings → Models** instead of a letter fallback + diff --git a/docs/public/changelog/2026-06-26.mdx b/docs/public/changelog/2026-06-26.mdx new file mode 100644 index 000000000..fd74e5212 --- /dev/null +++ b/docs/public/changelog/2026-06-26.mdx @@ -0,0 +1,11 @@ +--- +title: "Docker diagnostics and remote provider login" +date: "2026-06-26" +--- + +## More + + +- `fabro doctor` now pings the Docker daemon when the Docker sandbox provider is enabled and reports actionable failure or timeout details +- `fabro provider login --server ...` now reads the target server's catalog and tests credentials there, so you can log into providers that the local CLI does not know + diff --git a/docs/public/changelog/2026-06-27.mdx b/docs/public/changelog/2026-06-27.mdx new file mode 100644 index 000000000..3922e1a02 --- /dev/null +++ b/docs/public/changelog/2026-06-27.mdx @@ -0,0 +1,10 @@ +--- +title: "Runtime security updates" +date: "2026-06-27" +--- + +## More + + +- Updated the Rust `tar` runtime and React Router to patched releases that resolve disclosed security vulnerabilities + diff --git a/docs/public/changelog/2026-06-30.mdx b/docs/public/changelog/2026-06-30.mdx new file mode 100644 index 000000000..9190721e9 --- /dev/null +++ b/docs/public/changelog/2026-06-30.mdx @@ -0,0 +1,23 @@ +--- +title: "Safer hooks and managed MCP servers" +date: "2026-06-30" +--- + + +**Hook interpolation now fails closed.** A missing or unavailable `{{ env.* }}` or `{{ secrets.* }}` token in a command, URL, header, prompt, or model blocks the hook instead of firing it with an empty or partially resolved value. Ensure every referenced value is available where the hook runs. + + +## Safer hook interpolation + +Hook commands, URLs, headers, prompts, and models now carry typed interpolation through to execution instead of being flattened and reparsed. HTTP headers use the same narrow token syntax as other hook fields, and resolution failures consistently block command, HTTP, prompt, and agent hooks before they can act on incomplete data. + +## More + + +- New `GET`, `POST`, `PUT`, and `DELETE /api/v1/mcp-servers` endpoints manage a shared MCP catalog with ETag concurrency; read responses expose configured key names without returning stored values +- Run configurations can reference a server-managed MCP definition with `id = "..."` instead of repeating its transport settings + + + +- Workflow variables now live in SQLite; existing `variables.json` data is imported transactionally and renamed to a timestamped backup + diff --git a/docs/public/changelog/2026-07-01.mdx b/docs/public/changelog/2026-07-01.mdx new file mode 100644 index 000000000..1befa2713 --- /dev/null +++ b/docs/public/changelog/2026-07-01.mdx @@ -0,0 +1,30 @@ +--- +title: "MCP server settings and prepare-step environments" +date: "2026-07-01" +--- + +## Manage MCP servers in Settings + +Server-managed MCP definitions previously required direct API calls. The new **Settings → MCP Servers** pages provide list, create, edit, and delete flows for stdio, HTTP, and sandbox transports. Credential-like values get a nudge toward `{{ secrets.NAME }}` references, while stored header and environment values remain write-only in the UI. + +## Prepare-step environments + +Per-step environment values in `run.prepare.steps` were parsed but dropped before execution. Prepare commands and their environment values now reach the sandbox, resolve `{{ env.* }}` at the run boundary, and fail clearly when a required value is missing; argv-style commands are also shell-quoted correctly. + +```toml +[[run.prepare.steps]] +command = ["npm", "install"] +env = { NPM_TOKEN = "{{ env.NPM_TOKEN }}" } +``` + +## More + + +- `Sandbox::glob` now applies consistent `*`, `**`, and path-segment matching across local, Docker, and Daytona environments, restoring remote skill discovery and common agent glob searches +- Newly created GitHub Apps now include the organization Projects V2 permission required by project-tracker workflows + + + +- Newly created GitHub Apps now request Dependabot alert read/write permission; existing apps require a manual permission update and installation approval +- Server-managed environments now live in SQLite, with legacy TOML definitions imported and renamed to a timestamped backup + diff --git a/docs/public/changelog/2026-07-02.mdx b/docs/public/changelog/2026-07-02.mdx new file mode 100644 index 000000000..6de3d5b16 --- /dev/null +++ b/docs/public/changelog/2026-07-02.mdx @@ -0,0 +1,23 @@ +--- +title: "Faster web loading and run-time secret interpolation" +date: "2026-07-02" +--- + +## Faster web loading + +Remote web sessions previously downloaded about 13.5 MB of uncompressed JavaScript on every refresh, delaying first render by roughly 11–14 seconds on a 1 MB/s connection. Hashed assets now cache correctly, responses use Brotli or gzip compression, and dynamic chunks load only when needed, reducing the measured initial transfer to about 0.8 MB and making refreshes effectively instant. + +## Run-time secret interpolation + +Workflow configuration can now reference vault tokens with `{{ secrets.NAME }}` in MCP transports, prepare steps, and run environment values. Secrets resolve only in the worker at run start, are never persisted in expanded form, and a missing or non-token secret aborts startup instead of passing through literally. + +```toml +[run.environment.env] +DEPLOY_TOKEN = "{{ secrets.DEPLOY_TOKEN }}" +``` + +## More + + +- The stages sidebar now scrolls independently on run overview and stages pages, so long runs no longer push the graph off screen + diff --git a/docs/public/changelog/2026-07-03.mdx b/docs/public/changelog/2026-07-03.mdx new file mode 100644 index 000000000..fa0850477 --- /dev/null +++ b/docs/public/changelog/2026-07-03.mdx @@ -0,0 +1,10 @@ +--- +title: "Workflow graph direction in run views" +date: "2026-07-03" +--- + +## More + + +- Run overview graphs now honor the workflow's `rankdir` instead of always rendering with the default direction + diff --git a/docs/public/changelog/2026-07-07.mdx b/docs/public/changelog/2026-07-07.mdx new file mode 100644 index 000000000..c20b5aa50 --- /dev/null +++ b/docs/public/changelog/2026-07-07.mdx @@ -0,0 +1,19 @@ +--- +title: "Private deployments with Tailscale Services" +date: "2026-07-07" +--- + +## Private deployments with Tailscale Services + +Self-hosted Fabro can now stay private to a tailnet using the new loopback-only `docker-compose.tailscale.yaml` deployment. Set `FABRO_WEB_URL` to the Tailscale Service HTTPS origin, start the compose stack, and publish it with `tailscale serve`; Tailscale handles TLS without Caddy, public DNS, or a public Fabro port. Because a Tailscale Service is private, GitHub.com cannot deliver webhooks to it—use a public webhook relay, `server_url`, or Tailscale Funnel when webhooks are required. + +```bash +docker compose -f docker-compose.tailscale.yaml up -d +tailscale serve --service=svc:fabro --https=443 http://127.0.0.1:32276 +``` + +## More + + +- Automation workflow slugs now preserve kebab-case, preventing names such as `patch-cves` from being saved as the non-existent `patch_cves`; previously saved snake-case selectors require manual correction + diff --git a/docs/public/changelog/2026-07-08.mdx b/docs/public/changelog/2026-07-08.mdx new file mode 100644 index 000000000..e20b6cb12 --- /dev/null +++ b/docs/public/changelog/2026-07-08.mdx @@ -0,0 +1,18 @@ +--- +title: "Trackpad navigation for run graphs" +date: "2026-07-08" +--- + +## Trackpad navigation for run graphs + +Run overview graphs now use familiar canvas controls: two-finger scrolling pans, while Command/Ctrl-scroll or trackpad pinch zooms smoothly around the cursor. Drag-to-pan, toolbar zoom, fit-to-window, node interaction, and browser back-navigation protection continue to work with the shared viewport state. + +## More + + +- Added `[run.checkpoint] commit_timeout` so repositories with long-running commit hooks can raise the per-node checkpoint timeout above its 30-second default + + + +- Runs created before the prepare-step schema change now load in the run list again instead of disappearing during projection rebuild + diff --git a/docs/public/changelog/2026-07-09.mdx b/docs/public/changelog/2026-07-09.mdx index 654d1d1fa..ebe4072bb 100644 --- a/docs/public/changelog/2026-07-09.mdx +++ b/docs/public/changelog/2026-07-09.mdx @@ -3,8 +3,23 @@ title: "Provider header interpolation" date: "2026-07-09" --- + +**Provider `extra_headers` use a new value syntax.** Replace `{ literal = "value" }` with `"value"`, `{ env = "NAME" }` with `"{{ env.NAME }}"`, and `{ vault = "NAME" }` with `"{{ secrets.NAME }}"`. + + ## Provider header interpolation -Provider `extra_headers` now use interpolation strings instead of typed `{ literal = ... }`, `{ env = ... }`, and `{ vault = ... }` tables. Write literal header values as plain strings, replace `{ env = "X" }` with a `{{ env.X }}` token, and replace `{ vault = "X" }` with a `{{ secrets.X }}` token. Secret header tokens resolve only token-style vault entries, so file and OAuth vault entries fail closed. +Provider headers can now mix literal text with environment or secret values in one string, making bearer prefixes and gateway-specific formats straightforward. Secret tokens resolve only token-style vault entries, so missing, file, and OAuth secrets fail closed; put credentials in secrets rather than literal header values. -This also enables mixed literal and secret values such as `Authorization = "Bearer {{ secrets.GATEWAY_TOKEN }}"`. Bare string header values are now accepted; put credentials in secrets and reference them with `{{ secrets.NAME }}` instead of pasting a credential as a literal. Provider `base_url` remains a plain literal string for now and is not interpolated. +```toml +[llm.providers.gateway.extra_headers] +Authorization = "Bearer {{ secrets.GATEWAY_TOKEN }}" +``` + +Provider `base_url` remains a literal string and is not interpolated. + +## More + + +- Run graph zoom and pan now survive switching between the Overview and Stages tabs + diff --git a/docs/public/changelog/2026-07-10.mdx b/docs/public/changelog/2026-07-10.mdx new file mode 100644 index 000000000..225c99775 --- /dev/null +++ b/docs/public/changelog/2026-07-10.mdx @@ -0,0 +1,18 @@ +--- +title: "GPT-5.6 models" +date: "2026-07-10" +--- + + +**`server.integrations.slack.default_channel` is now literal text.** Replace any `{{ env.NAME }}` token with an explicit channel name. Per-run Slack notification and interview channels still support interpolation. + + +## GPT-5.6 models + +Fabro now includes GPT-5.6 Sol, Terra, and Luna in the built-in OpenAI catalog, routed through the Responses API with a Codex-safe 272K context policy. GPT-5.6 Sol becomes the OpenAI default, while all three models expose their current capabilities, reasoning controls, limits, aliases, and pricing. + +## More + + +- Long ACP agent turns now refresh GitHub App push credentials at turn entry and every 45 minutes, preventing late `git push` operations from failing after installation tokens expire + diff --git a/docs/public/changelog/2026-07-11.mdx b/docs/public/changelog/2026-07-11.mdx new file mode 100644 index 000000000..4eb1dc69c --- /dev/null +++ b/docs/public/changelog/2026-07-11.mdx @@ -0,0 +1,15 @@ +--- +title: "Unified SQLite storage" +date: "2026-07-11" +--- + +## Unified SQLite storage + +Server-managed secrets, MCP servers, automations, and run summaries now share Fabro's SQLite database instead of separate JSON or TOML stores. Existing data is imported during startup, with legacy sources renamed to timestamped backups after a successful transaction; the existing APIs and settings UI remain unchanged. + +## More + + +- Restored process-environment LLM credentials for standalone CLI and agent sessions after the secrets storage migration +- Fixed avatars and principal icons being squeezed into ovals in the runs list **By** column + diff --git a/docs/public/changelog/2026-07-22.mdx b/docs/public/changelog/2026-07-22.mdx new file mode 100644 index 000000000..a84beaea1 --- /dev/null +++ b/docs/public/changelog/2026-07-22.mdx @@ -0,0 +1,35 @@ +--- +title: "Poolside Laguna and safer database migrations" +date: "2026-07-22" +--- + +## Poolside Laguna models + +Fabro now includes Poolside as a built-in OpenAI-compatible provider for Laguna S 2.1 and Laguna XS 2.1, with native reasoning and tool use. Both models are available directly from Poolside and through the opt-in OpenRouter provider. + +```bash +fabro provider login --provider poolside +fabro run workflow.fabro --model laguna-s-2.1 +``` + +## Safer database migrations + +Schema upgrades now create a consistent `fabro.sqlite3.pre-migration.bak` snapshot before applying a new migration. If you need to roll back to an older binary, you have a known-good database from immediately before the latest schema change instead of having to repair migration metadata by hand. + +## More + + +- `fabro repo init` now leaves the sample workflow environment unset so the target server can supply its configured default + + + +- Parallel branch stages now reach a terminal state with their own duration instead of remaining **Running** after the fan-in and run complete +- OpenAI-compatible agent providers now receive compatible object-schema tools and an `edit_file` tool instead of the OpenAI-only custom patch tool +- Fixed legacy provider token details producing negative billing buckets in run summaries + + + +- Added Kimi K3 through the direct Kimi provider and OpenRouter +- Added GLM 5.2 through Z.AI and OpenRouter +- Left-to-right run graphs now zoom to 400% and preserve separate viewport positions for left-to-right and top-to-bottom layouts + diff --git a/docs/public/changelog/2026-07-23.mdx b/docs/public/changelog/2026-07-23.mdx new file mode 100644 index 000000000..6eee78aa0 --- /dev/null +++ b/docs/public/changelog/2026-07-23.mdx @@ -0,0 +1,48 @@ +--- +title: "Provider-aware models and reliable agent control" +date: "2026-07-23" +--- + + +**Ineffective agent execution-limit SDK fields were removed.** `SessionOptions.max_turns`, `SessionOptions.max_tool_rounds_per_input`, and the `TurnLimitReached` event no longer exist. Use session wall-clock timeouts or interruption when you need an execution bound. + + +## Provider-aware model selection + +Model aliases used to be globally unique, which made portable model names ambiguous across direct providers and aggregators. Providers can now expose the same canonical slug and aliases: Fabro selects the highest-priority ready offering for an unqualified name, treats an explicit provider as a pin, and persists the chosen offering when the run is created. Custom offerings now nest under their provider; legacy top-level `[llm.models]` rows and historical built-in selectors remain accepted for compatibility. + +```toml title="settings.toml" +[llm.providers.proxy.models."team-code-large"] +api_id = "provider-wire-model-name" +aliases = ["team-code"] +``` + +## Reliable interrupts and cancellation + +Interrupting an agent now settles active inference, tools, and subagent waits before the stage enters a durable **waiting for steering** state shown in the web UI. Steering resumes from that state without orphaning child agents, while live run cancellations are durably recorded and remain visibly **Cancelling…** until the run reaches its terminal cancelled state. + +## More + + +- LLM requests made during a run now carry `x-session-id: ` so compatible gateways can group them into one trace session + + + +- `fabro validate` now warns when handler-specific attributes are placed on node or parallel-branch types that never read them, including clearer diagnostics for multiple parallel parents, custom types, and inherited thread settings +- Run manifests now bundle `@file` references used by `output_schema` + + + +- Provider-reported completion costs now survive agent and workflow event projection, and billing accumulation saturates safely +- Explicit provider pins are preserved through run materialization and request-time routing instead of being redirected to a higher-priority provider +- OpenRouter Claude requests now emit cache-control breakpoints, enabling the prompt-cache reads and writes advertised by those catalog entries +- Agent stage events are flushed before stage completion, preventing final tool and usage events from appearing after the stage is marked done +- Agent routing's last-file fallback now accepts only `.json` and `.md` files whose final standalone object contains routing fields +- Generated run titles update atomically and no longer overwrite a title changed while generation was in flight +- Child OpenAI plans no longer replace the root agent's TODO list in the stage sidebar +- Run deletion failures now show delete-specific error messages + + + +- OpenRouter now includes Claude Fable 5, Claude Opus 4.8, and GPT-5.6 Sol, Terra, and Luna offerings + diff --git a/docs/public/changelog/2026-07-24.mdx b/docs/public/changelog/2026-07-24.mdx new file mode 100644 index 000000000..8e38af7ac --- /dev/null +++ b/docs/public/changelog/2026-07-24.mdx @@ -0,0 +1,45 @@ +--- +title: "Shared-checkout parallelism, Fireworks, and resume history" +date: "2026-07-24" +--- + + +**Parallel branches now share one checkout, and `join_policy` was removed.** Remove `join_policy` from parallel nodes; fan-out now waits for every branch. Keep file-writing branches read-only or assign disjoint paths, and update fan-in logic to consume `parallel.results` instead of a selected branch workspace or `parallel_results.json`. + + +## Shared-checkout parallel execution + +Parallel branches now run concurrently in the run's existing sandbox and working directory instead of creating branch-specific Git worktrees. Each branch still receives isolated context updates, returned in outgoing-edge order under `parallel.results`, while a prompted fan-in can synthesize the complete typed result array. Promptless fan-in nodes act as barriers and never select, restore, or merge workspace state. + +## Fireworks AI + +Fireworks AI is now an opt-in built-in provider with a curated serverless catalog for Kimi, DeepSeek, GLM, MiniMax, Qwen, and GPT-OSS models. The integration uses the existing OpenAI-compatible path, supports cached-input accounting, and works with the normal provider login, secret storage, diagnostics, and model-testing flows. + +```toml title="settings.toml" +[llm.providers.fireworks] +enabled = true +``` + +## Resumed stages keep their history + +When a run resumes after a node was cancelled or lost mid-flight, the replay now starts a new stage execution such as `work@2` instead of clearing and reusing `work@1`. The earlier execution keeps its events, session, output, timing, billing, and terminal state, and the stage UI links the new execution back to the one it resumed from. + +## More + + +- `GET /api/v1/runs/{id}/events` now supports descending cursor pagination with `order=desc` and `before_seq`; `fabro run events --tail` uses it to fetch only the newest events +- Completion usage now reports disjoint input, output, reasoning, cache-read, and cache-write token buckets +- Model responses now advertise exact `controls.reasoning_effort` values, and completion requests—including structured completions—validate and forward reasoning effort with a `400` response for unsupported values + + + +- Event history scans now use seek-based pagination with bounded cursors, avoiding full-history work and incorrect results for oversized sequence values +- Preflight now prefers providers that are actually ready while preserving explicit provider pins and useful diagnostics for unavailable offerings +- OpenAI-compatible agent providers now receive compatible file-edit and tool schemas +- Large values inside parallel branch results now stay available to fan-in prompts through normal artifact storage + + + +- Added `gpt-sol`, `gpt-terra`, and `gpt-luna` aliases for GPT-5.6 offerings +- Added portable `glm`, `glm52`, `glm5.2`, `deepseek`, and `deepseek-flash` aliases across direct and OpenRouter offerings + diff --git a/docs/public/core-concepts/models.mdx b/docs/public/core-concepts/models.mdx index e5eb837b3..ec15087b4 100644 --- a/docs/public/core-concepts/models.mdx +++ b/docs/public/core-concepts/models.mdx @@ -43,9 +43,9 @@ Fabro performs this selection once when creating a run and persists the chosen p | `claude-sonnet-4-6` | anthropic | `sonnet`, `claude-sonnet` | 200K | $3.00 / $15.00 | 50 tok/s | | `claude-sonnet-4-5` | anthropic | | 200K | $3.00 / $15.00 | 50 tok/s | | `claude-haiku-4-5` | anthropic | `haiku`, `claude-haiku` | 200K | $0.80 / $4.00 | 100 tok/s | -| `gpt-5.6-sol` | openai | `gpt-5.6`, `gpt56-sol` | 272K | $5.00 / $30.00 | n/a | -| `gpt-5.6-terra` | openai | `gpt56-terra` | 272K | $2.50 / $15.00 | n/a | -| `gpt-5.6-luna` | openai | `gpt56-luna` | 272K | $1.00 / $6.00 | n/a | +| `gpt-5.6-sol` | openai | `sol`, `gpt-sol`, `gpt56-sol`, `gpt-56-sol`, `gpt-5.6`, `gpt56`, `gpt-56` | 272K | $5.00 / $30.00 | n/a | +| `gpt-5.6-terra` | openai | `terra`, `gpt-terra`, `gpt56-terra`, `gpt-56-terra` | 272K | $2.50 / $15.00 | n/a | +| `gpt-5.6-luna` | openai | `luna`, `gpt-luna`, `gpt56-luna`, `gpt-56-luna` | 272K | $1.00 / $6.00 | n/a | | `gpt-5.4` | openai | `gpt54`, `gpt5`, `codex` | 272K | $2.50 / $15.00 | 70 tok/s | | `gpt-5.5` | openai | `gpt55` | 272K | $5.00 / $30.00 | 70 tok/s | | `gpt-5.5-pro` | openai | `gpt55-pro` | 1M | $30.00 / $180.00 | 20 tok/s | @@ -56,7 +56,8 @@ Fabro performs this selection once when creating a run and persists the chosen p | `gemini-3.5-flash` | gemini | `gemini-35-flash` | 1M | $1.50 / $9.00 | 150 tok/s | | `gemini-3-flash-preview` | gemini | `gemini-flash` | 1M | $0.50 / $3.00 | 150 tok/s | | `gemini-3.1-flash-lite` | gemini | `gemini-flash-lite`, `gemini-3.1-flash-lite-preview` | 1M | $0.25 / $1.50 | 200 tok/s | -| `kimi-k2.5` | kimi | `kimi` | 262K | $0.60 / $3.00 | 50 tok/s | +| `kimi-k2.5` | kimi | | 262K | $0.60 / $3.00 | 50 tok/s | +| `kimi-k3` | kimi | `kimi` | 1M | $3.00 / $15.00 | n/a | | `laguna-s-2.1` | poolside | `laguna`, `laguna-s` | 1M | $0.10 / $0.20 | n/a | | `laguna-xs-2.1` | poolside | `laguna-xs` | 262K | $0.10 / $0.20 | n/a | | `glm-5.2` | zai | `glm`, `glm5`, `glm52`, `glm5.2` | 1M | $1.40 / $4.40 | n/a | @@ -153,6 +154,8 @@ Model roles are separate: `default = true` controls normal model selection for w Provider auth is declared in `[llm.providers..auth]` with ordered `env:` or `vault:` refs. The primary auth header defaults to `bearer`; override with `header = { custom = "Header-Name" }` for providers like Anthropic that use `x-api-key`. Omit the `[llm.providers..auth]` block entirely for providers that need no API key (e.g. Ollama). Custom headers for any provider — including providers that need only interpolation headers and no API-key auth — go in `extra_headers` as literal text, `{{ env.NAME }}` tokens, or `{{ secrets.NAME }}` tokens. Put credentials in secrets and reference them with `{{ secrets.NAME }}` instead of a bare literal. +Workflow runs also add `x-session-id: ` to every LLM request so compatible gateways can group requests from the same run. An explicitly configured `x-session-id` in provider `extra_headers` takes precedence. + Provider `agent_profile` defaults from `adapter` and controls profile-specific behavior such as project-memory filenames, CLI/ACP command selection, and native session routing. Valid values are `anthropic`, `openai`, and `gemini`; model-level values override provider-level values. Provider `billing_policy` defaults from `adapter` and controls usage-cost estimation. Use `openai`, `anthropic`, `gemini`, or `none`. Model rows may override it for models whose billing family differs from their provider's — for example, Claude models served through OpenRouter set `billing_policy = "anthropic"` so cache reads and writes price correctly. @@ -204,7 +207,7 @@ When no model or provider is specified, Fabro chooses the default offering on th | `anthropic` | `claude-sonnet-4-6` | | `openai` | `gpt-5.6-sol` | | `gemini` | `gemini-3.5-flash` | -| `kimi` | `kimi-k2.5` | +| `kimi` | `kimi-k3` | | `poolside` | `laguna-s-2.1` | | `zai` | `glm-5.2` | | `minimax` | `minimax-m2.5` | diff --git a/docs/public/docs.json b/docs/public/docs.json index 2d33a07d7..3ce3cca8b 100644 --- a/docs/public/docs.json +++ b/docs/public/docs.json @@ -291,10 +291,39 @@ "tab": "Changelog", "icon": "clock-rotate-left", "groups": [ + { + "group": "July 2026", + "icon": "clock-rotate-left", + "pages": [ + "changelog/2026-07-24", + "changelog/2026-07-23", + "changelog/2026-07-22", + "changelog/2026-07-11", + "changelog/2026-07-10", + "changelog/2026-07-09", + "changelog/2026-07-08", + "changelog/2026-07-07", + "changelog/2026-07-03", + "changelog/2026-07-02", + "changelog/2026-07-01" + ] + }, { "group": "June 2026", "icon": "clock-rotate-left", "pages": [ + "changelog/2026-06-30", + "changelog/2026-06-27", + "changelog/2026-06-26", + "changelog/2026-06-25", + "changelog/2026-06-24", + "changelog/2026-06-18", + "changelog/2026-06-16", + "changelog/2026-06-15", + "changelog/2026-06-14", + "changelog/2026-06-13", + "changelog/2026-06-12", + "changelog/2026-06-11", "changelog/2026-06-10", "changelog/2026-06-09", "changelog/2026-06-05", diff --git a/docs/public/execution/checkpoints.mdx b/docs/public/execution/checkpoints.mdx index 71ff54329..814edf950 100644 --- a/docs/public/execution/checkpoints.mdx +++ b/docs/public/execution/checkpoints.mdx @@ -109,6 +109,12 @@ Fabro resolves the run by ID prefix, validates that durable state contains a che 7. Continues execution from `next_node_id` +### Resuming an in-flight stage + +If the latest checkpoint captured a stage that was still running, resume allocates a new stage execution ID instead of rewriting the original history. For example, a resumed `work@1` stage continues as `work@2`, and the new record sets `resumed_from_stage_id = "work@1"`. The earlier stage remains immutable. + +The number after `@` is the stage execution ordinal. It is separate from the graph visit count and from a handler's retry attempt, so loops, retries, and resume ancestry can be inspected independently. + ## The checkpoint cycle Here's the full sequence that runs after every node completes: diff --git a/docs/public/execution/environments.mdx b/docs/public/execution/environments.mdx index f88d9bdd4..786cf9063 100644 --- a/docs/public/execution/environments.mdx +++ b/docs/public/execution/environments.mdx @@ -150,6 +150,19 @@ preserve = true `env` and `labels` merge by key. +## Environment value interpolation + +Environment `env` values can mix literal text with `{{ vars.NAME }}`, `{{ env.NAME }}`, and `{{ secrets.NAME }}` tokens: + +```toml title="workflow.toml" +[environments.fabro-dev.env] +DEPLOY_ENV = "{{ vars.DEPLOY_ENV }}" +SERVICE_URL = "https://api.{{ env.REGION }}.example.com" +SERVICE_TOKEN = "{{ secrets.SERVICE_TOKEN }}" +``` + +Server-managed variables resolve when the run is created. Worker environment variables and token secrets resolve immediately before the sandbox starts, so resolved secret values are not persisted in the run definition. A missing or non-token secret fails closed. For backward compatibility, a value containing only missing `{{ env.* }}` references is passed through in source form. + ## Selecting an environment from the CLI Use `--environment` with an environment slug: diff --git a/docs/public/execution/run-configuration.mdx b/docs/public/execution/run-configuration.mdx index 3cb544906..89cb107b5 100644 --- a/docs/public/execution/run-configuration.mdx +++ b/docs/public/execution/run-configuration.mdx @@ -186,17 +186,20 @@ Ordered list of steps to run before the workflow starts. Use this to clone repos script = "pip install -r requirements.txt" [[run.prepare.steps]] -script = "npm install" +command = ["npm", "install"] +env = { NPM_TOKEN = "{{ secrets.NPM_TOKEN }}" } ``` | Field | Description | |---|---| -| `script` | Shell-evaluated command (runs through `sh -c`). | -| `command` | Argv-style command, mutually exclusive with `script`. | -| `env` | Additional environment variables for this step. | +| `script` | Shell-evaluated command (runs through `sh -c`). Supports `{{ vars.* }}`, `{{ env.* }}`, and `{{ secrets.* }}` interpolation. | +| `command` | Argv-style command, mutually exclusive with `script`. Each resolved element is shell-quoted as one argument. | +| `env` | Additional environment variables for this step. Values support the same interpolation as `script` and `command`. | Each step must exit with status 0. If any step fails, the run aborts before the workflow starts. Prepare steps replace across layers — the higher-precedence layer wins wholesale. +Fabro substitutes `{{ vars.* }}` when the server creates the run, then resolves `{{ env.* }}` from the worker process and `{{ secrets.* }}` from token entries in the server vault immediately before the worker executes the steps. Worker-time environment and secret expressions remain in the persisted run definition; resolved secret values are not persisted. A missing environment variable, missing secret, or non-token secret aborts startup with the affected step and token named in the error. + ### `[run.clone]` Configure whether clone-based sandboxes clone the run's GitHub origin before execution. @@ -292,23 +295,25 @@ When `provider = "local"`, Fabro runs directly in the resolved working directory. If you want local isolation, create or enter a separate clone or Git worktree yourself. -Environment variable values can be literal strings or host environment -references using `{{ env.VARNAME }}` syntax: +Environment variable values can combine literal text with server variables, worker environment variables, and token secrets: ```toml title="run.toml" [environments.ci.env] -API_KEY = "{{ env.MY_API_KEY }}" +API_KEY = "{{ secrets.SERVICE_API_KEY }}" NODE_ENV = "production" SERVICE_URL = "https://api.{{ env.REGION }}.example.com" +RELEASE_CHANNEL = "{{ vars.RELEASE_CHANNEL }}" ``` | Syntax | Description | |---|---| | `"literal"` | Static value passed as-is | -| `"{{ env.VARNAME }}"` | Whole-value reference resolved from the host environment at consumption time | -| `"prefix-{{ env.X }}-suffix"` | Substring interpolation; multiple tokens per string are supported | +| `"{{ vars.NAME }}"` | Server-managed variable substituted when the run is created | +| `"{{ env.VARNAME }}"` | Worker process environment value resolved when the run starts | +| `"{{ secrets.NAME }}"` | Token secret resolved from the server vault when the run starts | +| `"prefix-{{ env.X }}-suffix"` | Substring interpolation; multiple supported tokens per string are allowed | -Missing host variables produce a hard error pointing at the specific field and unresolved token. +Missing or non-token secret references fail closed before sandbox startup. For backward compatibility, an environment value that references only a missing `{{ env.* }}` value is passed through in source form; use preflight or prepare-step interpolation when an absent worker variable must be a hard error. ### `[run.integrations.github.permissions]` @@ -448,8 +453,17 @@ startup_timeout = "60s" tool_timeout = "2m" ``` +To reuse a definition from the server-managed MCP catalog, reference its ID instead of defining an inline transport: + +```toml title="run.toml" +[run.agent.mcps.sentry] +id = "sentry" +``` + | Field | Description | Default | |---|---|---| +| `id` | Server-managed MCP definition to use. Cannot be combined with inline transport fields. | — | +| `enabled` | Set `false` to leave this inline server or catalog reference disabled. | `true` | | `type` | Transport type: `"stdio"`, `"http"`, or `"sandbox"`. | — | | `script` | (stdio, sandbox) Shell-evaluated startup command, mutually exclusive with `command`. | — | | `command` | (stdio, sandbox) Argv array: executable + arguments. | — | @@ -460,6 +474,8 @@ tool_timeout = "2m" | `startup_timeout` | Max duration for server startup + MCP handshake (e.g. `"10s"`, `"1m"`). | `"10s"` | | `tool_timeout` | Max duration for a single tool call. | `"60s"` | +Inline transport commands, URLs, env values, and headers support `{{ vars.* }}`, `{{ env.* }}`, and `{{ secrets.* }}` interpolation. As with prepare steps, server variables resolve at run creation and worker env/token secrets resolve at launch; missing values fail closed. See [MCP runtime interpolation](/agents/mcp#runtime-interpolation) for the standalone `fabro exec` difference. + The `sandbox` transport runs the MCP server inside the workflow's sandbox. This is useful for tools that need access to the sandbox environment, such as browser automation with Playwright. See [MCP](/agents/mcp#sandbox) for details. ### `[run.pull_request]` diff --git a/docs/public/integrations/github.mdx b/docs/public/integrations/github.mdx index ba6039d5b..e70fe5428 100644 --- a/docs/public/integrations/github.mdx +++ b/docs/public/integrations/github.mdx @@ -60,6 +60,10 @@ When you choose the GitHub App strategy, the CLI opens GitHub with a pre-filled | Checks | Write | Report workflow status on commits | | Issues | Write | Create issues from workflows | | Emails | Read | Read verified email for OAuth login | + | Dependabot alerts | Write | Read and manage repository vulnerability alerts | + | Organization projects | Write | Read and update organization Projects V2 | + +These permissions are included when Fabro registers a new app. For an existing GitHub App, add the missing permissions in the app's settings, then approve the permission update on each installation before workflows can use them. The installer: @@ -232,7 +236,9 @@ 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. -Installation Access Tokens are short-lived, so Fabro refreshes them when they are close to expiry. Command stages and API-mode agent stages resolve `GITHUB_TOKEN` before use, which keeps long workflows working across token rollover. CLI-mode agent stages receive their token at launch time; for very long CLI-agent stages, run GitHub operations through command stages or API-mode agents if mid-stage token refresh matters. +Installation Access Tokens are short-lived. Fabro refreshes its own credentials before checkpoint pushes. For ACP/CLI agent turns launched with GitHub App push credentials, Fabro also re-mints the token and rewrites the sandbox's `origin` URL before the ACP process starts, then every 45 minutes for the lifetime of that turn. Refresh failures are logged and do not fail the stage. + +`FABRO_PUSH_CRED_REFRESH_AHEAD` defaults to enabled; set it to `0`, `false`, `off`, `no`, or an empty value to disable both turn-entry and background refresh. `FABRO_PUSH_CRED_REFRESH_INTERVAL_SECONDS` overrides the background interval, and `0` disables only the background loop. This refresh loop is ACP-specific; command and native/API agent stages do not run it. Reconnected sandboxes for resumed or parked runs currently lack the App credentials needed for ACP refresh, so the refresh is skipped there. The permissions table follows the standard layer-merge order (workflow > project > user > defaults). Set defaults at `[run.integrations.github.permissions]` in `~/.fabro/settings.toml` so every run inherits a baseline; tighten or override per-workflow as needed. A higher layer that defines `permissions = {}` clears the inherited map (no token requested). diff --git a/docs/public/integrations/openrouter.mdx b/docs/public/integrations/openrouter.mdx index c725ddca6..bd0c186dd 100644 --- a/docs/public/integrations/openrouter.mdx +++ b/docs/public/integrations/openrouter.mdx @@ -48,13 +48,13 @@ The built-in catalog gives OpenRouter offerings the same human-facing model slug | Fabro model slug | OpenRouter API ID / notes | | --- | --- | -| `claude-opus-4-7` | `anthropic/claude-opus-4.7`; Anthropic-style cache billing | +| `claude-fable-5`, `claude-opus-4-8`, `claude-opus-4-7` | Matching `anthropic/...` API IDs; Anthropic-style cache billing | | `claude-sonnet-4-6` | `anthropic/claude-sonnet-4.6`; provider default | | `claude-haiku-4-5` | `anthropic/claude-haiku-4.5`; provider small default | -| `gpt-5.4`, `gpt-5.5` | `openai/gpt-5.4`, `openai/gpt-5.5` | +| `gpt-5.6-sol`, `gpt-5.6-terra`, `gpt-5.6-luna`, `gpt-5.4`, `gpt-5.5` | Matching `openai/...` API IDs | | `gemini-3.1-pro-preview`, `gemini-3.5-flash` | `google/...` API IDs | | `deepseek-v4-pro` (`deepseek`, `deepseek-v4`), `deepseek-v4-flash` (`deepseek-flash`) | `deepseek/...` API IDs | -| `kimi-k2.6`, `qwen3-coder`, `qwen3.6-flash` | Vendor-prefixed API IDs | +| `kimi-k3`, `kimi-k2.6`, `qwen3-coder`, `qwen3.6-flash` | Vendor-prefixed API IDs | | `laguna-s-2.1`, `laguna-xs-2.1` | `poolside/...`; native reasoning and tool use | | `glm-5.2` (`glm`, `glm5`, `glm52`, `glm5.2`), `glm-4.6` | `z-ai/...` API IDs | | `minimax-m2.7`, `mimo-v2.5-pro` | Vendor-prefixed API IDs | @@ -134,7 +134,7 @@ OpenRouter's [provider routing preferences](https://openrouter.ai/docs/guides/ro ## Attribution headers -Fabro does not send OpenRouter's optional attribution headers (`HTTP-Referer`, `X-Title`) by default, so self-hosted installations stay anonymous on OpenRouter's public app leaderboard. To opt in: +Fabro does not send OpenRouter's optional attribution headers (`HTTP-Referer`, `X-Title`) by default, so self-hosted installations stay anonymous on OpenRouter's public app leaderboard. Workflow runs do send `x-session-id: ` for request grouping; an explicit provider `extra_headers` value for that header takes precedence. To opt in to attribution: ```toml title="settings.toml" [llm.providers.openrouter.extra_headers] diff --git a/docs/public/integrations/slack.mdx b/docs/public/integrations/slack.mdx index bca381e79..9420be8fd 100644 --- a/docs/public/integrations/slack.mdx +++ b/docs/public/integrations/slack.mdx @@ -117,7 +117,7 @@ enabled = true default_channel = "#fabro-reviews" ``` -`default_channel` is used only for human-in-the-loop interview prompts. Run lifecycle notifications use per-run or per-workflow `[run.notifications]` routes instead. +`default_channel` is a literal channel name used only for human-in-the-loop interview prompts. Fabro does not interpolate `{{ env.* }}` in this server setting. Run lifecycle notifications use per-run or per-workflow `[run.notifications]` routes instead, whose channel values can use environment interpolation. ### 8. Invite the bot diff --git a/docs/public/workflows/imports.mdx b/docs/public/workflows/imports.mdx index 3b37dfe42..f9e18f158 100644 --- a/docs/public/workflows/imports.mdx +++ b/docs/public/workflows/imports.mdx @@ -122,6 +122,8 @@ digraph Lint { } ``` +Imported prompts receive the same `{{ goal }}`, `{{ inputs.* }}`, and `{{ vars.* }}` context as prompts in the root graph. The server-managed variable values are the snapshot captured when the run is created. + Do not put templates in `import` paths, node IDs, edge definitions, other structural references, or attributes besides `prompt` — they are literal text. See [Variables](/workflows/variables#expansion-timing) for the rendering pipeline. ## Nested imports diff --git a/docs/public/workflows/variables.mdx b/docs/public/workflows/variables.mdx index 18b0c00b1..fb42d126b 100644 --- a/docs/public/workflows/variables.mdx +++ b/docs/public/workflows/variables.mdx @@ -7,14 +7,15 @@ Fabro renders `{{ ... }}` templates in exactly two workflow attributes: the grap ## Template context -Goal and prompt templates can reference: +Goal templates can reference inputs and server-managed variables. Prompt templates receive those values plus the rendered workflow goal: | Expression | Resolves to | |---|---| -| `{{ goal }}` | The workflow goal | +| `{{ goal }}` | The rendered workflow goal (prompts only; a goal cannot reference itself) | | `{{ inputs.name }}` | A value from `[run.inputs]`, optionally overridden by CLI input flags | +| `{{ vars.NAME }}` | A server-managed variable snapshotted when the run is created | -Environment variables are **not** available in goal or prompt templates. Use `{{ env.NAME }}` only in config strings and HTTP hook headers. +Environment variables and secrets are **not** available in goal or prompt templates. Use `{{ env.NAME }}` and `{{ secrets.NAME }}` only in the configuration fields that support run-boundary interpolation. ## Run config inputs @@ -68,7 +69,7 @@ Use server-managed variables for non-sensitive values that should be shared acro fabro variable set DEPLOY_ENV staging --description "Deployment target" ``` -Run configuration strings can reference these values with `{{ vars.NAME }}`: +Run configuration strings, graph goals, and node prompts can reference these values with `{{ vars.NAME }}`: ```toml title="workflow.toml" _version = 1 @@ -77,8 +78,19 @@ _version = 1 goal = "Deploy {{ vars.DEPLOY_ENV }}" ``` +```dot title="deploy.fabro" +digraph Deploy { + graph [goal="Deploy to {{ vars.DEPLOY_ENV }}"] + deploy [prompt="Deploy the requested release to {{ vars.DEPLOY_ENV }}."] +} +``` + Variables are intentionally readable: `fabro variable list` and `fabro variable get DEPLOY_ENV` show stored values. Do not store tokens, API keys, or credentials as variables; use `fabro secret set` for sensitive values. +Server-managed variables are stored in the shared SQLite database. When upgrading from file-backed storage, Fabro imports `/variables.json` once; existing SQLite rows win on name conflicts. After a successful import, the source file is renamed to `variables.json.imported-.bak`. + +The server snapshots the variable store when it creates the run. Changing a variable later does not rewrite that run's rendered goal, imported prompts, or `@file` prompt and goal contents. + ## `goal` Agent and prompt nodes also receive the workflow goal at runtime: @@ -93,13 +105,16 @@ digraph Example { That prompt becomes `Create a plan for: Implement the login feature`. +A graph goal cannot contain `{{ goal }}` because that would reference itself. `fabro validate` reports `goal_self_reference` as an error; put the reusable text in an input or server-managed variable instead. + ## Expansion timing Fabro keeps workflow structure static and renders workflow templates once: 1. Fabro parses the root DOT and imported `.fabro` files without rendering them. 2. Literal `import`, `@file`, graph-goal file, and child-workflow references are resolved. -3. The graph `goal` and node `prompt` attributes are rendered with the `{ goal, inputs }` context. +3. The graph `goal` is rendered with the `{ inputs, vars }` context. +4. Node `prompt` attributes are rendered with the `{ goal, inputs, vars }` context. Templates are not supported in graph syntax, node IDs, edge structure, `import` paths, `@file` paths, child workflow paths, other file references, or any attribute besides `prompt` and `goal`. @@ -107,7 +122,7 @@ Fabro renders the graph `goal` first and stores the rendered value back onto the ## Undefined variables -Fabro renders undefined workflow variables as empty text and records a `template_undefined_variable` diagnostic. `fabro validate` reports that diagnostic as a warning so you can validate workflow structure before all inputs are known. Run-style commands such as `fabro run`, `fabro create`, and preflight promote the same diagnostic to an error before proceeding. +Fabro renders undefined workflow variables as empty text and records a `template_undefined_variable` diagnostic. `fabro validate` reports that diagnostic as a warning so you can validate workflow structure before all inputs are known. Offline validation does not read a server's variable store, so `{{ vars.* }}` references also warn there. Run-style commands such as `fabro run`, `fabro create`, and preflight use the server snapshot and promote any still-undefined reference to an error before proceeding. ## Template includes