Skip to content

Raven x - #240

Open
TongLi31 wants to merge 31 commits into
EverMind-AI:mainfrom
TongLi31:raven-x
Open

Raven x#240
TongLi31 wants to merge 31 commits into
EverMind-AI:mainfrom
TongLi31:raven-x

Conversation

@TongLi31

@TongLi31 TongLi31 commented Jul 30, 2026

Copy link
Copy Markdown

Summary

Type

  • Fix
  • Feature
  • Docs
  • CI / tooling
  • Refactor
  • Other

Verification

  • Relevant tests pass locally
  • Relevant lint / type checks pass locally
  • User-facing docs or screenshots are updated when needed

Risk

  • Security impact considered
  • Backward compatibility considered
  • Rollback path is clear for risky changes

Related Issues

litong and others added 6 commits July 29, 2026 08:44
…ONPATH

Motivation: trajectory analysis of 177 raven failures on SWE-bench Verified
found the dominant framework-attributable failure (47/60 cases) was the agent
declaring completion after ad-hoc self-checks, because the bundled runtime's
PYTHONPATH leaked into exec child processes and broke the workspace project's
own test tooling (bundle packages shadowing a much older interpreter).

- raven/__main__.py: drop raven's own install dir from PYTHONPATH at startup
  so exec children never inherit bundle packages; sys.path of the raven
  process itself is already resolved and unaffected.
- raven/agent/loop/main.py: opt-in completion gate (env
  RAVEN_REQUIRE_REAL_TEST_EVIDENCE): a final text answer is only accepted
  after the session has run the project's own test runner at least once;
  bounded to 2 nudges so the gate itself can never loop.

Evaluated on a 136-task SWE-bench Verified subset: 67 resolved vs 56
baseline (net +11); the gate fired in 13 tasks with no runaway loops.

Co-authored-by: Claude (claude-fable-5) <noreply@anthropic.com>
Motivation: after wave-1 (environment isolation + completion gate), the
remaining SWE-bench Verified failures cluster into generalizable
methodology gaps, not benchmark specifics: over-wide edits that break
neighboring behavior (10 of 34 remaining target failures show
passed-before/failing-after tests), fixes that cover only the literal
example and miss sibling code paths (16 of 34), and correct fixes reverted
because an existing test encoded the old behavior.

Add a compact, domain-general discipline block to the identity segment so
every harness benefits (not only the SWE eval prompt): root-cause-first,
requirement text over stale tests, smallest complete change applied to all
sibling paths, verify with the project's own tooling, treat
passed-before/failing-after as a self-introduced regression, review the
full diff before declaring completion.

Co-authored-by: Claude (claude-fable-5) <noreply@anthropic.com>
…licts

A comprehension test by an independent agent (given only the block, no
context) rated the first version 6/10 and found two defects:
- the stale-test rule (Understand) and the regression rule (Verify) gave
  opposite default verdicts for the same observable event with no
  classification criterion; now the criterion is explicit (stale = asserts
  the exact old behavior the task asks to change; everything else is a
  self-introduced regression) and the two rules cross-reference each other.
- 'smallest change' conflicted with 'fix ALL occurrences'; now scoped as
  smallest change per fix site, applied to every site sharing the flaw.

Also restructured from flat compound bullets into Understand / Implement /
Verify / Before-declaring-done phases with short imperative rules, matching
the prompt style that stronger harnesses use.

Co-authored-by: Claude (claude-fable-5) <noreply@anthropic.com>
…l result persistence

Wave-4 completion-guardrail refinement, opt-in and layered on the wave-1
evidence gate (RAVEN_REQUIRE_REAL_TEST_EVIDENCE):

- Evidence ledger in the agent loop: parse each real-test run's output as
  red/green (conservative regexes; unparseable output never triggers
  anything) and track non-doc file edits made after it.
- RAVEN_GATE_STALE: when the model declares completion with untested edits,
  append ONE fact-stating reminder asking it to re-run or justify; doc-only
  edits exempt.
