Commit graph

2334 commits

Author SHA1 Message Date
Bryan Helmkamp
c083fa5209
Merge pull request #658 from fabro-sh/feat/stage-model-popover-billing
Show stage tokens and cost in the model popover
2026-07-28 14:58:51 -04:00
Release Repro
acff084afd
refactor: simplify stage billing reuse 2026-07-28 14:51:47 -04:00
Bryan Helmkamp
ee89caf649
Merge pull request #647 from fabro-sh/fix/live-active-time-accumulation
fix(timing): accumulate active time for in-flight stages
2026-07-28 14:32:31 -04:00
Bryan Helmkamp
c5d3345144
Merge pull request #651 from fabro-sh/fix/offline-validation-catalog-free
fix(cli): keep offline validation catalog-free
2026-07-28 14:28:35 -04:00
Bryan Helmkamp
a10ffb02b5
Merge pull request #654 from fabro-sh/fix/reject-edge-only-nodes
Reject nodes referenced by an edge but never declared
2026-07-28 14:28:11 -04:00
Bryan Helmkamp
d6ac910e73
refactor: reuse ModelResolutionTransform instead of a parallel options type
`ModelResolutionOptions` was a field-for-field duplicate of the existing
public `ModelResolutionTransform`, down to a verbatim copy of its `new()`.
`pipeline::transform` then unpacked one to rebuild the other, cloning the
catalog Arc and the eligible-provider set on the way.

- Delete `ModelResolutionOptions`. `TransformOptions.model_resolution` now
  holds an `Option<ModelResolutionTransform>` directly, so the TRANSFORM
  step is `resolution.apply(graph)?` with no rebuild and no clones. This
  is consistent with `custom_transforms`, which already holds transforms.
- Add `ModelResolutionTransform::catalog()` so the VALIDATE step can reach
  the same catalog for its lint rules. That is the only new code needed.
- Drop `CatalogScope` from `operations::validate`, which was a third copy
  of the same fields. The three entry points now hand a partially built
  transform to `validate_resolving_models`, which completes it with the
  workflow's default provider once the workflow is resolved.
- Extract `validate_child_workflow` in `manager_loop`, collapsing two
  near-identical validate-and-unwrap blocks.
- Point the transform tests at their own `transform_options()` helper via
  struct-update syntax instead of respelling all seven fields, and drop a
  HashSet -> Vec -> HashSet round trip from the create test helper.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 14:24:26 -04:00
Bryan Helmkamp
96f70a9aea
fix(timing): simplify live timing projections 2026-07-28 14:20:59 -04:00
Bryan Helmkamp
04c931e9d3
Simplify edge-only node validation 2026-07-28 14:14:32 -04:00
Bryan Helmkamp
8592a34968
refactor: collapse the split validation paths
Follow-up cleanup on the catalog-free validation split. Same behavior,
fewer parallel code paths.

- Make the catalog an explicit `Option<&Catalog>` on `pipeline::validate`
  instead of a `validate` / `validate_with_catalog` pair, so each call
  site states whether catalog rules run.
- Collapse `preprocess_and_validate`, `preprocess_and_validate_structural`,
  and `preprocess` into one function that takes `TransformOptions`. Its
  `model_resolution` field is now the single source of truth for catalog
  awareness, which drops a 12-argument signature and the
  `too_many_arguments` allow.
- Replace the duplicated resolve-and-preprocess block in
  `operations::validate` with one `validate_in_scope` helper, and drop the
  HashSet -> Vec -> HashSet round trip on the catalog path.
- Extract `configured_default_provider`, previously duplicated between
  `operations::create` and `operations::validate`.
- Delete `validate_manifest_with_environment_defaults`, which had no
  callers outside its own module.
- Share the `server-model.fabro` fixture between the two CLI tests instead
  of inlining it twice. The validate test now asserts the rendered output
  through the usual snapshot helper, which also removes a hand-rolled
  `std::fs::write` and its clippy allow.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 14:13:45 -04:00
