You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
* feat(reasoning): add reasoning_replay knob (full/keep-last/none)
Bound reasoning accumulation in the forge->backend direction. Adds
core/reasoning.py policy module and threads reasoning_replay through
inference, runner, and proxy convert/handler paths. keep-last emits
reasoning via reasoning_content for round-trip re-capture and trims
older reasoning; none strips it; full preserves prior behavior. The
Anthropic path drops reasoning under keep-last (no signable channel).
Includes docs (README, BACKEND_SETUP, USER_GUIDE) and unit tests.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* eval: thread reasoning_replay through batch_eval + eval_runner
Make reasoning_replay a first-class, resumable, recorded eval axis so the
re-sweep can run none-on-all (regression) and keep-last/full on reasoning
models without collisions.
- EvalConfig gains reasoning_replay; run_scenario passes it to WorkflowRunner
(the backend pipeline already consumes it). run_eval propagates it.
- batch_eval: run-wide --reasoning-replay choice (mirrors --ablation),
threaded into run_batch, every EvalConfig, and the JSONL row.
- Centralize the 6 inline resume keys into _run_key(); reasoning_replay is
now part of the key so distinct policies for the same model+scenario are
independent runs. _count_completed_runs defaults pre-knob rows (no field)
to keep-last, so old dumps resume cleanly under the default.
- Both CLIs expose --reasoning-replay {full,keep-last,none}; banners print it.
- Add test_batch_eval_resume.py covering key distinctness, row recording,
and per-policy resume counting incl. the legacy-default fold.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* eval: add on-wire reasoning counter to validate replay knob
Add count_wire_reasoning() (eval-only, no src change): serialize the
recorded transcript through the real fold_and_serialize choke point and
count which reasoning blocks survive onto the backend wire. Emit
reasoning_wire (survived) and reasoning_wire_total (non-empty blocks)
per batch_eval row, so the sweep records an actual replay rate.
Validated on a reasoning model (N=10, all 3 policies, 26 scenarios):
none -> 0 on the wire across all 260 rows; keep-last in {0,1};
full in [0, total]. Surfaces that legacy/full replay is itself lossy
(~29% of generated reasoning reaches the wire) due to consecutive-block
collapse and empty-reasoning omission in fold_and_serialize.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(eval): add Anthropic prompt caching + cache-aware cost
Cache the re-sent tool defs + system prompt on the Anthropic eval path so
the repeated input prefix bills at 0.1x (read) instead of full price every
turn. Billing-only: identical model behavior, accuracy, and iteration counts
(safe for cross-run comparability).
- AnthropicClient gains opt-in `prompt_caching` (default off, so the proxy
verbatim path and existing request shape are untouched). When on, a static
ephemeral breakpoint marks the tools + system prefix in the rebuild path.
- Static-only on purpose: a rolling conversation breakpoint is NOT placed.
The default reasoning_replay="keep-last" re-serializes earlier tool-call
messages each turn, which busts a rolling prefix cache (1.25x writes, no
reads). The conversation prefix is only stable under none/full, and
reasoning_replay is a measured variable we won't pin, so caching is confined
to the always-stable tools+system region.
- TokenUsage carries cache_creation/cache_read counts (additive, defaults 0);
captured in send() and send_stream(); accumulated through CountingClientWrapper
and RunResult into the JSONL row.
- _compute_cost is cache-aware (write 1.25x, read 0.1x of input rate); applied
at the row and both eval_runner cost summaries.
- Enabled by default for batch_eval sweeps; eval_runner gains --no-anthropic-cache
for a cache-free cost-floor comparison.
- Bump claude-opus-4-6 -> claude-opus-4-8 (configs + pricing, $5/$25 verified).
Validated: 1148 unit tests pass (incl. new cache tests) + a live one-run smoke
on compaction_chain_baseline (20,523 cache reads, behavior unchanged).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Add v0.7.5 reasoning-replay eval results (rig-02 dual-GPU sweep, 67.6k runs)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Complete v0.7.5 reasoning-replay eval grid (78 cells, 101.4k runs)
Adds the remaining single-GPU sweep partition (33.8k runs, 26 config
cells) to the existing dual-GPU results, completing the full
reasoning_replay grid across all 14 models x {none,keep-last,full} x
{bare,reforged} x {native,prompt}. All 78 cells verified complete
(26 scenarios x 50 runs each), zero duplicate run-keys.
Rows stamped gen=3 (v0.6.0=1, v0.7.0=2) so cross-generation report
dedup keeps this suite over older generations of the same config.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Add Anthropic v0.7.5 eval rows
* Add GPU-A catch-up replay eval shard
* eval: merge reasoning replay catch-up results
Merge the catch-up reasoning-replay eval rows into the canonical v0.7.5 dataset and remove the temporary GPU-A shard. Add FORGE_EVAL_PORT so concurrent local eval workers can use separate llama-server ports.
* feat(reasoning): default reasoning_replay to none
The v0.7.5 eval grid showed dropping replayed reasoning is statistically
indistinguishable from replay-all on score while saving the replayed
tokens every turn, so the bounded policy becomes the default. Help
strings, the resume-fold docstring, and the anthropic prompt-caching
rationale updated to match; default-behavior tests now assert omission,
with fold/exposure mechanics re-pinned under explicit keep-last.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* data(eval): re-stamp Haiku v0.7.5 rows reasoning_replay keep-last -> none
The Haiku baseline ran before the default-policy decision and recorded
keep-last; Sonnet/Opus recorded none. The knob is request-inert for
Claude rows (no captured reasoning is replayed), so the field is a label,
not a behavioral difference - re-stamped for a consistent board. Targeted
byte-level edit of the 3,900 Haiku rows; all other lines byte-identical.
Post-edit validation: 170,300 rows, 0 bad JSON, 0 duplicate run keys.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(eval): reasoning_replay as a first-class report/dashboard dimension
ConfigKey (display identity) gains the policy so none/keep-last/full
render as separate rows, tagged :keep-last/:full (untagged = the none
default; pre-knob rows count as full - that is what they ran). The dedup
identity (_config_tuple) deliberately excludes it so latest-gen-wins
still supersedes pre-knob rows whole-config instead of keeping them as
stale :full duplicates. Adds the reasoning-replay.md policy-comparison
view, a --reasoning-replay report filter, a Reasoning Replay dashboard
filter dimension with canonical ordering, and the gen-3 legend entry
(tag ref v0.7.5; the squash SHA does not exist pre-merge). Reports and
dashboard regenerated from all four dataset files.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs: reasoning_replay knob + ADR-017 + model-registry updates
Document the knob and the new none default across README, User Guide,
and Backend Setup, with links to the eval evidence. ADR-017 records the
policy design, the grid results behind the default, and the alternatives
considered. Model Registry: Claude footnote updated for the v0.7.5
thinking-on re-baseline (Sonnet 4.6 / Opus 4.8; Opus 4.6 and the
deep-ablation rows stay carried forward), and Qwen3 8B Q8_0 is flagged
for future retirement on compute-cost vs signal-value grounds (~23% of
the full sweep for a small Q4/Q8 delta).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* release: v0.7.5 version bump + changelog
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: CHANGELOG.md
+13Lines changed: 13 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -2,6 +2,19 @@
2
2
3
3
All notable changes to forge are documented here.
4
4
5
+
## [0.7.5] — 2026-06-11
6
+
7
+
Reasoning replay is now a measured, bounded policy. Reasoning-capable backends return hidden reasoning alongside tool calls, and forge previously re-serialized all of it into backend-facing history on every later turn. The new `reasoning_replay` knob bounds that — and after a full re-sweep of the published eval grid showed that dropping replayed reasoning is quality-free and token-cheaper, the default is `none`. The release also re-baselines the Claude eval tier with extended thinking enabled and adds Anthropic prompt caching with cache-aware cost accounting.
8
+
9
+
### Added
10
+
-**`reasoning_replay {full, keep-last, none}`** on `WorkflowRunner(reasoning_replay=…)` and the proxy (`--reasoning-replay`). `full` replays every captured reasoning block (the historical behavior), `keep-last` only the most recent, `none` keeps reasoning out of backend-facing history entirely. Serialization-only: reasoning is still captured and still surfaces in `on_message` and internal history. In OpenAI-compatible proxy responses, `keep-last` exposes current reasoning as `reasoning_content` rather than assistant `content`, so clients that preserve reasoning fields can replay just the latest block. See [ADR-017](docs/decisions/017-reasoning-replay-policy.md).
11
+
-**Reasoning-replay eval grid** (`eval_results_v0.7.5.jsonl`, a new eval generation): the full 8–14B lineup re-swept across all three policies × both ablations × native/prompt — ~170k runs. The policy is part of the eval resume key and a first-class report/dashboard dimension: row labels carry `:keep-last` / `:full` tags (untagged = `none`), the dashboard gains a Reasoning Replay filter, the report a `--reasoning-replay` filter, and a dedicated [reasoning-replay view](docs/results/raw/reasoning-replay.md) compares policies per config. A wire-level counter (`reasoning_wire`) validates each policy's on-wire behavior (`none` → exactly 0 replayed reasoning across every run).
12
+
-**Anthropic extended thinking — `AnthropicClient(thinking=…)`** — request-side extended-thinking config (e.g. `{"type": "adaptive"}`). When set, a forced `tool_choice` is suppressed (the API requires `auto` with thinking on) and `max_tokens` is raised to fit the thinking budget. The Claude eval baseline now runs Sonnet and Opus with adaptive thinking — all prior Claude rows had thinking off, the wrong baseline for a reasoning-flavored suite; Haiku does not support adaptive thinking and stays non-thinking.
13
+
-**Anthropic prompt caching — `AnthropicClient(prompt_caching=True)`** — marks a static ephemeral cache breakpoint over the tool definitions + system prompt (byte-identical every turn, so it read-hits from turn 2 onward instead of re-billing the re-sent schema). `TokenUsage` gains generic `cache_creation_input_tokens` / `cache_read_input_tokens` counters, and eval cost accounting prices cache writes (1.25×) and reads (0.1×) at their actual rates.
14
+
15
+
### Changed
16
+
-**Captured reasoning is no longer replayed to the backend by default.** Pre-0.7.5 behavior replayed every captured reasoning block (equivalent to `reasoning_replay="full"`); the default is now `"none"`. On the published eval suite, `none` is statistically indistinguishable from replay-all in aggregate while saving the replayed tokens every turn; no per-config regression survives multiple-comparison correction (closest: a mild raw drop on Ministral-3 14B Reasoning Q4, where `none` and `keep-last` are indistinguishable from each other). The knob is inert for models that emit no reasoning. Migration: `--reasoning-replay full` (proxy) or `WorkflowRunner(reasoning_replay="full")` restores the historical behavior. Anthropic-protocol proxy responses emit reasoning text only under `full` — forge does not synthesize signed Anthropic thinking blocks.
17
+
5
18
## [0.7.4] — 2026-06-03
6
19
7
20
Malformed tool-call arguments now self-correct on the tool-error channel, and the eval suite gains its first model-size upgrade — a 32GB tier (Qwen3.5 / 3.6 27–35B, Nemotron-3 Nano, Mistral-Small-3.2) surfaced in the dashboard alongside the existing 8–14B lineup.
Copy file name to clipboardExpand all lines: README.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -128,7 +128,7 @@ For multi-step workflows, multi-turn conversations, and backend auto-management,
128
128
129
129
Drop-in proxy that sits between any client and a local model server, speaking both the OpenAI chat-completions API and the Anthropic Messages API (`/v1/messages`). Point your client at the proxy (e.g. `http://localhost:8081/v1`) and forge applies its guardrails transparently — the client thinks it's talking to a smarter model.
130
130
131
-
This is the path for **using forge with an existing harness** (opencode, Continue, aider, Cline, anything that speaks the OpenAI chat-completions schema — or Claude Code, which speaks the Anthropic Messages API). No Python rewrite.
131
+
This is the path for **using forge with an existing harness** (opencode, Continue, aider, Cline, anything that speaks the OpenAI chat-completions schema — or Claude Code, which speaks the Anthropic Messages API). No Python rewrite. Reasoning replay defaults to `none`: Forge still captures reasoning for observability, but keeps it out of backend-facing history on later turns — the most token-efficient policy, and statistically indistinguishable from replay-all on the eval suite (see [reasoning-replay results](docs/results/raw/reasoning-replay.md)). Use `--reasoning-replay keep-last` to replay only the latest reasoning block, or `--reasoning-replay full` for the historical replay-all behavior.
132
132
133
133
```bash
134
134
# External mode — you manage the backend, forge proxies it
`LlamafileClient` is **native-first**: `mode="native"` (the default) forwards tools via the backend's `tools` parameter and requires native function calling (llama.cpp with `--jinja`). For a backend without native FC, declare `mode="prompt"` to inject tool descriptions into the prompt and parse the JSON call back out. The capability is declared at construction and frozen — there is no runtime auto-detection. Native-first is the default because local-model FC support has matured into the more reliable path; prompt-injection stays fully supported as an explicit opt-in, but note that on more complex, multi-step interactions models tend to struggle to drive the prompt-injected protocol reliably, so reach for it only when the backend leaves no alternative.
77
77
78
-
> **Proxy note:** the OpenAI-compatible proxy is **native-first**. By default (`--backend-capability native`) it forwards the client's tools verbatim to an FC-capable backend (llama.cpp with `--jinja`, vLLM, Ollama, Anthropic) — the recommended setup. For a non-FC llama.cpp/llamafile backend, opt into prompt-injection with `--backend-capability prompt` (strips tools into the prompt, parses the JSON call back; reuses the same prompt path as the WorkflowRunner). The choice is frozen at startup — there is no runtime auto-detect in the proxy. See ADR-012.
78
+
> **Proxy note:** the OpenAI-compatible proxy is **native-first**. By default (`--backend-capability native`) it forwards the client's tools verbatim to an FC-capable backend (llama.cpp with `--jinja`, vLLM, Ollama, Anthropic) — the recommended setup. For a non-FC llama.cpp/llamafile backend, opt into prompt-injection with `--backend-capability prompt` (strips tools into the prompt, parses the JSON call back; reuses the same prompt path as the WorkflowRunner). The choice is frozen at startup — there is no runtime auto-detect in the proxy. Reasoning replay is controlled separately with `--reasoning-replay {full,keep-last,none}`; the default `none` keeps captured reasoning out of backend-facing history (`keep-last` replays only the latest captured reasoning block, `full` replays everything). See ADR-012.
Copy file name to clipboardExpand all lines: docs/MODEL_REGISTRY.md
+5-4Lines changed: 5 additions & 4 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -4,7 +4,7 @@ Every model forge knows about, classified by eval-suite status.
4
4
5
5
## Status meanings
6
6
7
-
-**Current** — in the published eval. The dashboard folds multiple eval *generations* into one view (the v0.7.0 8–14B lineup, plus the v0.7.4 32GB tier); runs not yet re-swept against the latest code — e.g. the Anthropic ablation — are carried forward and superscript-tagged. Numbers in [`docs/results/`](results/) and the [dashboard](results/dashboard.html).
7
+
-**Current** — in the published eval. The dashboard folds multiple eval *generations* into one view (the v0.7.5 reasoning-replay grid for the 8–14B lineup and Claude tier, plus the v0.7.4 32GB tier); runs not yet re-swept against the latest code — e.g. the 32GB tier and the Claude deep-ablation rows — are carried forward and superscript-tagged. Numbers in [`docs/results/`](results/) and the [dashboard](results/dashboard.html).
8
8
-**Retired** — appeared in a prior eval suite, cut from the current one. Either too weak (bare scores below the threshold for informative comparison) or superseded by a newer family member. Sampling defaults retained for backward compatibility.
9
9
-**Unpublished** — sampling defaults are present, but no eval numbers have been published. Forge will work with these models; performance is undocumented.
10
10
@@ -20,7 +20,7 @@ Sampling values are sourced from the model's HuggingFace card unless noted. Valu
¹ Ministral-3 Instruct cards say "temperature below 0.1 for production"; 0.05 picked within that range.
39
39
² Ministral-3 Reasoning cards show `top_p=0.95` in code examples but do NOT include it in the formal "Recommended Settings" section. Add explicitly if you want to follow the examples.
40
40
³ Granite 4.1 sampling mirrors the Granite 4.0 IBM convention (greedy decoding); marked unconfirmed pending IBM publication for the 4.1 family specifically.
41
41
⁴ Phi-4: no formal sampling recommendation from any official source (Microsoft HF card, model docs). Falls through to backend defaults.
42
-
⁵ **Claude numbers are carried forward from the v0.6.0 dataset** — gen 1 on the dashboard, superscript-tagged. The Anthropic ablation has not been re-run since, owing to cost (~$272 for the full 11,700-row matrix). Backend support is unchanged; numbers are stable to within tool-error-channel sensitivity (small).
42
+
⁵ **Claude baseline re-measured in the v0.7.5 dataset** with extended thinking enabled (adaptive) for Sonnet 4.6 and Opus 4.8; Haiku 4.5 does not support adaptive thinking and runs non-thinking. Earlier Claude rows ran thinking-off: Opus 4.6 and the Anthropic deep-ablation rows are carried forward from the v0.6.0 dataset (gen 1 on the dashboard, superscript-tagged) — the ablation has not been re-run owing to cost (~$272 for the full 11,700-row matrix).
43
43
⁶ Qwen3.6 27B (dense) deliberately diverges from its A3B siblings: its card drops the `presence_penalty=1.5` the MoE variants recommend, so forge sends `0.0` (no penalty).
44
44
⁷ Nemotron-3 Nano: the card splits sampling into a Reasoning preset (T=1.0, top_p=1.0) and a Tool-calling preset (T=0.6, top_p=0.95); the tool-calling preset is used here, with thinking enabled via `chat_template_kwargs`.
45
+
⁸ **Qwen3 8B Q8_0 will be cut (→ Retired) in a future eval generation** on compute-cost vs signal-value grounds, not quality: it was the single most expensive model in the v0.7.5 grid (~108 GPU-hours, ~23% of the full sweep) while adding little information over its Q4_K_M sibling (the Q4/Q8 delta is a couple of points on a mid-board model, and the quant-comparison axis is preserved by the cheaper Ministral and Gemma Q4/Q8 pairs). Its numbers stay Current while they are part of the published dataset.
Copy file name to clipboardExpand all lines: docs/USER_GUIDE.md
+4Lines changed: 4 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -85,6 +85,8 @@ claude
85
85
86
86
**Function-calling capability.**`--backend-capability native` (default) uses the backend's chat-template tool-calling and is the smoother default for Claude Code's heavy multi-turn tool use. `--backend-capability prompt` injects the tool surface into the prompt for llama.cpp/llamafile backends without a tool-calling template; whether a model stays coherent across multi-turn tool results in prompt mode varies by model — and tends to degrade on more complex, multi-step interactions — so prefer native whenever the backend supports it. The capability is declared at startup and frozen.
87
87
88
+
**Reasoning replay.** Reasoning-capable backends may return hidden reasoning alongside tool calls. Forge captures that reasoning for observability, then controls how much is replayed to the backend on later turns with `--reasoning-replay {full,keep-last,none}`. The default is `none`: captured reasoning stays out of backend-facing history entirely. This is the most token-efficient policy, and on forge's eval suite it is statistically indistinguishable from replay-all (no aggregate score cost; see [reasoning-replay results](results/raw/reasoning-replay.md)). `keep-last` replays only the latest captured reasoning block. `full` preserves the historical behavior and replays every captured reasoning block. In OpenAI-compatible proxy responses, `keep-last` exposes current reasoning as `reasoning_content` instead of normal assistant `content` so clients that preserve reasoning fields can replay only the latest block without turning it into plain text; under the default `none`, proxy responses omit captured reasoning. Anthropic proxy responses only emit reasoning text under `full`; Forge does not synthesize signed Anthropic thinking blocks, so default Anthropic proxy responses do not expose replayable reasoning. See [ADR-017](decisions/017-reasoning-replay-policy.md) for the policy design and the eval evidence behind the default.
89
+
88
90
**Downstream protocol.**
89
91
90
92
-**Local model (default, `--backend-protocol openai`)** — forge translates Claude Code's Anthropic requests to OpenAI for llama.cpp / Ollama and converts the reply back to Anthropic SSE. Anthropic-only fields with no OpenAI analog (`cache_control`, `thinking`, `document` blocks) are dropped at that boundary; see [ADR-015](decisions/015-cache-control-preservation-path1.md).
@@ -283,6 +285,8 @@ await server.stop()
283
285
284
286
`WorkflowRunner` accepts an optional `on_message` callback that fires each time a `Message` is appended to the conversation during `run()`. This is the primary observability hook — use it for logging, eval metric collection, or building conversation history for multi-turn flows.
285
287
288
+
`WorkflowRunner(reasoning_replay=...)` uses the same policy as the proxy: `none` by default (captured reasoning is not replayed to the backend), `keep-last` to replay only the latest reasoning block, and `full` for the historical replay-all behavior. The policy affects backend-facing serialization only; `MessageType.REASONING` entries still appear in `on_message` and internal history unless context compaction removes them.
289
+
286
290
-**Single-turn (default):**`on_message` fires for every message the runner creates — system prompt, user input, assistant responses, tool results, nudges.
287
291
-**Multi-turn (`initial_messages`):**`run()` accepts an optional `initial_messages` parameter that seeds the conversation with prior history. `on_message` fires **only for new messages created during this turn**, not for the replayed history.
0 commit comments