- RAVEN_GATE_RED: when the model declares completion right after a failing
  run, append ONE inquiry with an explicit exit (a test asserting the old
  behavior the task changes is stale and may remain failing) so a correct
  judgment is never forced into iteration.
- Persist oversized tool results as head 12k + tail 4k instead of head-only
  16k: test runners put the pass/fail summary at the end, so the old
  truncation cut exactly the evidence later turns need.

Both reminders fire at most once per turn, state only verifiable facts, and
leave the decision to the model.

Co-authored-by: Claude (claude-fable-5) <noreply@anthropic.com>
…ratch paths

Three detector fixes surfaced by the 50-task variant exploration (6 of 12
v1-gate fires were false positives, one inducing a 163-turn re-verify loop):

- Strip ANSI color sequences before matching test output: color codes glue
  onto adjacent digits (ESC[32m201 passed) and defeat word-boundary matching.
- Recognize sympy's native runner invoked as a module (runtests without .py,
  sympy.doctest) and its output banners (test process starts, tests finished).
- Do not mark evidence stale for edits under /tmp, /var/tmp, or /dev/shm:
  scratch files are not part of the deliverable (2 of 3 stale fires were /tmp
  reproducer scripts).
- Gate nudge now says NOT to install packages and points at bundled runners
  (4 sessions responded to the nudge with pip install pytest, violating task
  constraints and adding skip/fail noise from pytest-8-on-old-repo).

Co-authored-by: Claude (claude-fable-5) <noreply@anthropic.com>
…g self-written checks

The old wording (a check you wrote yourself is NOT verification) is a
dead-end instruction in projects that have no test suite. Reword as an
evidence ranking: project tests first; if none cover the change, write one
following project conventions and run it through a real runner; ad-hoc
self-written checks are the weakest evidence because they re-encode the
same assumptions as the change. The completion claim must state what
evidence backs it.
litong and others added 21 commits July 31, 2026 02:06
Root-level change description for the raven-x branch: the runtime
PYTHONPATH leak fix, the opt-in test-evidence guardrails (completion gate,
stale-evidence reminder, red-test inquiry with a semantic exit), the
engineering-discipline identity block, and the evaluation results backing
each change. Written for reviewers (human or agent) landing on this branch.

Co-authored-by: Claude (claude-fable-5) <noreply@anthropic.com>
Add a detached BackgroundJobRegistry (setsid, log file, survives agent
exit) next to PTY exec sessions, and route background=true exec calls
to it unconditionally, using a provided session name as the job name.
Session-hosted background processes died with the agent and failed
verifier-time checks in TB eval (4 tasks lost their servers).

Also:
- anchor shutdown/reboot/mkfs deny patterns to command position and
  report the matched pattern in guard errors
- clamp over-limit exec timeouts (configurable tools.exec.maxTimeout)
  instead of rejecting them
- teach session errors the creation path; suggest the matching tool on
  invalid parameters (registry did-you-mean)
- register web_search only when an API key is configured
- spill truncated exec/session output to ~/.raven/tool-output so it can
  be recovered with grep/read_file instead of re-running
- log the final tool manifest (names + schema hash) for eval attribution

Co-authored-by: Claude (claude-fable-5) <noreply@anthropic.com>
Every failure path should carry its own next step (G-1 of the tools
improvement plan):

- repair unambiguous case/format-mangled tool names (Read_File,
  execRead) and execute the intended tool with a note, instead of
  failing the turn
- stop appending the generic change-approach suffix to validation
  errors and tool-authored errors; it buried the targeted guidance
  (did-you-mean, session creation path). Timeouts and unexpected
  exceptions keep it, as they have no better text
- file tools name the recovery tool in not-found errors (list_dir /
  find to locate, write_file to create)

Co-authored-by: Claude (claude-fable-5) <noreply@anthropic.com>
…kpoints