Bryan Helmkamp
465bdd6c27
Redirect test storage through with_storage_override
The test helper set server.storage.root directly, leaving the derived
local object-store roots (artifacts, slatedb) pointing at the real
~/.fabro/storage. Route the redirect through
ServerSettings::with_storage_override so every derived root moves to the
test directory together.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 13:50:16 -04:00
Bryan Helmkamp
9e744f8072
Keep server tests off the developer's real ~/.fabro/storage
Test settings usually omit `[server.storage] root`, so it resolved to the
production default. Handlers that walk that tree read whatever the machine
happened to have.

That is why all_spec_routes_are_routable was slow. Timing every request in
it showed 91% of the runtime in two routes:

  6304ms  GET /api/v1/system/resources
  4574ms  GET /api/v1/system/df
   583ms  POST /api/v1/system/prune/runs
  ...
  the remaining 134 operations: 8ms combined

Both size Fabro-managed storage. On this machine that meant 193MB and 90,795
entries under scratch/, so the test's duration tracked how long the developer
had been running Fabro locally. Run-creating tests were writing there too.

Redirect settings that still carry the production default to a `storage`
directory beside the test vault, alongside the existing `server.env` and
`settings.toml` siblings. A test that chose its own root keeps it.

all_spec_routes_are_routable drops from ~15s to 0.6s, and the full workspace
run from ~33s to ~21s. All 7402 tests pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-27 18:45:27 -04:00
Bryan Helmkamp
7841a77f2c
feat(web): show stage tokens and cost in the model popover
The model indicator on a stage page hovered to provider, model, and
reasoning effort only. Seeing what a stage actually spent meant leaving
for the Billing tab, which reports per node rather than per visit.

The stage list had no token data to show, so add a per-visit `billing`
block to `GET /runs/{id}/stages`. The Billing tab's pricing rule (a
provider-reported cost wins, otherwise the server catalog prices the
tokens) was private to `billing_rollup`; move it to
`StageProjection::billed_usage` and drive both call sites from it so the
two views cannot drift.

The popover's buckets use the Billing tab's labels verbatim. It stays
scoped to one visit, so a looped node's row on the Billing tab is the sum
of what each of its visits shows here.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-27 16:06:19 -04:00
Bryan Helmkamp
c501c67185
Show each diagnostic's suggested fix in CLI output
Diagnostics have carried a `fix` field all along, but the CLI renderer
never printed it — the suggestion was only reachable through --json. The
actionable half of every validation failure was invisible to the person
running the command.

print_diagnostics now emits the fix as a dim-labelled continuation line
under any diagnostic that has one, at both error and warning severity.
Gating it behind --verbose would defeat the point, and printing it only
for errors would read as "this warning has no fix" — the warning
suggestions are useful on their own. Diagnostics that set no fix simply
omit the line.

The severity match moved into print_diagnostic so the fix line is
appended once in the loop rather than copied into all five arms; the
rest of the diff is reindentation.

print_diagnostics is shared by validate, preflight, graph, exec, and
dry-run, so this covers all five. Eleven inline snapshots across four
files gain a fix line; every change is additive.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-27 14:06:31 -04:00
Bryan Helmkamp
59b1c2e59f
Reject nodes referenced by an edge but never declared
The DOT parser created a node for every edge endpoint, and nothing
recorded whether a node came from a declaration or was synthesized from
an edge. The edge_target_exists rule only checked whether the node id
was present in the graph, which was always true by then, so a misspelled
endpoint became an attribute-free node that defaulted to shape=box — an
LLM stage. Validation emitted a prompt_on_llm_nodes warning and exited 0.

Node now carries `implicit`, set only when the parser synthesizes the
node from an edge endpoint. A declaration anywhere in the workflow
clears it, so order does not matter and subgraph declarations count.
Node::new leaves it false, so programmatic construction and graphs
deserialized from older checkpoints read as declared.

edge_target_exists treats an endpoint as valid only when it exists and
is declared, reporting each undeclared node once. The near-identical
missing-source and missing-target branches collapse into one path. The
import transform copies the flag onto spliced nodes so an edge-only node
inside an imported fragment is caught too.

