Commit graph

1452 commits

Author SHA1 Message Date
Fabro
694aadcb47 fabro(01KMBJVDZAQHS05N3B6AQF67TG): verify (success)
Fabro-Run: 01KMBJVDZAQHS05N3B6AQF67TG
Fabro-Completed: 6
Fabro-Checkpoint: 79866d3710

⚒️ Generated with [Fabro](https://fabro.sh)
2026-03-22 20:13:19 +00:00
Fabro
31f35d328b fabro(01KMBJVDZAQHS05N3B6AQF67TG): impl (success)
Fabro-Run: 01KMBJVDZAQHS05N3B6AQF67TG
Fabro-Completed: 5
Fabro-Checkpoint: 44e02cea8e

⚒️ Generated with [Fabro](https://fabro.sh)
2026-03-22 20:13:16 +00:00
Fabro
c6448f3bef fabro(01KMBJVDZAQHS05N3B6AQF67TG): approve (success)
Fabro-Run: 01KMBJVDZAQHS05N3B6AQF67TG
Fabro-Completed: 4
Fabro-Checkpoint: 451699fa9a

⚒️ Generated with [Fabro](https://fabro.sh)
2026-03-22 20:13:05 +00:00
Fabro
e276e1adaf fabro(01KMBJVDZAQHS05N3B6AQF67TG): plan (success)
Fabro-Run: 01KMBJVDZAQHS05N3B6AQF67TG
Fabro-Completed: 3
Fabro-Checkpoint: bf4ada8383

⚒️ Generated with [Fabro](https://fabro.sh)
2026-03-22 20:13:01 +00:00
Fabro
6b01cb9f07 fabro(01KMBJVDZAQHS05N3B6AQF67TG): setup (success)
Fabro-Run: 01KMBJVDZAQHS05N3B6AQF67TG
Fabro-Completed: 2
Fabro-Checkpoint: 1b45208244

⚒️ Generated with [Fabro](https://fabro.sh)
2026-03-22 20:12:54 +00:00
Bryan Helmkamp
597601b314 Add token usage tracking to run conclusions and fix attach cleanup
Track input, output, cache, and reasoning tokens in the Conclusion
struct so the run summary can display token usage even when cost
pricing is unavailable. The summary now shows cache read/write stats
and reasoning tokens when present.

Also fix attach_run to kill the engine child process on timeout or
cancellation instead of orphaning it, and return exit code 1 on cancel.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 14:24:09 -04:00
Bryan Helmkamp
f28df7e536 Merge origin/main into issue-135
Integrates PR #137 (Extract `fabro resume` subcommand):
- resume.rs: take main's comprehensive extraction (devcontainer support,
  status guards, project config discovery, labels)
- create.rs: preserve issue-135's run_id passthrough for create+start+attach
- run.rs: preserve issue-135's print_run_summary (used by unified path)
- main.rs: take main's verbose flag propagation for resume

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 13:48:26 -04:00
brynary-fabro[bot]
165e2495bf Extract fabro resume subcommand (#137)
This PR extracts the `fabro resume` subcommand from `fabro run`,
replacing the `--resume` and `--run-branch` flags with a dedicated, more
ergonomic interface. Users can now run `fabro resume <RUN_ID>` instead
of constructing `fabro run --run-branch fabro/run/<RUN_ID>` manually,
and the command also accepts run ID prefixes (matching the pattern
established by `fabro rewind` and `fabro fork`). Checkpoint-file-based
resumption is also supported via `fabro resume --checkpoint
path/to/checkpoint.json --workflow workflow.fabro`.

The implementation moves the ~315-line `run_from_branch()` function out
of `run.rs` and into a new `commands/resume.rs` module, splitting it
into two preparation paths (`prepare_from_checkpoint` and
`prepare_from_branch`) that converge on a shared `run_resumed()` tail.
Several previously private helpers in `run.rs` are widened to
`pub(crate)` to allow sharing: `local_sandbox_with_callback`,
`resolve_ssh_config`, `resolve_ssh_clone_params`,
`resolve_preserve_sandbox`, `generate_retro`, `write_finalize_commit`,
`print_final_output`, `print_assets`, and the new `default_run_dir`
helper extracted from duplicated inline logic. The `RunArgs` struct
loses its `resume` and `run_branch` fields along with their
`conflicts_with` annotations, and `RunSpec` drops the corresponding
fields with `#[serde(default)]` for backward compatibility.

Documentation across `docs/reference/cli.mdx`,
`docs/execution/checkpoints.mdx`, and
`docs/core-concepts/how-fabro-works.mdx` is updated to reflect the new
interface, and the `rewind`/`fork` commands now hint `fabro resume
<short-prefix>` instead of the full branch name.

### Fabro Details

<details>
<summary>Ran 9 stages in 30m 25s for $6.70</summary>

| Stage | Duration | Cost | Retries |
|---|---|---|---|
| start | 0s | – | 0 |
| toolchain | 0s | – | 0 |
| preflight_compile | 1m 10s | – | 0 |
| preflight_lint | 13s | – | 0 |
| implement | 18m 30s | $3.95 | 0 |
| simplify_opus | 9m 38s | $2.75 | 0 |
| simplify_gpt | 0s | – | 0 |
| verify | 19s | – | 0 |
| fmt | 1s | – | 0 |
| **Total** | **30m 25s** | **$6.70** | **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-6; }
        "
    ]
    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 clippy -q --workspace -- -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-54)", prompt="@prompts/simplify.md", model="gpt-54"]
    verify            [label="Verify", shape=parallelogram, script="cargo clippy -q --workspace -- -D warnings 2>&1 && cargo nextest run --cargo-quiet --workspace --status-level fail 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 and test failures.", max_visits=3]
    fmt               [label="Format", shape=parallelogram, script="cargo fmt --all 2>&1", max_retries=0]

    start -> toolchain
    toolchain -> preflight_compile [condition="outcome=success"]
    toolchain -> exit
    preflight_compile -> preflight_lint [condition="outcome=success"]
    preflight_compile -> exit
    preflight_lint -> implement [condition="outcome=success"]
    preflight_lint -> fix_lints
    fix_lints -> preflight_lint
    implement -> simplify_opus -> simplify_gpt -> verify
    verify -> fmt   [condition="outcome=success"]
    verify -> fixup
    fixup -> verify
    fmt -> exit
}

```

</details>

⚒️ Generated with [Fabro](https://fabro.sh)

---------

Co-authored-by: Fabro <noreply@fabro.sh>
Co-authored-by: Bryan Helmkamp <bryan@brynary.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 13:45:06 -04:00
Bryan Helmkamp
7bde4e9e10 Configure nextest slow-timeout profiles for unit vs e2e tests
Tighten default profile to 2s slow / 4s kill for fast feedback on
hanging unit tests, and add an `e2e` profile with 10s slow / 30s kill
for ignored tests that hit real APIs. Switch CLAUDE.md/AGENTS.md and
CI to use `cargo nextest run`. Fix retry_emits_stage_started_per_attempt
test that exceeded the new timeout by using aggressive backoff preset
instead of the 5s default.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 15:47:20 -04:00
Bryan Helmkamp
e47d00a371 Fix three bugs in create→start→attach path that caused test hangs
1. _run_engine crashed for .fabro workflows because it hardcoded
   run.toml as the workflow path, but create_run only writes run.toml
   for .toml configs. Now falls back to graph.fabro when run.toml is
   absent.

2. attach_run couldn't detect a crashed engine because start_run
   dropped the Child handle, creating a zombie that kill(pid, 0)
   reported as alive. Now start_run returns the Child and attach_run
   uses try_wait() to safely detect exit.

3. create_run ignored --run-id, always generating a new ULID. Now
   uses args.run_id when provided.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 15:41:56 -04:00
Bryan Helmkamp
9792d96901 Unify fabro run foreground to use create + start + attach
Foreground mode previously called `run_command()` directly while detach
mode used `create_run() + start_run()` — two entirely different code
paths. Now both modes go through the same create + start pipeline, with
foreground adding an `attach_run()` call. Only `--preflight` remains as
a special case since it validates config without creating a run dir.

Extract `print_run_summary()` from `run_command` to display the run
result block (status, duration, cost, PR link, output, assets) from
`conclusion.json` after attach completes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 15:25:06 -04:00
Bryan Helmkamp
26a608a5fb Merge remote-tracking branch 'origin/main' into fabro/run/01KM8C0SVZW77C5W018CYEVE4Y 2026-03-21 14:39:15 -04:00
Bryan Helmkamp
2a791eab41 Fix resume command: clap constraints, sleep inhibitor, Docker sandbox
Add conflicts_with/requires to ResumeArgs checkpoint field so invalid
flag combos produce clap usage errors instead of runtime failures. Add
sleep_inhibitor guard to Command::Resume matching Run and Exec. Split
Docker out of Local match arm in prepare_from_checkpoint to create a
proper DockerSandbox instead of silently falling back to LocalSandbox.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 14:38:59 -04:00
brynary-fabro[bot]
cd7f0b1415 Support Anthropic fast mode (speed: fast) (#127)
This PR adds support for Anthropic's fast mode beta feature (`speed:
fast`), which enables up to 2.5x faster output for Claude Opus 4.6 at a
6x pricing premium. The implementation follows the same patterns
established by `reasoning_effort`, threading a new `speed:
Option<String>` field through the request/response pipeline from graph
stylesheet properties down through agent configuration, session
management, and the Anthropic provider adapter.

On the provider side, when `speed: "fast"` is set, the `ApiRequest`
struct now includes the `speed` field in the serialized JSON body, and
the `build_beta_header` function injects the required `anthropic-beta:
fast-mode-2026-02-01` header alongside any existing beta headers (cache,
interleaved thinking, etc.) without duplication. The response's
`usage.speed` field is parsed and propagated back through both streaming
and non-streaming paths into `StageUsage` and `Usage` types for
tracking.

Cost accounting applies a 6x multiplier in `compute_stage_cost` when
`speed == "fast"`, reflecting Anthropic's actual pricing differential.
The feature is configurable via stylesheet (`* { speed: fast; }`), which
gets wired through `SessionConfig` and prompt-mode `Request`
construction in the backend layer. New tests cover the API request
serialization, beta header injection, combined cache+fast-mode headers,
and the cost multiplier, while all existing test fixtures have been
updated with `speed: None` to maintain struct exhaustiveness.

### Fabro Details

<details>
<summary>Ran 9 stages in 58m 27s for $6.56</summary>

| Stage | Duration | Cost | Retries |
|---|---|---|---|
| start | 0s | – | 0 |
| toolchain | 0s | – | 0 |
| preflight_compile | 1m 20s | – | 0 |
| preflight_lint | 14s | – | 0 |
| implement | 47m 31s | $5.36 | 0 |
| simplify_opus | 7m 59s | $1.20 | 0 |
| simplify_gpt | 0s | – | 0 |
| verify | 25s | – | 0 |
| fmt | 1s | – | 0 |
| **Total** | **58m 27s** | **$6.56** | **0** |

</details>

<details>
<summary>Ran <code>ImplementAndSimplify.fabro</code> (12 nodes and 15
edges)</summary>

```dot
digraph ImplementAndSimplify {
    graph [
        goal="Implement and simplify",
        model_stylesheet="
            * { backend: api; model: claude-opus-4-6;}
        "
    ]
    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 clippy -q --workspace -- -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-54)", prompt="@prompts/simplify.md", model="gpt-54"]
    verify            [label="Verify", shape=parallelogram, script="cargo clippy -q --workspace -- -D warnings 2>&1 && cargo nextest run --cargo-quiet --workspace --status-level fail 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 and test failures.", max_visits=3]
    fmt               [label="Format", shape=parallelogram, script="cargo fmt --all 2>&1", max_retries=0]

    start -> toolchain
    toolchain -> preflight_compile [condition="outcome=success"]
    toolchain -> exit
    preflight_compile -> preflight_lint [condition="outcome=success"]
    preflight_compile -> exit
    preflight_lint -> implement [condition="outcome=success"]
    preflight_lint -> fix_lints
    fix_lints -> preflight_lint
    implement -> simplify_opus -> simplify_gpt -> verify
    verify -> fmt   [condition="outcome=success"]
    verify -> fixup
    fixup -> verify
    fmt -> exit
}