Three long-turn robustness fixes (G-6 of the tools improvement plan):

- refuse a byte-identical (tool, arguments) call after it has hard-failed
  3 times running, instead of executing a fourth identical failure.
  Scoped to all-failed streaks only: identical successful repeats are
  legitimate polling (13 such streaks observed across passing eval
  tasks) and never trip it
- repair dangling tool_call/result pairs in loaded history (synthetic
  aborted results, orphan results dropped) so a crash cannot poison
  later provider calls
- persist the turn-so-far every 10 iterations. A turn is the unit of
  session persistence and benchmark tasks are one turn of hundreds of
  iterations; a crash mid-turn used to lose the whole trajectory

Co-authored-by: Claude (claude-fable-5) <noreply@anthropic.com>
Add agents.defaults.profile (assistant | coding). The default keeps the
personal-assistant identity byte-identical; "coding" renders a
software-engineering identity following opencode's default prompt
structure and content (tone, conventions, no-comments code style,
verify-with-tests discipline, an explicit completion check, and an
opencode-style <env> block), with every tool-specific passage rewritten
for raven's tool surface: find/grep/read_file/edit_file/write_file
routing over shell equivalents, background jobs for servers and
over-ceiling work, sessions + exec_write/exec_read for interactive
programs, and the untrusted-content rule.

ContextBuilder._get_identity now delegates to the segment renderer so
the estimator can no longer drift from the request path.

Co-authored-by: Claude (claude-fable-5) <noreply@anthropic.com>
Attribution analysis of the tb21 full run (raven 39.3% vs terminus
44.9%), the P0/G fixes landed on this branch with their evidence, the
6-task re-verification results (pypi-server and configure-git-webserver
flipped green; kv-store-grpc / install-windows-3.11 now fail on task
logic instead of tool defects), and the deferred items (unified exec,
todo/verification discipline) with measured regression exposure.

Co-authored-by: Claude (claude-fable-5) <noreply@anthropic.com>
Combines the tools-improvement work (detached background jobs, deny
pattern anchoring, timeout clamping, actionable errors, same-call
breaker, mid-turn checkpoints, coding profile) with the raven-x
completion-gate work. The SE-discipline block is hoisted into a shared
constant rendered by BOTH identity profiles, so the eval harness's
profile=coding selection keeps the discipline it was written for.

Co-authored-by: Claude (claude-fable-5) <noreply@anthropic.com>
Resolves the registry boundary against main's ToolOutput/ToolResult
split: the unwrap keeps main's display-text channel while retaining the
tool-name-repair note and the no-generic-hint-on-tool-errors policy;
the loop's test-evidence gate now reads the unwrapped model text.

Co-authored-by: Claude (claude-fable-5) <noreply@anthropic.com>
feat(*): tools reliability fixes, coding profile, and main sync
vLLM 0.23.1 returns HTTP 200 with tool-call arguments that are not valid
JSON — truncated, unescaped quotes, bare control characters, fragments of
another tool-call format. Measured on a 500-task SWE-bench Verified run
with Qwen3.6-35B-A3B: 0.17%-0.25% of calls.

Raven-X repaired these with json_repair and then executed the result. That
is worse than crashing: json_repair "completes" JSON cut off by the output
limit into a syntactically valid object, so write_file wrote truncated
content and silently lost the tail of the file, while the model believed it
had succeeded and kept going. The run ends with a bad patch and no signal.

Refuse to execute instead, and treat the two failure modes differently
because their remedies are opposite:

- Truncation (bracket-depth scan, with finish_reason == "length" as a
  supplementary signal) doubles the output budget and retries up to 4
  times, capped at 32768, without consuming loop iterations. When the
  ladder is exhausted it still refuses, returning a tool result that says
  the call was cut off and asks for smaller edits.
- Malformed JSON resamples the same request up to 3 times and adds nothing
  to history in the meantime — recording a bad shape teaches the model the
  shape is acceptable.