parse_and_validate_human_gate had two edge-only nodes and now declares
them; it was an instance of the bug rather than a casualty of the fix.
No shipped workflow, docs example, or CLI fixture relied on the old
behavior.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-27 13:53:54 -04:00
Bryan Helmkamp
0b24649e76
fix(cli): keep offline validation catalog-free 2026-07-26 09:25:47 -04:00
Bryan Helmkamp
dd9f75fb05
fix(timing): harden live active projections 2026-07-25 23:43:43 -04:00
Bryan Helmkamp
e88cabbb75
Merge pull request #645 from fabro-sh/feat/spa-build-version-detection
Tell open tabs when a new build ships
2026-07-25 15:19:36 -04:00
Release Repro
6d61c6b5e4
fix(agent): stop the Kimi leak assertion from passing vacuously
The check tested for `never reconstruct it from memory`, a phrase the
Kimi edit description no longer contains after it was reworded to match
Kimi Code. A one-sided `!contains` against a literal cannot tell "the
phrase is absent because nothing leaked" from "the phrase is absent
everywhere", so it silently stopped protecting anything.

Assert the marker is present in Kimi's own description and absent from
the stock one. Removing the marker from the description now fails the
test instead of quietly disarming it, verified by doing exactly that.

Also correct the grep docs: all three sandbox implementations probe for
`rg` and fall back to POSIX `grep`, so the page should not imply a
single engine. Pre-existing, adjacent to the lines this branch touched.

Reported by Copilot review on #646.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 15:13:41 -04:00
Release Repro
454f07d560
refactor(kimi): match Codex and Kimi Code on where read guidance lives
Neither harness puts read-before-edit mechanics in the system prompt.
Kimi Code's `system.md` has no such section; the rules live in
`edit.md`, `write.md`, and `read.md`. Codex's prompts say nothing about
reading before an edit at all, and its editing guidance is attached to
`apply_patch`.

Drop the `# Reading Before Writing` section from the Kimi prompt and
carry its content in the Edit, Write, and Read descriptions, worded as
Kimi Code words it. Nothing is lost: every bullet in the removed
section was already covered by a tool description.

Two behaviors change to match upstream. Edit now says not to issue
consecutive edits against the same file, since the first invalidates
the second's `old_string` -- Kimi Code's stated reason. Read now says
not to re-read solely to confirm a write landed, which both harnesses
call out as waste; the previous prompt asked for exactly that re-read.

The gpt56 profile already followed the Codex split and is unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 15:07:19 -04:00
Bryan Helmkamp
c4971b93d3
fix(timing): accumulate active time for in-flight stages
`active_time_ms` was only ever computed from terminal stage events, so a
stage still running contributed zero to the run rollup. A run parked in one
long agent stage reported 2m 8s of active time against 16m 53s of wall
clock — the two finished stages — while the running stage had been doing
continuous inference and tool work for over 14 minutes.

`live_run_timing` summed `filter_map(|stage| stage.timing)`, and
`stage.timing` is only written at finalization. Wall time ticked live off
`start_time`; active time did not tick at all.

Stage projections now accumulate brackets from the event log:

- Closing an inference bracket folds its span into `live_inference_ms`
  instead of discarding it, including across retries, matching the
  in-process stopwatch.
- Tool calls open a batch on the first outstanding call and close it when
  the last one drains, so tools running concurrently within a turn count
  once — the same span `execute_tool_calls` is bracketed by. Summing
  per-call durations would over-count parallel tool use. Subagent tool
  events are excluded; they run inside the root call's span already.
- `StageProjection::live_timing(now)` composes accumulators with any open
  bracket, per handler: agent stages use the brackets, prompt and command
  stages count elapsed time as inference and tool respectively, and
  handlers that wait on a human, timer, condition, or child branches
  report zero.

Active is clamped to wall per stage. A worker killed mid-turn leaves its
bracket open forever, and without the clamp it would tick up unbounded.
The clamp does not need to detect the dead worker: a stage cannot have been
active longer than it has existed. `watchdog.timeout` remains the authority
on whether a run is stuck. The clamp is deliberately not applied at run
level, where concurrent branches can legitimately sum past run wall time.

Timing is derived from events rather than emitted by the worker, so this
needs no event-schema change and applies to runs already stored.
`StageProjection.timing` keeps its terminal-only meaning, and the
authoritative breakdown still replaces the live estimate at terminal
events.

