- Add /automations/new with Basics/Source/Goal/Triggers panels and a
kebab-case Slug auto-derived from Name until the user edits it
- Wire the "Create Automation" button on /automations to the new page
- Move individual automation URL from /automations/<slug> to
/automation/<slug>; /automations and /automations/new are unchanged
- Refresh /settings/secrets/new to use the Panel + Row layout pattern:
breadcrumb header, one field per row, plain footer, no inside-Panel
stacked form
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The lifecycle status pill next to the run title now hides when the run
is in the initializing column, mirroring the board view behavior. This
removes the duplicate "Initializing" / "STARTING" indicators that
appeared together on the row during startup.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Connect menu (Preview / SSH) on the run detail header was wired up
only in demo mode and the items were never connected to real actions.
Drop the dead UI.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Pending, Skipped, and Cancelled status pills used `text-fg-muted`
(#4B5768) on `bg-overlay-strong`, producing ~1.7:1 contrast against the
panel — well below WCAG AA. Switch to `text-fg-3` (#A8B5C5) for ~6:1
while keeping the subdued look that distinguishes these states from
active/result tones.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reuses the StagePopover from the stages sidebar so graph nodes reveal
the same handler, timing, model, and status-specific detail on hover.
The graph is server-rendered SVG injected via innerHTML, so listeners
are attached imperatively alongside the existing click handlers; the
popover is portal-positioned via the shared hoverCardStyle helper.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Hovering a stage row reveals handler, timing, model, and status-specific
detail — failure reason for failed/retrying, notes for skipped/partial,
tokens and files-touched for succeeded. Lazy-fetches per-stage events
on first hover via the existing useRunStageEvents hook; HoverCard gains
an openDelay so a cursor sweep doesn't trigger fetches for every row.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two icon buttons on the stage events toolbar, to the right of the
model usage label: copy all loaded events as pretty-printed JSON, or
download them as JSONL. Both read from the existing SWR cache, so no
new API surface — for in-flight stages they're a snapshot of what the
client has fetched so far.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the native browser title tooltip on the model chip with a
structured HoverCard listing provider, model, reasoning, and speed.
Shorten the chip label to `model[effort]` (e.g. `gpt-5.5[xhigh]`).
Search expands on focus with a width transition and collapses on blur
when empty. Ghost icon style when collapsed; full input styling slides
in on expand.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
On /settings/models, unconfigured providers now offer "Add secret →"
alongside "Get API key →", deep-linking to /settings/secrets/new with
the expected vault secret name prefilled. Driven by a new
`expected_secret_name` field on the Provider API, derived from the
first vault credential in the catalog so the suggestion stays in sync
with the catalog instead of being hardcoded on the frontend.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Surfaces the existing summary.size (XS/S/M/L/XL) as a compact bold
badge to the right of the last-event chip, with a tooltip showing
the underlying billed cost. Hidden when no billing data is available
so we don't show a misleading "XS" for runs with zero cost.
Wires the existing POST /api/v1/runs/delete endpoint into the runs list
selection toolbar. Surfaces a confirmation dialog before calling the
fail-soft batch delete, since deletion is irreversible. Only archived
runs are eligible, matching the single-run delete semantics.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
## Summary
Three small UX tweaks to the agent stage insights sidebar so it reads as
informational rather than alarming.
- **Permission badge stays neutral.** Removed `text-coral` (red) from
Full and `text-amber` (orange) from Read/write — every level now sits in
the foreground palette (`fg-2` / `fg-3`). Icon shape (lock / pencil /
bolt) carries the level distinction and the badge label spells it out.
- **Collapsed footer always uses the muted lock icon.** The footer is a
static affordance, not a danger signal, so a Full-access stage no longer
splashes a colored icon in the corner of the page.
- **Hide the Todos section when there are zero todos.** No header row,
no `0/0` count, no "No todos." line — saves vertical space on stages
where the agent never used TodoWrite.
## Test plan
- [x] `bun run typecheck` (apps/fabro-web)
- [x] `bun test app/components/stage-insights-sidebar.test.tsx` (8/8
pass)
- [ ] Visually confirm in a browser: Full-access agent stage shows a
neutral bolt + "Full access" label (no red); collapsed sidebar footer
shows a single muted lock regardless of level; stage with zero todos has
no Todos section.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Track which MCP servers the agent invoked. New `invoked: bool` on
`McpServerProjection` (OpenAPI + Rust type + generated TS client),
set by the projector when an `AgentToolStarted` event has an
`mcp__<server>__*` tool_name. UI shows `used/total` in the section
header, replaces the tool count with `used` on invoked rows, and dims
rows that weren't invoked. Sticky across status re-reads.
- Quiet noisy context-window warnings. When the snapshot's total is
provider-authoritative (ProviderApiScaledBreakdown or
ResponseUsageScaledBreakdown), drop local-estimator warning codes
from the snapshot — they imply the user-facing total is wrong when
it isn't. Also dedupe by code so a 35-turn conversation with opaque
reasoning blocks no longer surfaces 35 copies of the same warning.
- Reword the legitimately-local warnings. "opaque provider context
estimated from JSON" → "Some content couldn't be precisely
tokenized; total is approximate." Same treatment for the media,
provider-options, and whole-request local-estimate messages.
- Rename the sidebar header from "INSIGHTS" to "AGENT" to better
describe what it shows.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Renders a second left sidebar on /runs/<id>/stages/<agent-stage> with
todos, color-coded context-window usage and breakdown, skills, MCP
servers, and permission level. Data comes from the existing
StageProjection and the context-window endpoint added in #378; no API
changes.
Also set permission_level to Full on workflow agent SessionOptions —
workflow agents run with no tool_access_policy and expose the full
tool registry, so Full is the honest report and avoids "Unknown"
rendering in the new sidebar.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Runs nav link goes to /runs (no query string), so the route briefly
rendered with default params (view=columns, archived=false) before a
post-commit useEffect restored the URL from stored preferences. On
repeat clicks the useAllRuns SWR cache for {includeArchived:false}
returned zero rows immediately, flashing the Quick Start landing for
users whose only runs are archived.
Resolve workspace search params synchronously during render via
resolveRunsWorkspaceSearchParams(), so the first frame already reflects
stored prefs. The effect now just writes the URL back to match.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
## Summary
Adds a black-box server API regression test for the per-run event append
race observed as projection cache sequence gaps and non-contiguous event
streams.
The test creates a run through the public API, simulates a server
restart with a fresh AppState over the same backing object store, then
concurrently appends events through `POST /api/v1/runs/{id}/events`. The
expected contract is that all appends succeed and the resulting event
sequence is durable and contiguous.
## Test Plan
- `cargo nextest run -p fabro-server --features test-support --test it
concurrent_event_appends_after_restart_keep_projection_cache_contiguous`
Expected current result: fails RED, demonstrating the existing race.
Example observed failure showed duplicate/missing seqs such as `[1, 2,
3, 3, 4, 4, ...]` instead of a contiguous sequence through 66.
Fixes implement-plan runs failing at verify time when cloned sandboxes
lack Git identity, and prevents the verify forbidden-pattern scan from
being silently skipped when `rg` is unavailable.
## Changes
- Install `ripgrep` in the Fabro Daytona image and bump the snapshot ref
to `fabro-v12` so Daytona rebuilds it.
- Configure repository-local Git `user.name` and `user.email` from
`run.git.author` during workflow initialization before lifecycle setup
commands or workflow stages run.
- Make the implement-plan verify stage fail explicitly if `rg` is
missing.
## Validation
- `cargo test -p fabro-workflow
configure_sandbox_git_identity_uses_run_author --quiet`
- `cargo +nightly-2026-04-14 fmt --check --all`
- `cargo +nightly-2026-04-14 clippy -p fabro-workflow --all-targets --
-D warnings`
- `cargo run -p fabro-cli -- validate
.fabro/workflows/implement-plan/workflow.fabro`
---
[](https://github.com/EveryInc/compound-engineering-plugin)
🤖 Generated with GPT-5 via [Codex](https://openai.com/codex)
## Summary
- Render repeated stage visits as `verify@2` in the sidebar (and
waterfall/header/artifacts) to match Fabro's stage-reference syntax
instead of the parenthesized `verify (2)` form.
- Persist runs workspace preferences across sessions.
- Add multi-select with bulk archive/unarchive on the runs list view.
- Show provider logos on settings/models and integration logos on
settings/integrations, with a slightly wider logo-to-text gap.
## Test plan
- [ ] `cd apps/fabro-web && bun test` passes.
- [ ] Sidebar shows `verify`, `verify@2`, `verify@3` for a looped node
on a run with multiple visits.
- [ ] Runs list: select multiple runs and bulk archive/unarchive.
- [ ] Workspace preference on the runs page persists after reload.
- [ ] Settings → Models and Settings → Integrations render
provider/integration logos with the new spacing.
Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bumps the flex gap from 12px to 16px so the logo chip and the name/help block
breathe a little more.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Vendor brand icons for GitHub, Slack, Microsoft Teams, Discord, Linear, and
Jira under apps/fabro-web/public/images/integrations/ and render each one in
the same light chip used on the providers page. Refactors the existing rows
into IntegrationRow so name + help sit alongside the logo.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Vendor the 9 SVGs from models.dev under apps/fabro-web/public/images/providers/
and render each one in a light chip alongside the provider name and model count.
LiteLLM has no logo on models.dev; the onError handler falls back to an initial
in the same chip style.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a leading checkbox column with a tri-state select-all header and
a fixed bottom action toolbar that surfaces selection count plus
Archive and Unarchive buttons. Selection clears when pagination,
sort, or filters change. Bulk actions fan out to the existing single-
run endpoints via Promise.allSettled and report per-run outcomes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extend the RunsSort enum and sort_runs() with case-insensitive ordering
for repo, title, and workflow names, and total line changes (additions
+ deletions) for changes. Swap the corresponding `<th>` cells in the
runs list view to `<SortHeader>` so every column can toggle asc/desc.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
## Summary
Two related changes land together: the sandbox configuration surface is
replaced with a named-environment model, and `InterviewOption` gains
`description` and `preview` fields needed for the mid-stage agent
interview tools described in the plan.
## What changed
### Named environments (was `[run.sandbox]`)
`[run.sandbox]` and its provider-specific sub-tables
(`[run.sandbox.daytona]`, `[run.sandbox.docker]`) are replaced by a
two-level model:
- **`[environments.<slug>]`** — reusable catalog entries with a unified
shape: `provider`, `image`, `resources`, `network`, `lifecycle`,
`labels`, `volumes`, `env`.
- **`[run.environment] id = "<slug>"`** — selects which environment a
run uses.
- **`[run.environment.<field>]`** — sparse run-level overrides applied
on top of the selected environment.
The OpenAPI schema drops `RunSandboxSettings`, `DaytonaSettings`,
`DaytonaSnapshotSettings`, `DaytonaNetworkLayer`, and `DockerSettings`
in favour of `EnvironmentSettings`, `RunEnvironmentSettings`, and the
new sub-schemas (`EnvironmentImageSettings`,
`EnvironmentResourcesSettings`, `EnvironmentNetworkSettings`,
`EnvironmentLifecycleSettings`, `EnvironmentVolumeSettings`). The
`--sandbox` CLI flag becomes `--environment`.
All docs, example configs, `.fabro/project.toml`, and the
automation-detail / run-settings UI panels are updated to the new shape.
The run-settings page renames "Sandbox" → "Environment" and reads from
the new field paths.
### `InterviewOption` metadata fields
`description` and `preview` are added to the canonical `InterviewOption`
type (OpenAPI, helpers.ts, interview-dock, human-qa renderer). Both are
treated as untrusted model-authored text — stored and displayed as plain
strings, never rendered as HTML. The `interview-dock` test asserts that
raw HTML in `preview` is not rendered. Option `description` is shown as
secondary text under the label in choice and multi-select buttons.
### `StageModelUsage` projection
`provider_used` on `RunStageInfo` and stage projections is promoted from
a freeform object to a typed `StageModelUsage` schema (with `mode`,
`provider`, `model`, `reasoning_effort`, `speed`). The
`extractStageModel` event-scraping helper is replaced by
`formatStageModelUsageLabel` and `stageModelUsageTitle`, which work
directly from the projection field. The `Stage` interface gains
`providerUsed` and the `EventsToolbar` consumes it.
### Other schema additions
`ReasoningEffort` enum, `small_default` on model info,
`SubAgentProjection`/`SkillsProjection`/`McpServerProjection` inline in
stage projections, and `TodoListProjection` moved from the run-state
top-level `todos_by_list` map into per-stage `todos`.
### Plan summary
- Replace `[run.sandbox]` config with `[environments.<slug>]` +
`[run.environment]` selection across config, OpenAPI, UI, and docs.
- Extend `InterviewOption` with `description` and `preview`; render
`description` in choice/multi-select buttons.
- Promote `provider_used` to a typed `StageModelUsage` schema; drop
event-scraping in favour of the projection field.
- Add `ReasoningEffort`, `small_default`, subagent/skills/MCP
stage-projection schemas to OpenAPI.
### Fabro Details
<details>
<summary>Ran 9 stages in 93m 2s for $48.56</summary>
| Stage | Duration | Cost | Retries |
|---|---|---|---|
| start | 0s | – | 0 |
| toolchain | 1s | – | 0 |
| preflight_compile | 2m 4s | – | 0 |
| preflight_lint | 2m 16s | – | 0 |
| implement | 39m 28s | $35.95 | 0 |
| simplify_opus | 22m 6s | $8.66 | 0 |
| simplify_gpt | 7m 18s | $1.66 | 0 |
| verify | 6m 33s | – | 0 |
| fixup | 12m 34s | $2.29 | 0 |
| **Total** | **93m 2s** | **$48.56** | **0** |
</details>
<details>
<summary>Ran <code>ImplementPlan.fabro</code> (11 nodes and 14
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="git fetch origin main 2>&1 && git merge --no-edit --no-stat origin/main 2>&1 && cargo +nightly-2026-04-14 fmt --all 2>&1 && cargo dev docs refresh 2>&1 && cargo +nightly-2026-04-14 fmt --check --all 2>&1 && ! rg -n 'AuthMode::Disabled|RunAuthMethod|RunSubjectProvenance|\bActorRef\b|\bActorKind\b|AuthenticatedSubject|AuthenticatedService|AuthorizeRunScoped|AuthorizeRunBlob|AuthorizeStageArtifact|AuthorizeCommandLog|auth_method\s*==\s*\"disabled\"' lib/crates apps lib/packages docs/public/api-reference/fabro-api.yaml 2>&1 && cargo +nightly-2026-04-14 clippy --workspace --all-targets -- -D warnings 2>&1 && cargo nextest run --workspace --status-level slow --profile ci 2>&1 && cargo dev docs check 2>&1 && bun install --frozen-lockfile 2>&1 && (cd apps/fabro-web && bun run typecheck) 2>&1 && (cd apps/fabro-web && bun run test) 2>&1 && (cd lib/packages/fabro-api-client && bun run typecheck) 2>&1 && cargo dev build -- -p fabro-cli --release 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 format, clippy, Rust test, docs, TypeScript typecheck/test, and build failures.", max_visits=3]
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 -> exit [condition="outcome=succeeded"]
verify -> fixup
fixup -> verify
}
```
</details>
⚒️ Generated with [Fabro](https://fabro.sh)
---------
Co-authored-by: Fabro <noreply@fabro.sh>
Co-authored-by: Fabro <fabro@fabro.sh>
Co-authored-by: Bryan Helmkamp <bryan@brynary.com>
## Summary
Adds a stable `Run.size` API field so clients can bucket workflow runs
by current best-effort billed usage without introducing a separate
cost-estimation system. The field uses the `RunSize` enum and serializes
as uppercase `XS`, `S`, `M`, `L`, or `XL`.
## Changes
- Derives run size from terminal billed totals when available, otherwise
from the existing projected stage usage while a run is still active.
- Exposes `size` on `Run` in the OpenAPI contract and regenerated
TypeScript client.
- Preserves existing `Run.billing` behavior so live/provisional usage
only affects `size`, not the nullable billing summary.
## Verification
- `cargo nextest run -p fabro-types run_size`
- `cargo nextest run -p fabro-store
summary_size_tracks_current_projected_usage_before_terminal_conclusion`
- `cargo nextest run -p fabro-api
run_summary_json_matches_openapi_shape`
- `cargo +nightly-2026-04-14 fmt --check --all`
- `cd apps/fabro-web && bun run typecheck`
- `git diff --check`
- `cargo build --workspace`
---
[](https://github.com/EveryInc/compound-engineering-plugin)
🤖 Generated with GPT-5 via [Codex](https://openai.com/codex)
## Summary
Overhauls the `/runs` list view and consolidates the two runs endpoints
that backed it.
**API**
- Removes `GET /api/v1/boards/runs`, `PaginatedBoardRunList`, and
`BoardColumnDefinition`. The board view is now a pure frontend
rendering.
- `GET /api/v1/runs` gains `status` (repeatable `BoardColumn`), `sort`
(`created_at | updated_at | status | elapsed`, default `created_at`),
and `direction` (`asc | desc`, default `desc`).
- `BoardColumn` enum gains `removing`; default behavior hides
Removing-status runs, opt in with `?status=removing`.
- `PaginationMeta` gains an optional `total: int64`; `list_runs` fills
it in (free — it already filters all runs in memory before paging).
**List view UI**
- Renders as a real `<table>` with column headings instead of horizontal
cards.
- Sortable Status, Elapsed, Created, and Updated headers — click to
toggle direction, click another to switch sort key (resets to desc). URL
params drive `sort`/`direction`/`page`/`size`.
- New pager footer with rows-per-page selector (10/25/50/100), `Page X
of Y`, and first/prev/next/last icon buttons.
- Toolbar redesigned into left (search + filter buttons for
Time/Repo/Workflow + archived toggle) and right (column picker + view
toggle) sections. Filter buttons use Headless UI `Menu` popovers; the
column picker uses Headless UI `Listbox` with `multiple` for
multi-select. Hidden columns persist via `?hide=...`.
**Tests**
- 589 server tests pass, including new coverage for status filter
(single + repeated), Removing opt-in, sort × direction with `id desc`
tiebreak, and status-bucket sorting.
- Frontend tests updated for the matcher-based cache invalidation and
the new `buildBoardColumns` signature; 435 pass (3 pre-existing
`RunDetail full-height` failures unrelated to this change).
## Test plan
- [ ] `cargo build --workspace`
- [ ] `cargo nextest run -p fabro-server`
- [ ] `cd lib/packages/fabro-api-client && bun run generate` — no diff
(already regenerated and committed)
- [ ] `cd apps/fabro-web && bun run typecheck && bun test`
- [ ] Manual: visit `/runs` — board view still renders all columns in
canonical order, Removing runs hidden, archived toggle works.
- [ ] Manual: visit `/runs?view=list` — table renders with sortable
headers; clicking a header updates URL; pager advances; changing
rows-per-page resets to page 1; column picker hides/shows columns and
round-trips via `?hide=`.
- [ ] Manual: `curl '/api/v1/boards/runs'` → 404; `curl
'/api/v1/runs?status=removing'` returns only removing runs; `curl
'/api/v1/runs?sort=status&direction=asc'` returns runs grouped by status
bucket.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
## Summary
Introduces a `small_default` catalog role for identifying each
provider's small/cheap utility model, and uses that model to
asynchronously generate human-readable run titles when the caller
doesn't supply one explicitly.
### Plan Summary
- **Catalog**: Add `small_default: Option<bool>` to
`ModelCatalogSettings` and `small_default: bool` to the `Model` type.
Mark built-in small defaults: `claude-haiku-4-5` (Anthropic),
`gpt-5.4-mini` (OpenAI), `gemini-3.1-flash-lite-preview` (Gemini).
Validate that each provider has at most one small default; zero is
allowed with fallback to the provider's regular default.
- **Helpers**: Add `small_default_for_provider` and
`small_default_for_configured_ids` on `Catalog`, mirroring the existing
`default_for_provider` / `default_for_configured_ids` /
`probe_for_provider` pattern.
- **Title generation**: New `run_title_generation` module in
`fabro-server` builds a prompt from workflow identity, goal, and raw run
inputs, calls `generate_object` with `max_tokens(64)` and a 10 s
timeout, normalizes output (trim, reject blank/control, truncate to 100
chars), and falls back to the deterministic title on any failure.
- **Server integration**: In the create-run handler, if no explicit
`RunManifest.title` was supplied and at least one LLM provider is ready,
spawn a detached task that generates a title and appends
`run.title.updated` — but only if the title hasn't been changed by a
concurrent user PATCH.
## What changed and why
**`small_default` vs `default`** — the existing `default` role drives
normal model selection for workflow execution and must not be disturbed.
`small_default` is a separate, additive role for lightweight metadata
work. The two roles are intentionally independent so teams can promote a
newer large model to `default` without accidentally routing title
generation there.
**Best-effort, async title enrichment** — run creation is kept
synchronous and reliable. The title task is fire-and-forget: LLM errors,
timeouts, and validation failures all silently leave the deterministic
title in place. The stale-title guard (`current.title !=
deterministic_title`) prevents the async task from clobbering a
concurrent user edit via `PATCH /runs/{id}`.
**No redaction** — per the design goal, raw input values are forwarded
to the model. This is noted explicitly in the prompt and in the module
docs.
**Prompt size bounding** — each of the three prompt sections (workflow
identity, run inputs, workflow summary) is independently capped at 4 000
characters with a `...[truncated]` marker so pathological inputs can't
produce enormous requests.
## Public interface changes
- `Model` gains `small_default: bool` in the Rust type, OpenAPI schema,
and generated TypeScript client.
- `MAX_RUN_TITLE_CHARS` is now `pub` in `fabro-types` so the
title-generation module can reuse the same limit.
- Config docs (`models.mdx`, `litellm.mdx`) document `small_default =
true` alongside `default` and `probe`.
### Fabro Details
<details>
<summary>Ran 9 stages in 61m 23s for $32.17</summary>
| Stage | Duration | Cost | Retries |
|---|---|---|---|
| start | 0s | – | 0 |
| toolchain | 2s | – | 0 |
| preflight_compile | 1m 52s | – | 0 |
| preflight_lint | 2m 5s | – | 0 |
| implement | 29m 16s | $23.26 | 0 |
| simplify_opus | 17m 15s | $6.28 | 0 |
| simplify_gpt | 7m 1s | $2.63 | 0 |
| verify | 3m 7s | – | 0 |
| fmt | 3s | – | 0 |
| **Total** | **61m 23s** | **$32.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-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>