Also unify the streaming and non-streaming paths on one parser. Streaming
used a bare json.loads and, on failure, injected a `_raw_arguments` key
that nothing in the tree consumes; it reached the tool through **params and
surfaced as "missing required field", pointing the model away from the real
cause. And fix an adjacent defect: chat_stream never fell back to
self.generation the way chat_with_retry does, so the streaming output limit
was the signature default of 4096 rather than the configured value.

Tests: 12 cases over the parser and pure helpers, 5 over the loop side
effects. Every bad sample is a real string captured from that 500-task run.
Red/green verified: with the guard removed, the truncated write_file does
write incomplete content. Full suite 4701 passed with 0 new failures (the
31 pre-existing failures in sandbox/channels/cli-theme tests reproduce
identically with these changes stashed).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Route 424 Failed Dependency to the server bucket (retryable + fallback):
OpenRouter surfaces upstream provider crashes as 424 storms; classifying
them invalid_request killed agent turns mid-task. Match by status code and
the 'failed dependency' phrase only - no bare '424' substring, which would
false-positive on token counts in payloads.

Honor Retry-After on rate limits (bounded to 60s, ignores hostile values).
Mirror reasoning_effort past litellm drop_params for gateway models, which
silently ran with reasoning off. Add llm_probe_timeout / probe config so a
dead backend costs one timeout instead of one per retry attempt.

Config schema also gains fields consumed by follow-up commits
(verify_before_complete, wrap_tool_outputs).

Co-authored-by: Claude (claude-fable-5) <noreply@anthropic.com>
Standalone provider speaking the DSV4 native template directly, bypassing
gateway chat-template drift; used for A/B-ing gateway serving fidelity.
Encoding module is vendored verbatim from the official DeepSeek-V4
reference (ruff ignores keep the upstream diff at zero). Wired behind
provider_name=deepseek_v4_raw; no default-path impact.

Co-authored-by: Claude (claude-fable-5) <noreply@anthropic.com>
…file

Fence only external-content tools (web/search/MCP) instead of every tool
result - local exec/grep output echoes agent-controlled state and fencing
it cost ~50 tokens per result; wrap_tool_outputs config selects the
policy. Fence under the executed tool's canonical name (registry alias
resolution exposed as canonical_name) so alias calls cannot dodge the
gate. Coding profile bootstraps only TOOLS.md into task workspaces -
assistant persona and memory scaffolding are noise in a project dir.

Co-authored-by: Claude (claude-fable-5) <noreply@anthropic.com>
Background jobs now track the whole process group, not the wrapper pid:
group-truth liveness (killpg probe), TERM-then-KILL group reaping on
cancel, pid-reuse guard via /proc starttime, and describe() that
distinguishes a dead wrapper from surviving children. Persisted jobs are
scoped per-workspace (hash subdir) so concurrent sessions cannot see or
kill each other's jobs; subagents opt out of adoption to keep their
isolation contract.

Sandbox: raise child oom_score_adj so runaway task processes are chosen
by the OOM killer before the agent; executor results carry a structured
timed_out flag instead of stderr string-sniffing; DirectExecutor can
inherit host env when embedding requires it. Deny-rule matching also
anchors on newline-separated commands.

Co-authored-by: Claude (claude-fable-5) <noreply@anthropic.com>
…ll recovery

Context overflow: two-layer defense. Classified overflows compress as
before; opaque provider 400s (OpenRouter passthrough carries no overflow
wording) trigger an emergency shrink when the prompt is near the window,
judged by max(chars//4 estimate, billed prompt+completion of the last
successful call) - the char heuristic alone underestimates token-dense
content (disassembly, C code at ~2 chars/token) by 2x and never crossed
the gate. Emergency shrink elides old reasoning_content/thinking_blocks
in addition to old tool results: reasoning-heavy models accumulate 200k+
tokens of past reasoning that tool elision barely dents.