The billing endpoint had the same hole behind its `wall_only` fallback:
running stages reported zero inference/tool/active. Not visible in the
product, which renders only `wall_time_ms`, but wrong for any other
consumer of `GET /runs/{id}/billing`.

Parallel branch stages lose their breakdown permanently, even after
completion, because `parallel.branch.completed` carries only `duration_ms`.
That is a separate data-loss bug, tracked in #644.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 14:54:38 -04:00
Release Repro
a925275778
fix(agent): remove the read-before-write guard
`ReadBeforeWriteSandbox` blocked writes to any existing file the agent
had not read, tracked by a session read set populated only by
`read_file`, `grep`, `read_many_files`, and the Kimi `Read`.

The gpt56 profile has none of those. It mirrors Codex's tool contract --
`shell_command`, `apply_patch`/`edit_file`, `update_plan`, `web_search`
-- and reads through the shell, so its read set stayed permanently
empty and every edit to an existing file failed. In run
01KYD4360GN6SED4BYEVGYP4XT all 28 `edit_file` calls failed, 25 of them
on the guard. The agent read `package.json` with `sed` and `cat`,
hex-dumped it trying to diagnose the rejections, then routed around the
guard with `sed -i`, which the guard never covered. It prevented no
blind write; it converted content-anchored edits into an unreviewed
in-place shell rewrite.

Neither Codex nor Kimi Code enforces read-before-write at runtime.
Codex's `apply_patch` `Add File` overwrites an existing path silently;
Kimi Code's `Write` has no check at all. Both rely on the exact-match
requirement in their edit tools, which is stronger proof of inspection
than a read set, plus per-write approval.

Tool descriptions and the Kimi prompt keep telling the model to read
before editing -- that guidance matches Kimi Code's own `edit.md` and
still prevents `old_string not found` -- but no longer claim the
workspace refuses unread writes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 14:53:38 -04:00
Bryan Helmkamp
695a981f42
feat(web): tell open tabs when a new build ships
A tab left open across a deploy keeps running the previous build's
JavaScript indefinitely. index.html is fetched only on a full page load,
all later navigation is client-side, and hashed bundles are served
`immutable`, so nothing reveals that the code is stale. This produced a
false-positive bug report where two correctly-deployed fixes appeared to
be missing.

Publishes a build id and offers a reload when the running document falls
behind. The toast never reloads on its own; the only automatic reload is
recovery from a chunk that no longer exists.

Build id derivation
-------------------
The obvious approach — hash the emitted asset filenames, which already
embed content hashes — does not work: Bun's minified identifier naming is
not deterministic. Building an unchanged tree twice produces byte-different
output roughly one run in three (same length, ~100k differing bytes, all of
it mangled names). Output hashes therefore move with no source change,
which would fire the toast on redeploys of identical code and train people
to ignore it.

The id is instead derived from the bundle's source inputs, so it changes if
and only if something we control changed. Verified stable across eight
consecutive builds while the entry hash flipped between both variants.

This non-determinism also means two builds of the same commit embed
different bytes into the server binary, which is worth addressing
separately for reproducible builds.

Detection
---------
SWR with `refreshInterval` + `revalidateOnFocus`, per the repo's React
effects policy. SWR does not poll while the document is hidden, so
background tabs stay quiet without extra gating. Unknown state on either
side — missing meta tag, failed fetch, 503 during a dev rebuild — never
produces a prompt.