```

</details>

⚒️ Generated with [Fabro](https://fabro.sh)

---------

Co-authored-by: Fabro <noreply@fabro.sh>
2026-03-21 14:37:42 -04:00
Bryan Helmkamp
844e00b72c Fix three bugs in run.toml snapshot handling from PR #138 review
- Fix false positive in backward-compat fallback: use path.exists() instead
  of error chain inspection to distinguish missing run.toml from one with
  a broken internal reference (e.g. missing Dockerfile)
- Skip write_run_config_snapshot in _run_engine path to prevent double
  apply_defaults corrupting the snapshot on each restart
- Resolve ${env.VARNAME} refs in run_defaults.sandbox.env when falling
  back for bare .fabro workflows

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 14:32:09 -04:00
Bryan Helmkamp
161caf54b0 Merge remote-tracking branch 'origin/main' into fabro/run/01KM8C0SVZW77C5W018CYEVE4Y
# Conflicts:
#	lib/crates/fabro-cli/src/commands/create.rs
#	lib/crates/fabro-cli/src/commands/run.rs
2026-03-21 14:19:01 -04:00
brynary-fabro[bot]
4da1f59695 Fix: Workflow TOML config lost in detach mode (#138)
This PR fixes a bug where workflow TOML configuration (including
`[pull_request]` settings) was silently dropped when running in detached
mode (`fabro run -d`). The root cause was a three-part failure:
`create.rs` checked the raw CLI argument string for a `.toml` extension
instead of the resolved path, so `run.toml` was never written to the run
directory; `RunEngine` always fell back to `graph.fabro` (a DOT file),
causing `prepare_workflow` to return `run_cfg = None` and lose all
TOML-level configuration; and `pull_request`/`asset_globs` fields in
`RunConfig` had no fallback to `run_defaults` when `run_cfg` was absent.

The fix replaces the naive file-copy approach with a proper
serialization pipeline. Rather than copying the raw TOML (which would
contain a `graph` field pointing to a nonexistent file in the run
directory), `create.rs` now calls `write_run_config_snapshot`, which
serializes the already-merged `WorkflowRunConfig` and rewrites the
`graph` field to `"graph.fabro"` — the canonical cached name. This makes
the run directory fully self-contained with all defaults merged,
environment variables resolved, and the graph path correct. `RunEngine`
in `main.rs` now unconditionally points at `run.toml`; a new
`resolve_workflow_source` helper handles the `.toml` path by loading the
config and resolving the graph path, with a backward-compatible fallback
to `graph.fabro` for older detached runs created before this change.

As defense-in-depth, fallbacks to `run_defaults` are added throughout
`run.rs` for `pull_request`, `asset_globs`, `devcontainer`, and
`sandbox.env` — ensuring bare `.fabro` files passed directly still pick
up project-level defaults. Two new unit tests verify the serialization
round-trip (confirming `graph` is rewritten and `pull_request` config is
preserved) and the missing-`run.toml` fallback behavior.

### Fabro Details

<details>
<summary>Ran 9 stages in 26m 29s for $9.17</summary>

| Stage | Duration | Cost | Retries |
|---|---|---|---|
| start | 0s | – | 0 |
| toolchain | 0s | – | 0 |
| preflight_compile | 1m 14s | – | 0 |
| preflight_lint | 13s | – | 0 |
| implement | 4m 54s | $0.71 | 0 |
| simplify_opus | 8m 41s | $1.77 | 0 |
| simplify_gpt | 10m 41s | $6.69 | 0 |
| verify | 18s | – | 0 |
| fmt | 1s | – | 0 |
| **Total** | **26m 29s** | **$9.17** | **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-6; }
        "
    ]
    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 clippy -q --workspace -- -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-54)", prompt="@prompts/simplify.md", model="gpt-54"]
    verify            [label="Verify", shape=parallelogram, script="cargo clippy -q --workspace -- -D warnings 2>&1 && cargo nextest run --cargo-quiet --workspace --status-level fail 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 and test failures.", max_visits=3]
    fmt               [label="Format", shape=parallelogram, script="cargo fmt --all 2>&1", max_retries=0]

    start -> toolchain
    toolchain -> preflight_compile [condition="outcome=success"]
    toolchain -> exit
    preflight_compile -> preflight_lint [condition="outcome=success"]
    preflight_compile -> exit
    preflight_lint -> implement [condition="outcome=success"]
    preflight_lint -> fix_lints
    fix_lints -> preflight_lint
    implement -> simplify_opus -> simplify_gpt -> verify
    verify -> fmt   [condition="outcome=success"]
    verify -> fixup
    fixup -> verify
    fmt -> exit
}

```

</details>

⚒️ Generated with [Fabro](https://fabro.sh)

---------

Co-authored-by: Fabro <noreply@fabro.sh>
Co-authored-by: Bryan Helmkamp <bryan@brynary.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 14:15:13 -04:00
Bryan Helmkamp
ede54fcdb3 Add Daytona sandbox support and progress.jsonl/live.json writers to resume command
The resume command had two gaps: Daytona sandbox selection bailed with
"not yet supported" despite being documented, and run_resumed omitted
the JSONL/live.json event writers that run_command registers, so
`fabro logs`, `fabro attach`, and the web UI saw no events for resumed
runs. Wire up DaytonaSandbox in both prepare paths and mirror the event
writer block from run_command. Widen resolve_daytona_config and
build_event_envelope to pub(crate).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 13:50:07 -04:00
Bryan Helmkamp
925411e229 Remove stale ssh field from RunSpec in start.rs test
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 13:24:31 -04:00
Bryan Helmkamp
b22862d04c Add clarifying comment to EventEmitter::emit() snapshot behavior
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 13:14:48 -04:00
Bryan Helmkamp
e2f884df23 Fix resume command bugs, remove --ssh flag, simplify EventEmitter
Fix three bugs from PR review in the new `fabro resume` command:

1. RunArgs.workflow lost its required constraint when --run-branch was
   removed — add #[arg(required = true)] so clap enforces it.

2. run_resumed ignored --preserve-sandbox (hardcoded false),
   --verbose (no ProgressUI), and --ssh (no listener). Wire
   preserve_sandbox through resolve_preserve_sandbox, create a
   ProgressUI registered on the emitter, and handle SSH access.

3. prepare_from_checkpoint unconditionally created a LocalSandbox,
   ignoring --sandbox. Add the same sandbox resolution logic used by
   prepare_from_branch (Local, Docker, Ssh, Exe, Daytona).

Remove --ssh from `fabro run` and `fabro resume` since `fabro ssh`
is the dedicated command now. Remove the flag from RunArgs, ResumeArgs,
RunSpec, and all docs/changelogs.

Simplify EventEmitter: change on_event to take &self (via
Mutex<Vec<Arc<...>>>) instead of &mut self, removing the need for
the late_listeners workaround. emit() snapshots the listener list
before dispatching to prevent deadlocks from reentrant emit calls.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 13:07:58 -04:00
Bryan Helmkamp
756915b1ea Merge branch 'main' into fabro/run/01KM8C0SVZW77C5W018CYEVE4Y 2026-03-21 12:35:48 -04:00
Bryan Helmkamp
306f5b4b6c Update model catalog snapshots with new effort field
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 12:11:09 -04:00
Bryan Helmkamp
f307c1fbdf Fix OpenAI reasoning round-trip: use store: false with encrypted_content
The OpenAI Responses API requires store: false for non-Azure endpoints.
Reasoning items round-trip correctly by requesting encrypted_content
via the `include` field, which embeds them in the response payload
rather than relying on server-side storage.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 11:46:01 -04:00
Bryan Helmkamp
025316500d Fix Sonnet 4.5 effort parameter error by converting to thinking API
claude-sonnet-4-5 doesn't support output_config.effort — it needs the
older thinking API with budget_tokens. Add an `effort` feature flag to
ModelFeatures and have the Anthropic adapter auto-convert reasoning_effort
to a thinking config for models that lack effort support.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 11:44:15 -04:00
Bryan Helmkamp
35cac9185b Fix OpenAI provider: always set store: true
store: !codex_mode was sending store: false for non-Codex models,
which prevented reasoning items from being persisted. This broke
multi-turn conversations where reasoning items from turn 1 need to
be sent back in turn 2.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 11:37:30 -04:00
Bryan Helmkamp
d29fd66ac9 Run model tests concurrently with progress bar
Tests 6 models at a time in shuffled order to spread load across
providers. Uses indicatif progress bar instead of per-model eprint
lines. Results table is sorted back to original catalog order.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 11:36:26 -04:00
Bryan Helmkamp
1493c848b5 Run only the deep test when --deep is passed, not both
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 11:28:23 -04:00
Bryan Helmkamp
73a7c28bd2 Add --deep flag to fabro model test for multi-turn tool-use validation
Exercises a 2+ turn tool-call round-trip with reasoning_effort("high")
to catch bugs like store: false that only manifest when reasoning items
from turn 1 are sent back in turn 2.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 11:27:25 -04:00
Fabro
78ec86e19a fabro(01KM8C0SVZW77C5W018CYEVE4Y): fmt (success)
Fabro-Run: 01KM8C0SVZW77C5W018CYEVE4Y
Fabro-Completed: 9
Fabro-Checkpoint: c3f060a8aa

⚒️ Generated with [Fabro](https://fabro.sh)
2026-03-21 14:46:01 +00:00
Fabro
05af2d9f75 fabro(01KM8C0SVZW77C5W018CYEVE4Y): verify (success)
Fabro-Run: 01KM8C0SVZW77C5W018CYEVE4Y
Fabro-Completed: 8
Fabro-Checkpoint: 8b469cf6e9

⚒️ Generated with [Fabro](https://fabro.sh)
2026-03-21 14:45:58 +00:00
Fabro
ea017b956d fabro(01KM8C0SVZW77C5W018CYEVE4Y): simplify_gpt (fail)
Fabro-Run: 01KM8C0SVZW77C5W018CYEVE4Y
Fabro-Completed: 7
Fabro-Checkpoint: c450fb9456

⚒️ Generated with [Fabro](https://fabro.sh)
2026-03-21 14:45:36 +00:00
Fabro
7be58bec5a fabro(01KM8C0SVZW77C5W018CYEVE4Y): simplify_opus (success)
Fabro-Run: 01KM8C0SVZW77C5W018CYEVE4Y
Fabro-Completed: 6
Fabro-Checkpoint: 514cac59f5

⚒️ Generated with [Fabro](https://fabro.sh)
2026-03-21 14:45:27 +00:00
Fabro
52a46a218b fabro(01KM8C0SVZW77C5W018CYEVE4Y): implement (success)
Fabro-Run: 01KM8C0SVZW77C5W018CYEVE4Y
Fabro-Completed: 5
Fabro-Checkpoint: 7b249487c8

⚒️ Generated with [Fabro](https://fabro.sh)
2026-03-21 14:35:46 +00:00
Bryan Helmkamp
a915543fc0 Suppress stderr output in detach and create modes
`prepare_workflow` unconditionally printed Workflow/Graph/Goal info to
stderr, which leaked into `--detach` and `create` output that should
only emit the run ID. Add a `quiet` flag to suppress this output.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 10:26:20 -04:00
Fabro
3295828378 fabro(01KM8C0SVZW77C5W018CYEVE4Y): preflight_lint (success)
Fabro-Run: 01KM8C0SVZW77C5W018CYEVE4Y
Fabro-Completed: 4
Fabro-Checkpoint: 6272a66f77

⚒️ Generated with [Fabro](https://fabro.sh)
2026-03-21 14:17:13 +00:00
Fabro
99d7b6bb8f fabro(01KM8C0SVZW77C5W018CYEVE4Y): preflight_compile (success)
Fabro-Run: 01KM8C0SVZW77C5W018CYEVE4Y
Fabro-Completed: 3
Fabro-Checkpoint: 824e840a23

⚒️ Generated with [Fabro](https://fabro.sh)
2026-03-21 14:16:57 +00:00
Fabro
c12260fa9f fabro(01KM8C0SVZW77C5W018CYEVE4Y): toolchain (success)
Fabro-Run: 01KM8C0SVZW77C5W018CYEVE4Y
Fabro-Completed: 2
Fabro-Checkpoint: 8073579618

⚒️ Generated with [Fabro](https://fabro.sh)
2026-03-21 14:15:45 +00:00
Bryan Helmkamp
1922240901 Add missing attributes to DOT language reference docs
Document loop_restart_signature_limit (graph), fallback_retry_target
(node), freeform (edge), and the full manager loop node attribute table
that was previously absent.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 10:07:11 -04:00
Bryan Helmkamp
2172f5987e Add Node Outcomes docs page and fix status gaps across docs
New page (execution/outcomes.mdx) defines the 5 stage statuses, documents
how each handler produces them, and explains allow_partial, auto_status,
the retry loop, goal gate interaction, and outcome in edge conditions.

Existing pages updated: added missing `skipped` status to outcome key
descriptions, improved `goal_gate`/`auto_status` descriptions in the
dot-language reference, added `allow_partial` to the attributes table,
and added cross-links from failures.mdx and transitions.mdx.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 10:05:36 -04:00
Bryan Helmkamp
f4e9503df1 Fix edge thread_id precedence to match fidelity behavior
Edge thread_id should override node thread_id, consistent with how
resolve_fidelity already works. The previous order was reversed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 09:10:52 -04:00
brynary-fabro[bot]
c119bf3c79 Use short hex IDs for subagents instead of UUIDs (#128)
This PR updates subagent ID generation to use short 8-character hex
strings instead of full UUID v4 strings. Previously, subagent IDs were
36-character UUIDs (e.g. `550e8400-e29b-41d4-a716-446655440000`), which
were verbose in CLI output and unwieldy when the LLM needed to reference
them in tools like `send_input`, `wait`, and `close_agent`. The new
format generates IDs like `a3f1b20c` — compact, human-readable, and with
~4 billion possible values, effectively collision-free within a session.

The change is made at the source in `subagent.rs`, where UUID generation
is replaced with `format!("{:08x}",
uuid::Uuid::new_v4().as_fields().0)`. Because IDs are now inherently 8
characters, the display-layer truncations in `cli.rs` (5 occurrences)
and `run_progress.rs` (2 occurrences) are redundant and have been
removed — `agent_id` is used directly in format strings instead of a
`short_id` slice.

### Plan Summary

- **Replace UUID generation** in `subagent.rs`: use the first field of a
UUID v4 formatted as 8-char lowercase hex, yielding IDs like `a3f1b20c`
instead of full 36-char UUIDs
- **Remove `short_id` truncation** in `cli.rs` (5 places) and
`run_progress.rs` (2 places): since IDs are now already 8 chars, the
`let short_id = &agent_id[..8.min(agent_id.len())]` pattern is
eliminated and `{agent_id}` is used directly in all format strings
- No test changes required — existing tests use hardcoded IDs like
`"sa-1"` and don't assert on ID length or format

<details>
<summary>Full plan</summary>

````md
The plan has been written to `/home/daytona/workspace/plan.md`.

It covers:
- **4 files to modify**: `fabro-agent/Cargo.toml` (add `rand` dep), `subagent.rs` (replace UUID with 8-char hex), `cli.rs` (remove 5 `short_id` truncations), `run_progress.rs` (remove 2 `short_id` truncations)
- **Step-by-step implementation** with exact line references and before/after code
- **Verification commands** to confirm correctness
- **Test case analysis** explaining why no test changes are needed
````

</details>

### Fabro Details

<details>
<summary>Ran 3 stages in 18m 46s for $0.57</summary>

| Stage | Duration | Cost | Retries |
|---|---|---|---|
| start | 0s | – | 0 |
| plan | 1m 28s | $0.57 | 0 |
| implement | 17m 6s | – | 0 |
| **Total** | **18m 46s** | **$0.57** | **0** |

</details>

<details>
<summary>Ran <code>GhImplement.fabro</code> (4 nodes and 3
edges)</summary>

```dot
digraph GhImplement {
    graph [
        goal="Implement a GitHub issue",
        model_stylesheet="
            * { model: claude-opus-4-6; }
        "
    ]
    rankdir=LR

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

    plan [label="Plan", prompt="Fetch the GitHub issue from the goal using: gh issue view $goal --json title,body,labels,comments\n\nRead the issue title, description, and any comments carefully. Analyze what code changes are needed to resolve the issue.\n\nWrite a detailed implementation plan to plan.md that includes:\n- Summary of the issue\n- Files to create or modify\n- Step-by-step implementation approach\n- Test cases to add or update\n\nThe plan should be specific enough for another agent to implement without seeing the original issue.\n\nRespond with the location of the plan file (plan.md)."]

    implement [label="Implement", shape=house, stack.child_workflow="fabro/workflows/implement/workflow.fabro", manager.max_cycles=100]

    start -> plan
    plan -> implement [fidelity="summary:high"]
    implement -> exit
}

```

</details>

⚒️ Generated with [Fabro](https://fabro.sh)

---------

Co-authored-by: Fabro <noreply@fabro.sh>
2026-03-20 21:38:16 -04:00
Bryan Helmkamp
77064677d5 Rename gh-implement to implement-issue and implement to implement-plan
Gives both workflows clearer, consistent names. Updates the graph
identifiers and the child_workflow reference accordingly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 20:58:59 -04:00
Bryan Helmkamp
c7a3495c3e Add gh-implement workflow for GitHub issue implementation
Introduces a two-stage workflow (plan → implement) that fetches a GitHub
issue via `gh issue view`, writes an implementation plan, then delegates
to the existing implement workflow. Also removes the unnecessary
`backend: api` directive from the implement workflow's model stylesheet.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 20:58:59 -04:00
Bryan Helmkamp
bcba32329a Destructure ModelInfo to avoid unnecessary clone
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 20:58:59 -04:00
Bryan Helmkamp
07c3669d1c Resolve model aliases to canonical IDs in workflow graph nodes
ProviderInferenceTransform only inferred the provider but passed the
raw alias (e.g. "gpt-54") to the LLM API, causing request failures.
Rename to ModelResolutionTransform and resolve aliases via the model
catalog so the canonical ID (e.g. "gpt-5.4") is used in API calls.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 20:58:59 -04:00
Bryan Helmkamp
088ac91d53 Unify goal prefix stripping into shared strip_goal_decoration() in fabro-util
Three places stripped markdown headings and `Plan:` prefixes from goals
with slightly different logic. Extract a shared function so all call sites
behave consistently, and fix `fabro run` which wasn't stripping at all.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 20:58:59 -04:00
Bryan Helmkamp
07406678c6 Add gh-triage workflow to check open PRs and issues via gh CLI
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 20:58:59 -04:00
Bryan Helmkamp
a80839863a Show tool details for remaining agent tools in fabro logs and fabro attach
Add detail text for wait, send_input, close_agent (agent_id),
apply_patch (ellipsis), and read_many_files (file count) — these
were falling through to the `_ => None` catch-all in both
`tool_detail()` and `tool_display_name()`.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 20:58:59 -04:00
Bryan Helmkamp
ff051631a8 Show tool details for spawn_agent, use_skill, list_dir in fabro logs
Sync `tool_detail()` in logs.rs with `tool_display_name()` in
run_progress.rs — the two had drifted, so `fabro logs -pf` was
missing detail text for these tool types.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 20:58:59 -04:00