Empty-response recovery gains a midstream-kill signature (large reasoning
payload but <=2 billed completion tokens marks a serving-side abort):
plain retry instead of prefilling, which replayed the truncated reasoning
and burned the prefill budget on a provider fault. Truncated-output turns
get an act-now nudge instead of prefill for the same reason.

verify-before-complete nudge (config-gated): before finalizing, re-read
the task and check every stated requirement; measured floor of 8 saved
attempts per 267 (3.0pp) on TB2.1 with zero induced timeouts. Verify
segment adds from-scratch guidance. Time-budget reminders wire elapsed/
remaining wall clock into the loop. Checkpoint indexing no longer drifts
when recovery injects synthetic messages.

Co-authored-by: Claude (claude-fable-5) <noreply@anthropic.com>
…rs, web errors

grep fallback declares when results are truncated instead of presenting
a partial list as complete. Filesystem edit snapshots are taken in LF
space for CRLF files so verification diffs do not false-negative. Shell
command-boundary regex anchors on newlines and sudo prefixes. web tool
errors return plain text instead of JSON blobs the model quotes back.
TOOLS.md documents read/write truncation and paging behavior; exec
description steers pkill toward exact matching.

Co-authored-by: Claude (claude-fable-5) <noreply@anthropic.com>
Co-authored-by: Claude (claude-fable-5) <noreply@anthropic.com>
Adds the checklist half of task management, kept separate from spawn:
todowrite tracks the agent's own multi-step plan, spawn delegates work to a
subagent. opencode and codex both ship these as two distinct tools.

Every call carries the whole list rather than patching items by id, so a
model that loses track of an id cannot silently edit the wrong row. A second
in_progress item warns in the returned text instead of failing the call:
rejecting it would burn a turn and drop the status update that came with it,
and weaker models trip that rule most often.

TodoStore is injected rather than held on the tool, matching the existing
registry pattern, so a subagent builds its own store and its private steps
stay out of the parent's list.

Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com>
The same instruction can help one model family and hurt another, so the
coding identity is no longer a single hard-coded string. The model id is
matched against an ordered family table, first hit wins, falling back to a
shared default - the mechanism opencode uses in session/system.ts.

default.txt is the previous hard-coded text byte for byte, so a model with no
prompt of its own behaves exactly as it did before this dispatch existed, and
a new prompt file stays the only variable when its family's numbers move. A
family may be listed before its file is written: load_template falls back
instead of raising, and returns the family actually served so a caller logs
the prompt in force rather than the one requested.

Sentinels are substituted rather than passed through str.format, because the
prompts contain literal braces in code snippets and skill paths.

anthropic.txt is the first family variant. Its content follows documented
Opus 4.8 behaviour but is not yet validated on a benchmark, so it should be
A/B tested rather than assumed to be an improvement.

Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com>
Three optimization waves land together: they touch the same files
(agent loop, identity renderer, file tools), so they cannot be split
into per-wave commits without interactive staging.

Identity and tool surface:
- One identity only. The personal-assistant profile is folded into the
  coding one, keeping every behavior that was not assistant-specific
  (platform policy, working discipline, ask_user guidance, skills path,
  and the full anti-injection wording). The `profile` and `bootstrap`
  config fields and their plumbing are gone.
- Remove the `message` tool. A coding agent has no second reply channel,
  and its description invited the model to use it as one. A reply is
  plain assistant text; delivery is unaffected.
- Segment 2 injects the repository's own rules files (AGENTS.md,
  CLAUDE.md, CONTEXT.md, size-capped per file), the same project-level
  set opencode reads. Raven writes none of them.

In-turn context compaction (raven/agent/loop/compaction.py):
- A long coding turn appends tool results for hundreds of iterations
  while the context is assembled once, so it grew until the model's real
  window overflowed and recovery cost all the evidence at once.
- Two tiers, triggered on real reported usage plus a local estimate of
  what was appended since that report: prune replaces older tool-result
  bodies with a placeholder; summarize replaces the transcript head with
  one LLM handoff brief while a recent tail stays verbatim. A
  context-overflow error takes the same path reactively, and the stream
  path now recovers instead of raising.