Stylesheet hashing
------------------
Tailwind's output was stable-named and therefore served `no-cache`, letting
a tab revalidate into new CSS while running old JS. Tailwind purges unused
classes per build, so classes the old bundle still emits could silently
lose their styles. It is now content-hashed and moves with the build.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 14:45:26 -04:00
Bryan Helmkamp
e2df6e68c7
Merge pull request #640 from fabro-sh/codex/workspace-glob-semantics
Unify workspace glob semantics across sandboxes and artifacts
2026-07-25 12:05:17 -04:00
Bryan Helmkamp
420267bb5e
Merge remote-tracking branch 'origin/main' into codex/workspace-glob-semantics
# Conflicts:
#	lib/components/fabro-sandbox/src/daytona/mod.rs
2026-07-25 11:59:57 -04:00
Bryan Helmkamp
dec67ec92e
fix(glob): harden artifact traversal 2026-07-25 11:56:57 -04:00
Bryan Helmkamp
5353ba8183
Merge pull request #641 from fabro-sh/feat/gpt56-agent-profile
feat(agent): add gpt56 profile for GPT-5.6 Sol, Terra, and Luna
2026-07-25 11:56:44 -04:00
Bryan Helmkamp
d931ae6105
fix(agent): simplify GPT-5.6 tool routing 2026-07-25 11:49:40 -04:00
Bryan Helmkamp
7f7d292466
Merge remote-tracking branch 'origin/main' into fix/daytona-session-bash-probe 2026-07-25 11:36:11 -04:00
Bryan Helmkamp
5dbe4691c4
fix(sandbox): make Daytona probe cleanup reliable 2026-07-25 11:34:45 -04:00
Bryan Helmkamp
c7ad387d3e
feat(agent): add gpt56 profile for GPT-5.6 Sol, Terra, and Luna
Codex drives the GPT-5.6 models with a much narrower tool set than the
other OpenAI models: a shell, `apply_patch`, and `update_plan`. It has no
file-read, file-write, grep, glob, or fetch tool at all -- reading and
searching go through the shell, and every write goes through
`apply_patch`. Offering 5.6 fabro's extra tools advertises affordances its
instructions never mention, so this adds a profile that registers only
what Codex does.

The profile is selected per model via `agent_profile = "gpt56"` on the six
5.6 rows (three each on `openai` and `openrouter`), following the existing
Kimi-over-a-gateway pattern. Every other model on those providers keeps
its provider default, with no code branch and no version sniffing.

- `ToolVocabulary::Codex` renames `shell` to `shell_command`; a strum
  alias keeps `from_any_name` resolving it to `NativeTool::Shell`, so
  permissions, categories, and telemetry still key on the canonical name.
- `shell_command` gains `workdir`, passed to the `cwd` argument
  `execute_shell_command` already accepted, with Codex's "always set
  `workdir`, do not `cd`" guidance.
- `prompts/gpt56.md.j2` is adapted from Codex's 5.6 `base_instructions`,
  which are byte-identical across Sol, Terra, and Luna. A header comment
  records provenance and the departures fabro's harness forces.

This is an alignment-only pass: it matches Codex's tool contract while
keeping direct tool calls. Codex actually drives 5.6 in code mode, with a
single `exec` tool taking JavaScript and every other tool reached through
a `tools` object inside a V8 isolate. That is deliberately out of scope.

Luna's `multi_agent_version: v1` (vs v2 on Sol and Terra) is also out of
scope. It only changes the sub-agent tool set, which fabro registers from
the caller rather than the profile, and fabro's current set matches
neither version exactly.

Two server cancel-timing tests are adjusted. `gpt-5.6-sol` is the
`openai` provider's default model, so runs that name no model now build a
3-tool profile instead of an 8-tool one and reach their first stage
sooner. `full_http_lifecycle_cancel` asserted `status.kind == "blocked"`
at the instant of cancel, which the worker is free to change the moment it
is signaled; it now accepts either live state, matching the tolerance its
own comment already documents for `pending_control`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 10:42:32 -04:00
Bryan Helmkamp
5980627bc8
refactor(glob): unify workspace path matching 2026-07-25 10:30:19 -04:00
Release Repro
61956673c3
fix(workflow): drop token accounting from agent-facing preamble
The stage-summary preamble rendered per-stage token usage for every
completed LLM stage: "Model: kimi-k3, 92.6k tokens in / 41.1k out" at
compact fidelity and "Tokens: N in / N out" at summary:high. Agents read
that as their own remaining budget.

In run 01KYCM3EG4KMCVRDYNV93PZWBV an implementation stage stopped after 2
of 9 units, reasoning "We have around 100k tokens, but time constraints
are an issue" and recording the rest as halted "within the available
execution window". The 92.6k it saw was the preceding plan stage's
billing telemetry, the only token quantity anywhere in its context. It
had used 11% of a 1,050,000-token window and 0.8% of a 24h stage timeout,
and no harness limit was near.

