Expose TaskGet for Claude-style task inspection, refresh Anthropic prompt and tool guidance, and remind long-running sessions to use task tracking when those tools are available.
Use raw sandbox reads for memory and skills, keep line-numbered reads focused on display, and share retry-delay handling across agent and LLM code.
Trim task tool descriptions, bound multi-file read concurrency, restore Docker's text read path, and add the reviewed implementation plan docs.
Separate raw file reads from the line-numbered display API so apply_patch and edit_file operate on unformatted UTF-8 content. Keep read_file/read_many_files model-facing output numbered and cover regressions for prefix corruption.
Replay retryable stream failures from the last committed turn state, clear partial visible output before retry or terminal failure, and map Anthropic stream error events into structured provider errors.
Modularize the Anthropic system prompt into Claude-style sections and expand TaskCreate, TaskUpdate, and TaskList descriptions with task-management guidance adapted to Fabro's tool surface.
Allow fabro_run_create object specs to pass goal_file, reject goal and goal_file together, and preserve file-sourced goal semantics when building run manifests.
Expose only policy-allowed tool schemas to the model and block hidden tool calls before executor lookup. Give Ask Fabro a run-scoped read-only policy and prompt so it only advertises tools it can actually use.
## What
Makes the run-detail stage sidebar (shown on the Overview and Stages
tabs) collapsible with a slide animation.
- A toggle button slides the panel between full width (`w-56`) and an
icon-only rail (`w-12`), animating `width` over 300ms with the same
easing as the Ask Fabro panel.
- When collapsed, **stage status icons stay visible** — green check /
red X / spinning teal for running — so run progress is still scannable
at a glance. Workflow links (Graph Source, Run Logs, etc.) collapse to
icons too so they remain reachable.
- Labels and durations become `sr-only` with `title` tooltips for hover.
- The open/closed choice persists to `localStorage`
(`fabro:stage-sidebar-collapsed`), carrying across the Overview and
Stages tabs and reloads.
## Layout
- The collapse toggle is inline with the `STAGES` heading row (or
`WORKFLOW` when a run has no stages yet), so it doesn't push the stage
list down.
- The stage sidebar's top padding on the Stages tab was reduced (`pt-6`
→ `pt-3`) so the heading aligns with the adjacent content column and
sits closer to the tab nav.
## Notes
Self-contained in `StageSidebar` — `run-overview.tsx` and
`run-stages.tsx` render it inside flex layouts that already track its
width, so the slide works in both with no parent changes (aside from the
padding tweak).
Verified: `tsc` typecheck passes; `stage-sidebar` lib tests pass
(10/10).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: fabro-sh-0530[bot] <281434857+fabro-sh-0530[bot]@users.noreply.github.com>
Co-authored-by: Fabro <noreply@fabro.sh>
Fabro run tools are now gated behind an explicit per-run opt-in so
workflow agents only receive those capabilities when the run requests
them.
## What changed
- **`fabro_tools` setting** (`run.agent.fabro_tools`, default `false`)
is resolved through the existing TOML config layer stack.
- **Worker JWT scope** adds `agent:run_tools` only when the resolved
setting is `true`; default worker tokens carry only `run:worker`.
- **Worker tool registration** derives from the worker JWT scope claim.
The CLI worker locally decodes the token payload and registers
`FabroRunToolServices` only when the scope includes both `run:worker`
and `agent:run_tools`.
- **Server-side authorization remains authoritative**. The worker-side
decode is only a local tool-registration gate; the server still
validates token signature and scopes before accepting run-tool API
calls.
> **Behavior change:** existing runs that relied on Fabro run tools
being always available must add `[run.agent] fabro_tools = true` to
their workflow config.
## Verification
```sh
cargo +nightly-2026-04-14 fmt --all
cargo test -p fabro-cli fabro_run_tools_enabled_token_requires_run_tools_scope
cargo test -p fabro-server worker_command_
cargo test -p fabro-static
cargo +nightly-2026-04-14 clippy -p fabro-cli -p fabro-server -p fabro-static --all-targets -- -D warnings
git diff --check
```
---------
Co-authored-by: Fabro <noreply@fabro.sh>
Co-authored-by: Bryan Helmkamp <bryan@brynary.com>
## Summary
OpenAI-profile agents now receive `apply_patch` as a Codex-compatible
freeform custom tool instead of a JSON function with a `patch` field.
The model sends raw patch text validated by the vendored Lark grammar,
and Fabro round-trips OpenAI custom tool calls/results through the
Responses API.
## What Changed
- Added `fabro-llm` support for function and custom tool definitions
while preserving existing JSON function behavior for normal tools.
- Translated OpenAI custom tool calls, custom tool outputs, and
streaming `response.custom_tool_call_input.delta` events into Fabro tool
calls with raw arguments.
- Ported the Codex apply-patch grammar and adapted Codex-style patch
parsing/application semantics to Fabro's `Sandbox` trait, including
strict envelopes, fuzzy context matching, move/delete/add/update
behavior, trailing-newline normalization, and Codex-style
summaries/errors.
- Updated OpenAI agent prompt/tool registration so `apply_patch` is
freeform, and skipped JSON-schema validation/repair for custom tool
calls only.
- Updated file tracking to read Codex-style `A`/`M` result lines from
successful patch output.
## Verification
- `cargo nextest run -p fabro-llm -p fabro-agent`
- `cargo +nightly-2026-04-14 fmt --check --all`
- `cargo +nightly-2026-04-14 clippy --no-deps -p fabro-llm -p
fabro-agent --all-targets -- -D warnings`
- `git diff --check`
Note: the full non-`--no-deps` clippy command still surfaces an
unrelated existing `fabro-sandbox` `large_enum_variant` warning in
`lib/crates/fabro-sandbox/src/sandbox_spec.rs`.
---
[](https://github.com/EveryInc/compound-engineering-plugin)
🤖 Generated with GPT-5 via [Codex](https://openai.com/codex)
## Summary
Ships the Ask Fabro sidebar on run pages end-to-end: the agent now has
live `fabro_run_interact` and `fabro_run_events` tools scoped to its
owning run, and the web sidebar talks to real session APIs instead of a
scripted adapter. The `?ask=1` prototype gate is dropped in favour of
server-reported `run.ask_fabro.available`.
## What changed and why
### Rust — run-control tools in Ask Fabro sessions (`fabro-server`,
`fabro-workflow`, `fabro-tool`)
**Tool registration** (`fabro-workflow`): `register_fabro_run_tools` is
now `pub`; a new `register_named_fabro_run_tools` variant accepts a name
allowlist so callers can register a subset without forking the catalog
loop. Unknown names are silently ignored.
**Run-scoped backend** (`fabro-tool`): `ClientBackend` gains a
`run_scope: Option<RunId>` field set via `.with_run_scope(run_id)`.
Every method checks the scope before delegating to the HTTP client,
returning an error before a network call is made. `list_store_runs`
returns a single-element vec of the owning run when scoped;
`resolve_run` rejects non-parseable selectors rather than forwarding
them.
**Session wiring** (`fabro-server`): `build_profile` now returns
`Box<dyn AgentProfile>` (mutably accessible) instead of `Arc`;
`build_agent_session` mints a same-run worker token, builds a
`ClientBackend::with_run_scope`, constructs `FabroRunToolServices`, and
calls `register_named_fabro_run_tools` for the two tools before freezing
into an `Arc`. `AppState::self_server_target()` reads the bound address
from the runtime daemon record for the loopback HTTP call.
**Approval gate**: `build_ask_fabro_tool_approval` now fast-paths
`fabro_run_interact` and `fabro_run_events` to `Ok(())`; all other tools
remain subject to the `ReadOnly` auto-approve check. File/shell tools
are still denied.
### Web — real session adapter and sidebar wiring (`fabro-web`)
**`ask-fabro-runtime.ts`** (new): a `ChatModelAdapter` that creates a
session lazily on the first turn (`sessionsApi.createRunSession`),
caches the session id in `sessionStorage` keyed by run id, and streams
turns via `streamSessionTurn`. `applyTurnEvent` maps `run.session.*` SSE
events to assistant-ui `ThreadAssistantMessagePart[]` incrementally
(text deltas, tool-call started/completed pairs). A 404 on stream clears
the cached id so the next turn starts fresh.
**`ask-fabro-sidebar.tsx`**: drops `scriptIndexRef`, `EMPTY_CHAT`, and
the scripted adapter import; accepts `runId` and `defaultModel` props;
constructs the real adapter via `createAskFabroAdapter`.
**`run-detail.tsx`**: removes `?ask=1` / `askEnabled`; reads
`run.ask_fabro.{available, default_model}` from the summary; always
renders an `AskFabroTriggerButton` (disabled with a tooltip when
unavailable); passes `runId` and `defaultModel` to `<AskFabroSidebar>`.
### Architecture
```mermaid
graph TB
Browser -->|SSE turn stream| SessionsHandler
SessionsHandler -->|spawn| AskFabroAgent
AskFabroAgent -->|fabro_run_interact\nfabro_run_events| ClientBackend
ClientBackend -->|HTTP + same-run\nworker token| RunsAPI[Runs API\n/runs/:id]
ClientBackend -->|run_scope check| ClientBackend
RunsAPI -->|403 cross-run| ClientBackend
```
### Design decisions
- **Same-run scoping is double-enforced**: the `ClientBackend` scope
check fires before the HTTP call; the worker token's run scope causes a
403 at the API layer if the check were somehow bypassed.
- **`build_profile` → `Box` not `Arc`**: the profile needs mutable
access for tool registration after construction, so the `Arc` wrapping
is deferred until registration is complete.
- **`sessionStorage` per-run**: one session is reused across sidebar
open/close cycles for the same run tab; a page reload or different run
always starts clean.
- **Mutating actions included**: `interact` exposes
start/cancel/steer/archive/answer. This is intentional per the locked
decisions; the worker-token scope prevents cross-run blast radius.
### Fabro Details
<details>
<summary>Ran 9 stages in 74m 52s for $44.11</summary>
| Stage | Duration | Cost | Retries |
|---|---|---|---|
| start | 0s | – | 0 |
| toolchain | 1s | – | 0 |
| preflight_compile | 2m 11s | – | 0 |
| preflight_lint | 2m 22s | – | 0 |
| implement | 38m 37s | $32.02 | 0 |
| simplify_opus | 17m 40s | $6.55 | 0 |
| simplify_gpt | 9m 32s | $5.55 | 0 |
| verify | 3m 43s | – | 0 |
| fmt | 3s | – | 0 |
| **Total** | **74m 52s** | **$44.11** | **0** |
</details>
<details>
<summary>Ran <code>ImplementPlan.fabro</code> (12 nodes and 15
edges)</summary>
```dot
digraph ImplementPlan {
graph [
goal="Implement and simplify",
model_stylesheet="
* { model: claude-opus-4-7; }
"
]
rankdir=LR
start [shape=Mdiamond, label="Start"]
exit [shape=Msquare, label="Exit"]
toolchain [label="Toolchain", shape=parallelogram, script="command -v cargo >/dev/null || { curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && sudo ln -sf $HOME/.cargo/bin/* /usr/local/bin/; }; cargo --version 2>&1", max_retries=0]
preflight_compile [label="Preflight Compile", shape=parallelogram, script="cargo check -q --workspace 2>&1", max_retries=0]
preflight_lint [label="Preflight Lint", shape=parallelogram, script="cargo +nightly-2026-04-14 clippy -q --workspace --all-targets -- -D warnings 2>&1", max_retries=0]
fix_lints [label="Fix Lints", prompt="The preflight lint step failed. Read the build output from context and fix all clippy lint warnings.", max_visits=3]
implement [label="Implement", prompt="Read the plan file referenced in the goal and implement every step. Make all the code changes described in the plan. Use red/green TDD."]
simplify_opus [label="Simplify (Opus)", prompt="@prompts/simplify.md"]
simplify_gpt [label="Simplify (GPT-55)", prompt="@prompts/simplify.md", model="gpt-55"]
verify [label="Verify", shape=parallelogram, script="cargo +nightly-2026-04-14 clippy -q --workspace --all-targets -- -D warnings 2>&1 && cargo nextest run --cargo-quiet --workspace --status-level fail 2>&1 && cargo dev docs refresh 2>&1 && cargo dev docs check 2>&1", goal_gate=true, retry_target="fixup"]
fixup [label="Fixup", prompt="The verify step failed. Read the build output from context and fix all clippy lint warnings, test failures, and generated docs errors.", max_visits=3]
fmt [label="Format", shape=parallelogram, script="cargo +nightly-2026-04-14 fmt --all 2>&1", max_retries=0]
start -> toolchain
toolchain -> preflight_compile [condition="outcome=succeeded"]
toolchain -> exit
preflight_compile -> preflight_lint [condition="outcome=succeeded"]
preflight_compile -> exit
preflight_lint -> implement [condition="outcome=succeeded"]
preflight_lint -> fix_lints
fix_lints -> preflight_lint
implement -> simplify_opus -> simplify_gpt -> verify
verify -> fmt [condition="outcome=succeeded"]
verify -> fixup
fixup -> verify
fmt -> exit
}
```
</details>
⚒️ Generated with [Fabro](https://fabro.sh)
---------
Co-authored-by: Bryan Helmkamp <bryan@brynary.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Fabro <noreply@fabro.sh>
Co-authored-by: fabro <fabro@example.com>
Co-authored-by: fabro <fabro@fabro.sh>
## Summary
This branch improves several run-management surfaces that agents and
users rely on: archived runs now stay visible and ordered correctly in
the board view, pair-session messages appear in the stage Thread tab,
and the `fabro_run_create` MCP tool accepts the workflow-string
shorthand it advertises.
## Changes
- Updates the web board cache invalidation and archived-column handling
so archive/unarchive actions refresh both active and archived board
queries and keep archived runs in a predictable column position.
- Adds pair user/system message events to stage activity parsing, Thread
rendering, search, details, and DNA timeline items.
- Aligns `fabro_run_create` MCP runtime deserialization and `tools/list`
schema so each run entry may be either a workflow string or a full
create spec object.
## Test Plan
- `cargo nextest run -p fabro-tool -p fabro-mcp-server`
- `cargo nextest run -p fabro-cli
stdio_server_initializes_and_lists_run_tools
mcp_create_string_shorthand_deserializes_before_auth
mcp_create_validation_errors_happen_before_auth_or_network
mcp_create_and_search_manage_real_runs_with_cli_auth`
- `cargo +nightly-2026-04-14 fmt --check --all`
---
[](https://github.com/EveryInc/compound-engineering-plugin)
🤖 Generated with GPT-5 via [Codex](https://openai.com/codex)
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
## Summary
Adds the run-backed API surface needed for a real Ask Fabro sidebar: run
readiness metadata, detailed session projections, session-scoped event
listing/attach streaming, and turn control that exposes durable turn IDs
and machine-readable failures.
## What Changed
- Extended the OpenAPI contract and regenerated Rust/TypeScript clients
for `Run.ask_fabro`, `SessionDetail`, `SessionTurn`, paginated run
sessions, session event APIs, and optional client-supplied `turn_id`
values.
- Updated `fabro-types` and `fabro-store` so durable `run.session.*`
events project active turn state, transcript messages, and the latest
owning run event sequence.
- Implemented server routing for session details, `/events`, `/attach`,
turn conflict headers, typed turn failure codes, and cheap run readiness
decoration across run responses.
- Added browser helpers for POST turn streaming and session attach SSE
parsing, plus an exported generated `sessionsApi`.
## Verification
- `cargo +nightly-2026-04-14 fmt --check --all`
- `cargo +nightly-2026-04-14 clippy --workspace --all-targets -- -D
warnings`
- `cargo build --workspace`
- `cargo test -p fabro-types
run_session_turn_failed_defaults_code_for_old_events`
- `cargo test -p fabro-store run_sessions::tests`
- `cargo test -p fabro-api`
- `cargo test -p fabro-server --features test-support --test it
api::sessions`
- `cargo test -p fabro-server --features test-support --test it
api::runs`
- `cd apps/fabro-web && bun test app/lib/session-stream.test.ts`
- `cd apps/fabro-web && bun run typecheck`
---
[](https://github.com/EveryInc/compound-engineering-plugin)
🤖 Generated with GPT-5 Codex (context unknown, medium reasoning) via
[Codex](https://openai.com/codex/)
## Summary
API-backed Fabro agent sessions can now use the same five run-control
tools that were previously MCP-only, while the server uses a single
scoped `FABRO_WORKER_TOKEN` path for worker authorization. This lets
server-dispatched API agents create, gather, inspect, and interact with
runs without reintroducing a separate delegated run-agent token or
leaking credentials into sandboxed child environments.
## Changes
- Moved the reusable run-tool implementation into the new `fabro-tool`
crate so MCP and API agent backends share the same tool schemas and
client behavior.
- Registered the five `fabro_run_*` tools for API-mode agents, with ACP
sessions continuing to omit those tools.
- Replaced `FABRO_RUN_AGENT_TOKEN` with scoped worker-token auth: base
worker tokens keep same-run access, and `run:worker agent:run_tools`
tokens can call the run-control API across runs.
- Added server auth guards for run-tool actors and
run-scoped-or-run-tools routes, then applied them only to the routes
used by the run-tool client backend.
- Kept `FABRO_WORKER_TOKEN` scrubbed from sandbox commands, hooks, ACP
subprocesses, MCP env providers, and other child tool environments.
## Testing
- `cargo nextest run -p fabro-server worker_token principal_middleware
spawn_env --no-fail-fast`
- `cargo nextest run -p fabro-cli runner --no-fail-fast`
- `cargo nextest run -p fabro-workflow agent_run --no-fail-fast`
- `cargo nextest run -p fabro-static --no-fail-fast`
- `cargo +nightly-2026-04-14 fmt --check --all`
- `cargo +nightly-2026-04-14 clippy -p fabro-server -p fabro-cli -p
fabro-static --all-targets -- -D warnings`
- `git diff --check`
---
[](https://github.com/EveryInc/compound-engineering-plugin)
🤖 Generated with GPT-5 via [Codex](https://openai.com/codex)
Fixes Daytona sandbox network policy rendering so absent allow lists
with `networkBlockAll=false` are reported as open egress instead of
unknown, while Daytona ingress is always reported as blocked.
The mapper still reports blocked egress when Daytona blocks all
networking and CIDR allow-list egress when `networkAllowList` is
present. Tests cover blocked, allow-list, empty allow-list, and default
Daytona network data.
Verified with `cargo nextest run -p fabro-sandbox --features daytona`
and `cargo +nightly-2026-04-14 fmt --check --all`.
---
[](https://github.com/EveryInc/compound-engineering-plugin)
🤖 Generated with GPT-5 via [Codex](https://openai.com/codex)
`bun run generate` (openapi-generator typescript-axios) emits trailing
spaces and extra blank lines, so every regeneration produced a noisy
whitespace diff that masked real spec/client drift.
Add a normalize-generated.ts post-generation pass that strips trailing
whitespace and ends each file with exactly one newline, and chain it
into the `generate` script. Establishes the normalized baseline across
the generated client; running `generate` twice now yields no diff.
No content changes — the entire diff is whitespace.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Hovering the run header items now reveals a popover with extra
context:
- Run status: failure reason and error message for failed runs;
archived timestamp for archived runs (no popover otherwise)
- Repository: full owner/repo name and the cloned branch
- Workflow: node and edge counts plus run labels
- PR: live GitHub details fetched lazily on hover — title, an
open/draft/merged/closed badge, and the head -> base branch arrow
Workflow node/edge counts are new: WorkflowRef now carries
node_count/edge_count, computed in build_summary from the parsed
graph that is already in hand there.
Adds a HoverCard primitive alongside Tooltip (shared useHoverAnchor
hook) for rich, viewport-aware popovers.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
build_disk_usage_response only summed scratch/ run dirs and logs/*.log,
omitting objects/ (SlateDB + artifacts), sessions/, and vaults/ — a ~30x
undercount of "Fabro managed" storage on the resources page.
Measure the whole storage_dir tree for total_size_bytes so it can't drift
as new subdirectories are added. Reclaimable stays a curated estimate that
matches what `fabro system prune` actually frees. A residual "other"
summary row keeps `fabro system df` totals consistent and surfaces as a
"Database & artifacts" table row.
Also add a KiB tier to formatBytesAsMemory so small storage values render
human-readably instead of raw byte counts.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Fixes https://github.com/fabro-sh/fabro/issues/335
## Summary
- Run artifact discovery with `find -H` so a symlinked sandbox
working-directory root is traversed.
- Keep existing behavior for symlinks discovered inside the tree by
preserving the current `-not -type l -type f` filter.
- Add workflow integration coverage for artifact collection when the
local sandbox working directory itself is a symlink.
## Test plan
- `cargo nextest run -p fabro-workflow
asset_collection_local_sandbox_symlink_working_directory`
- `cargo nextest run -p fabro-workflow artifact_snapshot`
- `cargo nextest run -p fabro-workflow asset_collection_local_sandbox`
- `cargo +nightly-2026-04-14 fmt --check --all`
## Summary
Ask Fabro sessions are now run-bound instead of standalone. Sessions are
created under their owning run, then accessed by flat session ID routes,
with durable state projected from the run event stream rather than a
separate session store.
## Changes
- Move session creation/listing to `POST/GET /api/v1/runs/{id}/sessions`
while keeping flat session reads, turns, interrupts, and event streams
under `/api/v1/sessions/{id}/...`.
- Add typed `run.session.*` events, ULID-backed session/turn IDs,
read-only default permissions, and a rebuildable SlateDB `session_id ->
run_id` index.
- Remove the old file-backed session store and wire the server, runtime,
Rust client, generated API crates, and TypeScript client around run
event projections.
- Replace the old top-level CLI session command with `fabro run ask` for
chatting with a run.
- Regenerate the TypeScript API client; this also catches up existing
generated models for Pair/run event detail schemas already present in
the OpenAPI spec.
## Validation
- `cargo build -p fabro-api -p fabro-client -p fabro-server -p
fabro-cli`
- `cargo nextest run -p fabro-server --features test-support -E
'test(run_bound_session_is_created_as_run_event_and_resolves_by_flat_id)
| test(sessions_are_listed_only_under_their_owning_run)'`
- `cargo nextest run -p fabro-store
projection_rebuilds_runtime_context_from_run_events`
- `cargo +nightly-2026-04-14 clippy -p fabro-api -p fabro-client -p
fabro-store -p fabro-server -p fabro-cli --all-targets -- -D warnings`
- `cargo +nightly-2026-04-14 fmt --check --all`
- `cd lib/packages/fabro-api-client && bun run typecheck && cd
../../../apps/fabro-web && bun run typecheck`
- `git diff --check`
---
[](https://github.com/EveryInc/compound-engineering-plugin)
Generated with GPT-5 via [Codex](https://openai.com/codex)
## Summary
Fixes artifact promotion for configured `[run.artifacts].include` globs
by collecting matching files after each stage regardless of mtime and
surfacing failed discovery commands as collection failures.
The workflow lifecycle now keeps a per-run ledger keyed by `(path,
content_sha256)`, rebuilt from existing `artifact.captured` events, so
unchanged files are persisted and emitted once while changed content at
the same path can still be captured again.
Fixesfabro-sh/fabro#335.
## Tests
- `cargo +nightly-2026-04-14 fmt --check --all`
- `cargo nextest run -p fabro-workflow artifact_snapshot`
- `cargo nextest run -p fabro-cli
unchanged_matching_artifact_is_captured_once_across_stages`
- `cargo nextest run -p fabro-cli
acp_artifacts_are_listed_when_touched_file_mtime_precedes_attempt_start`
---
[](https://github.com/EveryInc/compound-engineering-plugin)
🤖 Generated with GPT-5 via [Codex](https://openai.com/codex)
---------
Co-authored-by: Jess Martin <27258+jessmartin@users.noreply.github.com>
## Summary
Unifies installer persistence so CLI and web install paths share the
same file/env/vault primitives, while preserving the CLI's server-API
secret persistence and auth bootstrap behavior.
## What Changed
- Added shared `fabro-install` config writers for installer-owned tagged
enum tables, replacing `server.listen` and `cli.target` atomically so
stale variant fields cannot survive.
- Added `InstallPersistencePlan` for disk-backed settings, server env,
and vault writes/removals with the existing rollback semantics for
settings and vault failures.
- Refactored `fabro install`, `fabro install github`, and
`/install/finish` to use the shared persistence plan where their disk
behavior overlaps.
- Preserved full-install ordering: settings/env first, workflow-visible
secrets through the server API second, and CLI `auth.json` only after
API secret persistence succeeds.
- Preserved web installer failure response fields for leftover and
removed env keys, plus the post-success finish hook/shutdown behavior.
## Test Plan
- `cargo nextest run -p fabro-install`
- `cargo nextest run -p fabro-cli commands::install::tests`
- `cargo nextest run -p fabro-cli --test it cmd::install`
- `cargo nextest run -p fabro-server --features test-support --test it
api::install`
- `cargo +nightly-2026-04-14 fmt --check --all`
- `git diff --check`
---
[](https://github.com/EveryInc/compound-engineering-plugin)
🤖 Generated with GPT-5 via [Codex](https://openai.com/codex)
## Summary
Fixes `fabro-sh/fabro#330` by making template partials that reference
missing inputs validate structurally with a warning instead of failing
the validate command. The template crate now owns MiniJinja semantic
error classification and source-location mapping, so workflow
diagnostics can consume already-resolved template locations instead of
remapping fragment spans itself.
## What Changed
- Added `TemplateErrorLocation` and `TemplateSourceOrigin` APIs to
report source name, line, column, and span from `fabro-template`.
- Classified wrapped MiniJinja errors by their deepest semantic cause,
preserving the original source chain for renderer context.
- Added fragment-origin rendering paths so attribute fragments embedded
in full workflow source report locations in the original source text.
- Removed workflow-side source span remapping from template diagnostics;
workflow now only adds owner, node/edge, severity, rule, and fix
context.
- Added regression coverage for include/import/from/extends undefined
variables and the CLI `fabro validate` partial fixture.
## Test Plan
- `cargo nextest run -p fabro-template`
- `cargo nextest run -p fabro-workflow transforms::variable_expansion
transforms::file_inlining`
- `cargo nextest run -p fabro-cli --test it cmd::validate`
- `cargo +nightly-2026-04-14 fmt --check --all`
- `cargo +nightly-2026-04-14 clippy -p fabro-template -p fabro-workflow
-p fabro-cli --all-targets -- -D warnings`
---
[](https://github.com/EveryInc/compound-engineering-plugin)
🤖 Generated with GPT-5 (context not reported, reasoning not reported)
via [Codex](https://openai.com/codex)
## Summary
Adds the server-side run pairing surface for joining one active API-mode
agent session, sending pair messages, reading a compact transcript, and
ending pairing explicitly before workflow release continues.
This PR wires the feature end to end:
- adds OpenAPI paths and shared `fabro-types` DTOs for pair lifecycle,
messages, transcript entries, and run event details
- adds typed `RunEvent` variants for pair lifecycle and pair-scoped
user/system messages
- extends the workflow steering hub and agent session drain path with
typed pair control items, single-target validation, pair parking, and
pair end/resume behavior
- extends worker JSONL control and server transports for pair
start/message/end while preserving existing
steer/interrupt/answer/cancel behavior
- adds Axum handlers for `/api/v1/runs/{id}/pair`, pair messages, pair
transcript, and `/api/v1/runs/{id}/events/{seq}`
- adds `fabro-client` helpers for the new endpoints
## Notes
The subprocess path does not add a bidirectional worker ack channel in
this PR. Instead, the HTTP pair handlers only return lifecycle/message
success after the corresponding durable runtime event is observed, so
mpsc enqueue success alone is not treated as API success.
The plan checklist in
`docs/superpowers/plans/2026-05-18-server-side-run-pairing-api-events.md`
is included with that distinction left visible.
## Verification
- `cargo build -p fabro-api`
- `cargo check -p fabro-api -p fabro-client -p fabro-agent -p
fabro-workflow -p fabro-interview -p fabro-server`
- `cargo +nightly-2026-04-14 fmt --check --all`
- `cargo +nightly-2026-04-14 clippy --workspace --all-targets -- -D
warnings`
- `cargo nextest run -p fabro-api pair
run_event_round_trips_pair_lifecycle_events
run_event_round_trips_agent_pair_messages`
- `cargo nextest run -p fabro-workflow pair`
- `cargo nextest run -p fabro-interview pair`
- `cargo nextest run -p fabro-server pair
subprocess_answer_transport_pair_commands_enqueue_control_messages steer
interrupt`
## What
Adds a **Settings → Secrets** page so secrets can be managed from the
browser, backed by the existing secrets HTTP API and generated TS
client.
- **`/settings/secrets`** — lists stored secrets (name, type badge,
description, last-updated) and deletes them through the shared confirm
dialog.
- **`/settings/secrets/new`** — the create form for **token** and
**file** secrets. OAuth secrets still list and delete here, but are
created by provider sign-in flows, not typed by hand (matching the CLI's
`secret set`).
- The **Secrets** entry is added to the settings sidebar nav.
## How
- `secretsApi` wired into `api-client.ts`; `useSecrets()` SWR hook +
`secrets` query key.
- New sibling routes `secrets` and `secrets/new` under `settings` (same
pattern as `runs` / `runs/:id`).
- The settings layout gains optional **handle-driven** `description` and
`headerAction`. When a page declares them, the layout renders title +
subheading + a vertically-centered header action button as one unified
header. Other settings pages are unaffected — they fall back to the
existing title-only header.
## Notes
- Values are write-only: the API never returns secret values, and the UI
never displays them.
- Reuses existing primitives throughout (`Panel`, `Badge`,
`ConfirmDialog`, `useToast`, button/input classes) — no new shared
components.
- Verified: `bun run typecheck` and `bun run build` pass; routes serve
200.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
## Summary
Adds server-visible resource reporting and a compact Resources settings
tab for CPU, memory, and the filesystem that contains Fabro storage.
## Changes
- Adds `GET /api/v1/system/resources` backed by `sysinfo`, including CPU
sampling, cgroup-aware memory reporting, storage filesystem matching,
and Fabro-managed disk byte totals.
- Extends the OpenAPI contract and regenerates the Rust and TypeScript
API clients.
- Adds a deterministic demo-mode resources route.
- Adds `/settings/resources` with 5 second polling and panels for
overview, CPU, memory, disk, and notes.
- Adds server integration/unit coverage and web route/render coverage.
## Screenshot

## Verification
- `cargo build -p fabro-api`
- `cd lib/packages/fabro-api-client && bun run generate`
- `cargo nextest run -p fabro-server --features test-support --test it
api::system`
- `cargo test -p fabro-server resource_sampler::tests`
- `cd apps/fabro-web && bun test`
- `cd apps/fabro-web && bun run typecheck`
- `cd apps/fabro-web && bun run build`
- `cargo +nightly-2026-04-14 fmt --check --all`
- `cargo +nightly-2026-04-14 clippy --workspace --all-targets -- -D
warnings`
---
[](https://github.com/compound-engineering)
🤖 Generated with GPT-5 via [Codex](https://openai.com/codex)
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
## Summary
Fabro-created Daytona sandboxes now carry the same managed-resource
labels Docker containers already use: `sh.fabro.managed=true` and
`sh.fabro.run_id=<run-id>` when a run id is available.
This moves the Docker label constants into a shared sandbox helper,
keeps Docker behavior unchanged, and applies the helper when Daytona
create params are built. User-provided Daytona labels are preserved, but
Fabro's reserved keys are authoritative on collisions. Daytona snapshot
behavior is unchanged because the snapshot API does not expose labels.
## Testing
- `cargo test -p fabro-sandbox managed_labels --no-default-features
--features docker,daytona`
- `cargo test -p fabro-sandbox
docker::tests::real_run_container_gets_name_and_labels
--no-default-features --features docker`
- `cargo test -p fabro-sandbox daytona::tests::base_params
--no-default-features --features daytona`
- `cargo test -p fabro-sandbox daytona_managed_labels_live_smoke
--no-default-features --features daytona`
- `cargo test -p fabro-sandbox --no-default-features --features
docker,daytona`
- `cargo +nightly-2026-04-14 fmt --check --all`
- `cargo +nightly-2026-04-14 clippy --workspace --all-targets -- -D
warnings`
- `cargo +nightly-2026-04-14 clippy -p fabro-sandbox --all-targets
--no-default-features --features docker,daytona -- -D warnings`
The live Daytona smoke test remains ignored; it compiles under the
Daytona feature but was not run against live credentials.
## Post-Deploy Monitoring & Validation
- Log queries/search terms: `Failed to create Daytona sandbox`,
`Daytona`, `labels`, `sh.fabro.managed`, `sh.fabro.run_id`, and sandbox
initialization errors for `provider=daytona`.
- Metrics or dashboards: Daytona sandbox creation success/error rate,
Fabro run initialization failures for Daytona runs, and Daytona resource
inventory filtered by `sh.fabro.managed=true`.
- Expected healthy signals: new Fabro-created Daytona sandboxes include
`sh.fabro.managed=true`, run-owned sandboxes include the matching
`sh.fabro.run_id`, user labels remain visible, and Daytona sandbox
creation failure rates stay at baseline.
- Failure signals and rollback trigger: any sustained increase in
Daytona sandbox creation failures, API validation errors around labels,
or missing managed labels on newly created sandboxes. Roll back this PR
or hotfix the label merge to omit Daytona labels if Daytona rejects the
keys in production.
- Validation window and owner: release owner watches the first 24 hours
after deploy, with an immediate manual Daytona dashboard/API spot-check
after the first managed Daytona run.
---
[](https://github.com/EveryInc/compound-engineering-plugin)
🤖 Generated with GPT-5 (context unknown, reasoning enabled) via
[Codex](https://openai.com/codex)
## Summary
- Adds a backend-neutral live control abstraction so steering,
interrupt, and interrupt+steer no longer depend on API-only session
handles.
- Reworks ACP sessions into a live protocol loop that uses ACP
`session/prompt` for follow-up steers and ACP `session/cancel` for
interrupts without restarting the process.
- Registers ACP sessions as steerable, removes the stale non-steerable
server/UI/API path, preserves ACP projection metadata, and keeps
unsupported backends out of the steerability gate.
## Validation
- `LC_ALL=C cargo nextest run --workspace --no-fail-fast` (5,833 passed,
178 skipped)
- `cargo +nightly-2026-04-14 clippy --workspace --all-targets -- -D
warnings`
- `cargo +nightly-2026-04-14 fmt --check --all`
- `git diff --check`
- `cd apps/fabro-web && LC_ALL=C ASDF_NODEJS_VERSION=20.13.1
ASDF_BUN_VERSION=1.3.11 bun test` (396 passed)
- `cd apps/fabro-web && LC_ALL=C ASDF_NODEJS_VERSION=20.13.1
ASDF_BUN_VERSION=1.3.11 bun run typecheck`
- `cargo build -p fabro-api`
- `cd lib/packages/fabro-api-client && LC_ALL=C ASDF_BUN_VERSION=1.3.11
bun run typecheck`
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: Bryan Helmkamp <bryan@brynary.com>
## Summary
- Fix host command hooks to use the submitter/source directory instead
of a sandbox-only working directory.
- Introduce `HookExecutionContext` and `RunLocations` so host source,
sandbox work, and run scratch paths are explicit.
- Route lifecycle hooks and tool hooks through the shared hook execution
context instead of rebuilding cwd pairs at call sites.
## Test Plan
- `cargo nextest run -p fabro-hooks`
- `cargo check -p fabro-workflow --tests`
- `cargo +nightly-2026-04-14 fmt --package fabro-hooks --package
fabro-workflow --check`
- `cargo +nightly-2026-04-14 clippy -p fabro-hooks -p fabro-workflow
--all-targets -- -D warnings`
---------
Co-authored-by: Jess Martin <jessmartin@gmail.com>
## Summary
Graph rendering now accepts documented Fabro dotted DOT attributes end
to end while keeping raw Graphviz calls behind `fabro-graphviz`. The new
`RenderableDot` boundary applies Fabro render styling and normalization
before raw SVG rendering, and both the CLI subprocess and server path
now route through that typed boundary instead of calling `graphviz_sys`
directly outside the graphviz crate.
The branch also adds a small curated DOT compatibility corpus covering
ACP agent attributes, human default choices, and subworkflow manager
attributes. Those fixtures are exercised by both render and validation
tests, and the run overview now shows graph render errors directly
instead of falling through to the empty graph state.
## Verification
- `cargo nextest run -p fabro-graphviz`
- `cargo nextest run -p fabro-validate`
- `cargo nextest run -p fabro-cli render_graph`
- `cargo nextest run -p fabro-server
render_graph_from_manifest_accepts_fabro_dotted_attributes`
- `cargo +nightly-2026-04-14 fmt --check --all`
- `cargo +nightly-2026-04-14 clippy -p fabro-graphviz -p fabro-cli -p
fabro-server -p fabro-validate --all-targets -- -D warnings`
- `rg -n "graphviz_sys" lib/crates/fabro-cli lib/crates/fabro-server`
- `cd apps/fabro-web && bun test`
- `cd apps/fabro-web && bun run typecheck`
---
[](https://github.com/EveryInc/compound-engineering-plugin)
Generated with GPT-5 via [Codex](https://openai.com/codex)
---------
Co-authored-by: Jess Martin <27258+jessmartin@users.noreply.github.com>
## Summary
- add a regression test for unanswered human gate timeouts with
`human.default_choice`
- route human gate `timeout` through the interview timeout path so it
emits `interview.timeout` and selects the default target
- make timeout ownership explicit for handlers that consume
`node.timeout()`, including command and ACP handlers
Fixes#317
## Testing
- cargo nextest run -p fabro-workflow --test it
human_gate_timeout_routes_to_default_choice_when_unanswered
- cargo nextest run -p fabro-workflow timeout_policy
built_in_handlers_that_consume_node_timeout_manage_it_themselves
agent_handler_delegates_timeout_policy_to_backend
- cargo nextest run -p fabro-workflow script_handler_timeout
script_handler_timeout_error_includes_output_tails
writes_script_timing_json_on_timeout timeout_causes_fail_status_record
- cargo nextest run -p fabro-workflow wait_human
- cargo +nightly-2026-04-14 fmt --check --all
- cargo +nightly-2026-04-14 clippy -p fabro-workflow --all-targets -- -D
warnings
---------
Co-authored-by: Jess Martin <jessmartin@gmail.com>