- Three consecutive summary failures disable the summary tier for the
  rest of the turn: a failed summary frees nothing, so the trigger would
  otherwise stay armed and bill another doomed request every iteration.
  The counter is a ContextVar because the AgentLoop is shared across
  concurrent turns.

Nothing of raven's own is written into the working directory:
- The workspace is the user's repository for a coding run, so runtime
  state moves to a per-workspace directory under raven's data dir
  (Curator archives, manifests and traces, skill-injection telemetry).
- Memory paths and the workspace skills directory are created on first
  write instead of at construction. Reads are unchanged: an absent file
  still yields an empty result.
- Only `raven onboard` seeds template files, and only under raven's own
  home. Session transcripts are the one remaining exception.

File tools:
- find expands brace globs instead of silently matching nothing, and its
  docs no longer teach the syntax it could not handle.
- write_file, edit_file, read_file and list_dir descriptions carry the
  operative discipline (prefer editing existing files, read before
  overwriting, do not assume a path exists, list recursively first).

Verification: full suite 4746 passed with the 31 pre-existing failures
unchanged, ruff clean, and a real run against a live endpoint in a clean
git repository confirms both compaction tiers fire, the task still
answers correctly after a summary, and the only untracked path left is
the session store.

Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com>
Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com>
… gateways

Agent runs through provider=custom billed every prompt token uncached
(measured on a TerminalBench batch: cached_tokens=0 on every call). Three
gaps compounded:

1. find_gateway resolves provider=custom to the generic gateway spec, whose
   supports_prompt_caching=False vetoed cache_control injection. A generic
   gateway cannot speak for its upstream vendor, so fall through to the model
   id's vendor spec instead of trusting the gateway's False.
2. LiteLLM's plain openai/ driver strips cache_control blocks before sending;
   its openrouter/ driver keeps them for caching vendors. Resolve custom-gateway
   models whose vendor caches to the openrouter/ prefix so the markers reach
   the wire.