These counts have no task value to the agent: they describe a different
model's usage on an earlier stage, they are stale by one stage, and
nothing in the preamble distinguishes them from a budget. Keep the model
id and files touched, which carry provenance the agent can act on.

Both tests that asserted the counts now assert their absence, so the
regression is caught rather than re-snapshotted.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 09:51:14 -04:00
Bryan Helmkamp
932c07aa32
test(sandbox): probe the Daytona session transport at init
Extend the Daytona Bash probe to cover the streaming toolbox-session transport in addition to the direct process exec. The two build different requests, so passing one is not evidence for the other: the `exec` regression fixed in #636 left every streaming command stalling until its timeout while the lifecycle probe reported a healthy sandbox. The session probe reuses the streaming path's own command construction and completion wait, so a transport that suppresses Daytona's exit-code bookkeeping fails at the lifecycle boundary with a remediation that names the wrapper-shell contract.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 09:32:13 -04:00
Bryan Helmkamp
0c4db2686d
fix(sandbox): preserve Daytona streaming completion
Run the streaming Bash wrapper as a child of Daytona's session shell so the provider can resume its bookkeeping and persist the command exit code. Add a regression test that exercises the sourced-command contract and preserves a nonzero exit status.
2026-07-25 06:58:06 -04:00
Bryan Helmkamp
30b5d74495
Merge pull request #633 from fabro-sh/feat/sandbox-bash-contract
feat(sandbox): standardize command execution on non-login Bash
2026-07-24 23:06:34 -04:00
Bryan Helmkamp
462858725d
docs(sandbox): clarify Bash probe rationale 2026-07-24 23:02:33 -04:00
Bryan Helmkamp
50a6cd3637
fix(sandbox): canonicalize cached Bash path 2026-07-24 23:01:32 -04:00
Bryan Helmkamp
c81ea69c73
Merge origin/main into feat/inference-observability 2026-07-24 22:55:15 -04:00
Bryan Helmkamp
6261c3b0fc
fix(sandbox): prepare local workspace on resume 2026-07-24 22:55:09 -04:00
Bryan Helmkamp
7f436bf64c
fix(agent): avoid nested Bash for sandbox MCP scripts 2026-07-24 22:53:11 -04:00
Bryan Helmkamp
d4f619bc2a
fix: clean up inference observability 2026-07-24 22:50:00 -04:00
Bryan Helmkamp
2f84b67558
Merge latest origin/main into feat/sandbox-bash-contract 2026-07-24 22:45:35 -04:00
Bryan Helmkamp
29e408aa18
Merge origin/main into feat/sandbox-bash-contract 2026-07-24 22:42:28 -04:00
Bryan Helmkamp
1d939ca3eb
Merge pull request #632 from fabro-sh/fix/shell-process-outcome-reporting
fix(agent): report real shell process outcomes
2026-07-24 22:42:00 -04:00
Bryan Helmkamp
4d5458b64c
test(agent): honor Docker shell integration preconditions 2026-07-24 22:35:51 -04:00
Bryan Helmkamp
4666f51d98
feat(model): add Claude Opus 5 to OpenRouter 2026-07-24 22:34:02 -04:00
Bryan Helmkamp
f25d7ddfdd
fix(sandbox): clear Bash startup environment 2026-07-24 22:30:01 -04:00
Bryan Helmkamp
9083b1b035
Merge remote-tracking branch 'origin/main' into fix/shell-process-outcome-reporting
# Conflicts:
#	lib/components/fabro-agent/src/tools.rs
2026-07-24 22:29:06 -04:00
Bryan Helmkamp
c914fbbbe0
Merge pull request #630 from fabro-sh/fix/compaction-reasoning-token-budget
fix(agent): budget compaction summaries for reasoning models
2026-07-24 22:26:12 -04:00
Release Repro
bf62450a28
fix(agent): align summary prompt with output cap
Interpolate the visible summary allowance after applying the model max_output cap, so low-output models are not asked to produce more text than the request permits.
2026-07-24 22:21:52 -04:00