mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-08-28 05:27:41 +00:00
## Summary
The run page stage badge showed only the model name. This PR plumbs
`reasoning_effort` and `speed` from the LLM call site all the way
through the event stream, store projection, API, and UI so the badge now
renders `gpt-5.5 · high`.
### Plan Summary
- **Event props** — `AgentSessionActivatedProps` and `StagePromptProps`
gain `reasoning_effort: Option<ReasoningEffort>` and `speed:
Option<Speed>` with `serde(default, skip_serializing_if)` for
back-compat.
- **Typed projection** — `provider_used: Option<serde_json::Value>` is
replaced by `Option<StageModelUsage>`, a proper struct in `fabro-types`
with factory methods (`from_prompt_props`,
`from_agent_session_activated`). The freeform JSON bag is gone.
- **Emission sites** — `ActivationLeaseOptions` carries the new fields;
`emit_stage_prompt()` (new shared helper) resolves
`EffectiveRequestControls` via the backend and stamps them on
`Event::Prompt`. `AgentHandler` and `PromptHandler` both call this
helper instead of building the event inline.
- **ACP path** — `AgentAcpStarted` no longer writes `provider_used`; the
canonical source is the later `AgentSessionActivated` event, which is
already emitted for ACP steering sessions. Runs without a hub
legitimately leave `provider_used` unset.
- **OpenAPI** — new `StageModelUsage` and `ReasoningEffort` schemas
replace the `object | null` bag; `build.rs` maps both to the canonical
Rust types; a new `stage_model_usage_round_trip` integration test
enforces the parity requirement.
- **UI** — `extractStageModel` (event-scanning heuristic) is deleted;
replaced by `formatStageModelUsageLabel` and `stageModelUsageTitle` that
read directly off `selectedStage.providerUsed`. `parseFanInOutcome` now
sources the reducer model from `stage.prompt` instead of
`prompt.completed`.
### Key design decisions
**No type sprawl**: `fabro_model::ReasoningEffort` and `Speed` are
reused verbatim via `with_replacement` in `build.rs` — no parallel
enums.
**ACP behavior change**: previously `AgentAcpStarted` wrote a bespoke
`provider_used` blob and a later `AgentSessionActivated` would be
ignored for ACP sessions. Now `AgentSessionActivated` is the single
write path for all modes; ACP runs that never activate a steering hub
correctly leave `provider_used = null`. The integration test (`acp.rs`)
is updated to assert the new shape, and the unit test is renamed
`agent_acp_started_alone_leaves_stage_provider_used_unset` to document
intent.
**`emit_stage_prompt` helper**: both `AgentHandler` and the existing
prompt path share one function to avoid the two call sites drifting
apart again.
### Fabro Details
<details>
<summary>Ran 9 stages in 98m 44s for $65.70</summary>
| Stage | Duration | Cost | Retries |
|---|---|---|---|
| start | 0s | – | 0 |
| toolchain | 2s | – | 0 |
| preflight_compile | 2m 15s | – | 0 |
| preflight_lint | 2m 30s | – | 0 |
| implement | 42m 19s | $26.85 | 0 |
| simplify_opus | 38m 3s | $35.17 | 0 |
| simplify_gpt | 9m 20s | $3.68 | 0 |
| verify | 3m 38s | – | 0 |
| fmt | 3s | – | 0 |
| **Total** | **98m 44s** | **$65.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-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.", model="gpt-55", reasoning_effort="xhigh"]
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: Fabro <noreply@fabro.sh>
Co-authored-by: Bryan Helmkamp <bryan@brynary.com>
Co-authored-by: Bryan Helmkamp <bhelmkamp@users.noreply.github.com>
|
||
|---|---|---|
| .. | ||
| brainstorms | ||
| ideation | ||
| internal | ||
| plans | ||
| public | ||
| superpowers | ||