3. Auto-injection marked only the system prompt and tools schema, leaving the
   growing conversation history uncached. Add two rolling tail breakpoints
   (system + tools + 2 tail = Anthropic's 4-breakpoint budget).

Verified end-to-end against OpenRouter anthropic/claude-opus-4.8: hit rate
90-99% from call 1; non-caching vendors via custom (vLLM, deepseek) still
resolve to openai/ and never receive cache_control.

Co-authored-by: Claude (claude-fable-5) <noreply@anthropic.com>
litong and others added 3 commits August 5, 2026 10:11
…ilience

Ten commits from the colleague's branch land on top of the coding-only
identity and the in-turn compaction work. Overlapping areas and how each
was resolved:

- Identity prompts: their per-model dispatch (prompts/coding/<family>.txt
  with sentinel substitution) is kept as the mechanism, and the clauses
  folded in from the retired assistant identity are ported into it.
  default.txt was otherwise byte-identical to the pre-merge inline text, so
  the port is that text plus the platform policy, the skills path, the
  repository-mapping bullet, the strengthened untrusted-content wording and
  the working-discipline block. A PLATFORM_POLICY sentinel carries the OS
  branch. The same four model-agnostic blocks are added to anthropic.txt,
  which would otherwise drop them for claude-family models.
- profile stays removed: the dispatch keys on the model, not on the profile,
  so the coding path is unconditional and identity_text takes (workspace,
  model).
- The injected repository rules files stay AGENTS.md / CLAUDE.md /
  CONTEXT.md. Their TOOLS.md-only bootstrap and the per-run template seeding
  into the working directory are dropped: nothing of raven's own is written
  into the user's repository, and the paging guidance TOOLS.md carried is
  already in the read_file description.
- Emergency shrink: their reasoning-stripping tier moves into the compaction
  module beside the tool-result prune, and both the reactive and the
  proactive path now run both tiers.
- todowrite joins the always-visible tool set: the identity prompt mandates
  it for multi-step work, so tool_search must not be able to hide it.

Two defects the merge exposed, each fixed with a mutation-checked test:

- The curator ran its slow path over an empty history. A zero history budget
  makes the fast-path comparison false (0 < 0), so every turn whose prompt
  leaves no room for history spent up to max_steps LLM calls planning over
  nothing. The longer identity prompt is what pushed the budget to zero.
- A proactive compaction disarmed the overflow heuristic. The trigger zeroes
  its observed-usage value to re-arm on fresh data, and the heuristic read
  that same variable as ground truth about content density, so an overflow
  arriving right after a compaction surfaced as a fatal error instead of
  being recovered. The two readings are separate variables now.

One incoming test changed its premise rather than its subject: the dense
overflow test reports 9.5k billed against a 10k window on every call, so
proactive compaction would consume the prunable tool results before the 400
arrives. It now runs with the proactive tier off, which is what it was
written to exercise.

Verification: full suite 4895 passed with 30 failures whose list is
byte-identical to the incoming branch's own baseline (zero new), ruff check
clean.

Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com>
The Curator selected the history slot by running a bounded internal LLM loop
with its own tool registry (search / set-relevance / archive / retrieve /
build-context / check-budget / read-memory / update-working-state) and
persisted a manifest, archives and per-turn traces to disk. That was built
for a long-lived assistant whose sessions outgrow any window. A coding
turn's transcript is bounded by one task and the loop now compacts in-turn,
so the LLM-driven selection bought nothing and cost a call plus disk writes
every turn -- and its fast/slow decision had an edge (a zero history budget
makes 0 < 0 false) that spent max_steps calls planning over an empty
transcript.

Both jobs it actually did survive in a 37-line deterministic builder:
projection to provider-safe keys starting at a user message (what its fast
path did, byte-for-byte), and bounding a long session inside
budget.available_history by dropping oldest-exchange-first (what its slow
path did, without the LLM). A non-positive budget deliberately does not
trim: no amount of dropping fits when the fixed overhead already fills the
window, and trimming to nothing would make a small window silently lose the
whole conversation.

Every ContextConfig field stays declared and documented as retired. The
model forbids extra keys, so deleting them would make an existing config
file -- or a benchmark harness that writes curatorModel /
fastPathThreshold -- fail to load instead of being ignored.

Found while verifying the removal, and fixed: ContextAssembler collected
the history slot from phase B only. The Curator declared needs_prefix=True
so it landed there; a builder that honestly declares needs_prefix=False had
its history silently dropped, which made every turn look like the first
one. Both phases are now collected, phase B still winning the slot --
needs_prefix must not decide whether a builder is heard.

Two context-fullness implementations become one. Both answered "how full is
the context", one through a real tokenizer including tool definitions, the
other through chars//4 excluding them (hence its 2x undercount on
token-dense content, and hence the billed-usage fallback added to work
around it). _context_fullness anchors on the billed figure, adds a local
estimate of what was appended since, and floors on a whole-prompt estimate;
the proactive trigger and the overflow heuristic now differ only in their
thresholds. The overflow heuristic no longer needs a never-zeroed copy of
the billed figure either: having compacted in this turn is recorded as a
fact, which is stronger evidence than any estimate.

The todowrite checklist is re-rendered onto every request that has one. It
reached the model as a tool result, and the prune tier replaces older
tool-result bodies with a placeholder, so the plan being worked to would
silently vanish from a long turn. It rides on the outgoing copy only, so
nothing accumulates, nothing lands in the session transcript and there is
no stale copy to keep in sync. Both prompt files now explain that
<system-reminder> text is system-inserted, with a test that every prompt
family carries the explanation.

Verification: full suite 4898 passed with the 30 pre-existing failures
unchanged, ruff check clean, and a three-turn run against a stub confirms
history accumulates across turns.

Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants