Skip to content

(MOT-4150) fix(harness,context-manager,shell): context-overflow accounting, resume budget, and loud child failures - #552

Merged
andersonleal merged 6 commits into
mainfrom
andersonleal/context-overflow-transient-resume-fixes
Jul 21, 2026
Merged

(MOT-4150) fix(harness,context-manager,shell): context-overflow accounting, resume budget, and loud child failures#552
andersonleal merged 6 commits into
mainfrom
andersonleal/context-overflow-transient-resume-fixes

Conversation

@andersonleal

@andersonleal andersonleal commented Jul 21, 2026

Copy link
Copy Markdown
Collaborator

Four live incidents from parallel-scan subagent runs on 2026-07-21, each traced to a distinct harness-stack defect. All four are fixed here; each carries a regression test that fails without its fix.

1. Phantom token accounting (context-manager/src/core/estimate.rs)

The chars/4 estimator counted the full serialized FunctionResult including details, but no provider wire adapter ever sends details (content text/images only; the denied envelope is the exception and stays counted). File reads carry the whole file in BOTH content and details, so tool-heavy sessions were double-billed and lost ~half their usable window.

Incident: session scan-x7k2-c11-l2 — 22 file reads, ~88k real wire request, estimated 148,602 > 148,000 usable in a 200k-window model, terminal harness.context_overflow.

2. Futile re-assembly (harness/src/turn_loop.rs)

The one-shot re-assembly folded final − usable into the reservation, but post-assembly additions (the fp::inject-guidance prompt append is ~2.5k tokens vs the 256-token allowance) can dwarf that overshoot: assembly stayed under its own ceiling, rebuilt the identical request, and the turn died twice on the same count. The fold now measures final − assembled.token_count — everything the request grew beyond what assembly believed (reassembly_deficit()).

3. Transient resume budget of 1 (harness/src/config.rs, harness/src/types/turn.rs)

Anthropic 529s cluster in bursts and only mid-stream errors burn harness resumes (pre-stream errors already get llm-router backoff retries). Budget 1 killed a turn one generation step from done when a second overload landed after the first resume.

Incident: session dcmcp-scan-p6w4-c-aq — both findings INSERTed (rows 292/293), the final bookkeeping calls never ran. Default is now 3; config and per-turn serde defaults stay in lockstep.

4a. Empty-context compaction (context-manager/src/functions/assemble.rs)

select() legally returns a whole-head selection for a view with no user turn (a harness candidate window opened at a prior compaction's assistant-boundary tail_start) or tail_turns == 0, and try_compact only guarded the head. Compaction then summarized the entire working set and assemble returned an empty model-facing context — providers hard-reject that, and the harness fails the turn terminally. The guard skips compaction whenever it would consume the whole working set; emergency reduction (replaces-but-never-removes) shrinks instead. Guard lives in assemble, not select(), because context::compact legitimately summarizes whole histories.

Incident: session scan-batch-4-7x2k — "context::assemble returned an empty model-facing context" after a second compaction on a user-less window. New BDD scenario fails 84/85 without the guard.

4b. Structurally silent child failures (harness/src/deferred.rs, harness/src/turn_loop.rs, harness/src/subagent.rs)

A fire-and-forget spawn settles its call Done at spawn time and the parent turn has usually completed, so resolve_parent(failed) no-oped at both resolve() gates — and a dead child can never write the state keys its spawner watches, while turn-completed events reach only registered subscriptions. Child death was silent by construction.

Incident: session console-42ae032b — two spawned scanners died instantly; the coordinator waited forever on success-path state triggers.

resolve_parent now reports delivery; when unconsumed, finalize_failed sends an idempotent [child-failure] message to the spawner session (model/provider inherited; loop-safe — send-created turns carry no ParentLink; react-spawned children unaffected). The spawn result text documents the automatic failure delivery.

Also

shell::fs::read docs now lead with "returns a ContentRef handle, NOT file text — use coder::read-file for text": the tail-position hint caused a haiku scanner to double-read all 22 files (incident 1's trigger).

Tests

  • context-manager: 103 (lib + BDD), incl. 3 new
  • harness: 234, incl. 2 new
  • shell: 1308
  • clippy + fmt clean on all three

Deploy note

These crates ship as long-running workers — rebuild + restart context-manager, harness, and shell for the fixes to take effect.

Summary by CodeRabbit

  • Bug Fixes

    • Prevented context compaction from producing empty message windows.
    • Improved token budgeting when context expands during processing.
    • Function results now estimate token usage more accurately.
    • Failed child tasks now notify their parent session instead of waiting silently.
  • Improvements

    • Increased automatic recovery attempts for transient provider or routing failures from 1 to 3.
    • Clarified that file reads return a content handle, with expanded response and error details.
    • Updated child-task guidance to explain automatic failure notifications.

Fixes MOT-4150

The chars/4 estimator counted the full serialized FunctionResult message,
but no provider wire adapter ever sends `details` (content text/images
only; the denied envelope is the one exception and stays counted). Fat
tool results — a file read carries the whole file in BOTH content and
details — were double-billed, halving the effective usable window.

Observed live (session scan-x7k2-c11-l2): 22 file reads inflated an ~88k
wire request to 148602 estimated > 148000 usable and the turn died
terminally in a 200k-window model.
…embly

select() legally returns a whole-head selection (tail_turns 0, or a view
with no user turn — e.g. a harness candidate window opened at a prior
compaction's assistant-boundary tail_start), and try_compact only guarded
against an empty head. Compaction then summarized everything, split_off
emptied the messages, and assemble returned an empty model-facing context
— which providers hard-reject and the harness turns into a terminal
context_overflow.

Skip compaction whenever it would consume the whole working set; emergency
reduction (replaces-but-never-removes) does the shrinking instead. The
guard lives in assemble, not select(), because context::compact
legitimately summarizes whole histories.

Observed live (session scan-batch-4-7x2k): second compaction on a
user-less window killed the turn with "context::assemble returned an
empty model-facing context". BDD scenario fails without the guard.
Two turn-loop gaps observed live on 2026-07-21, one commit because the
changes share turn_loop.rs hunks:

Re-assembly deficit vs believed size: the one-shot re-assembly folded
final - usable into the reservation, but post-assembly additions (the
fp::inject-guidance prompt append is ~2.5k tokens vs the 256 allowance)
can dwarf that overshoot. Assembly stayed under its own ceiling, rebuilt
the identical request, and the turn died twice on the same count
(148602 > 148000, session scan-x7k2-c11-l2). Measure the deficit against
assembled.token_count — everything the request grew beyond what assembly
believed — via reassembly_deficit().

Loud child failures: a fire-and-forget spawn settles its call Done at
spawn time and the parent turn has usually completed, so
resolve_parent(failed) no-oped at both resolve() gates and a dead child
was structurally silent — it can never write the state keys its spawner
watches, and turn-completed events reach only registered subscriptions
(session console-42ae032b: two dead scanners, coordinator waited
forever). resolve_parent now reports delivery; when unconsumed,
finalize_failed sends an idempotent [child-failure] message to the
spawner session (model/provider inherited, loop-safe: send-created turns
carry no ParentLink). The spawn result text documents the automatic
failure delivery.
Provider overloads (Anthropic 529s) cluster in bursts, and only
mid-stream errors burn this budget — pre-stream errors already get
llm-router backoff retries. A budget of 1 killed a turn one generation
step from done when a second overload landed after the first resume
(session dcmcp-scan-p6w4-c-aq: both findings INSERTed, bookkeeping never
ran). Config and per-turn serde defaults stay in lockstep.
shell::fs::read returns a ContentRef handle, not file text, but the
coder::read-file hint sat at the docstring tail where weak models miss
it — a haiku scanner read 22 files through it, got useless stubs, and
re-read everything via coder::read-file, doubling round-trips on the way
to a context-overflow death. State the handle-not-text nature and the
text-file redirect in the first two sentences.
@vercel

vercel Bot commented Jul 21, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
workers Ready Ready Preview, Comment Jul 21, 2026 6:56pm
workers-tech-spec Ready Ready Preview, Comment Jul 21, 2026 6:56pm

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

skill-check — worker

0 verified, 47 skipped (no docs/).

Layer Result
structure
vale
ai
render

Four for four. Nicely done.

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR adjusts context token accounting and compaction safeguards, improves child-failure propagation and reassembly recovery, raises transient resume defaults, and clarifies the shell file-read response description.

Changes

Context management

Layer / File(s) Summary
Function result token estimation
context-manager/src/core/estimate.rs
Non-denied function result details are excluded from heuristic estimates, while denied details remain counted; tests cover both cases.
Whole-window compaction safeguard
context-manager/src/functions/assemble.rs, context-manager/tests/features/assemble.feature
Compaction is skipped when its selected head covers all working messages, with regression coverage for user-less windows.

Harness reliability

Layer / File(s) Summary
Reassembly deficit calculation
harness/src/turn_loop.rs
Retry reservation uses post-assembly token growth, with tests for positive and saturating deficits.
Child failure propagation
harness/src/deferred.rs, harness/src/turn_loop.rs, harness/src/subagent.rs
Parent resolution reports delivery status; undelivered child failures generate parent-session notices and updated hint text.
Transient resume defaults
harness/src/config.rs, harness/src/types/turn.rs
The default transient resume budget increases from one to three, with updated tests and comments.

Shell API documentation

Layer / File(s) Summary
Shell read response description
shell/src/main.rs
The shell file-read description identifies the response as a ContentRef handle rather than file text.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ChildTurn
  participant resolve_parent
  participant notify_parent_of_child_failure
  participant ParentSession
  ChildTurn->>resolve_parent: finalize failed turn
  resolve_parent-->>ChildTurn: resolved false
  ChildTurn->>notify_parent_of_child_failure: child failure details
  notify_parent_of_child_failure->>ParentSession: send [child-failure] notice
Loading

Possibly related PRs

  • iii-hq/workers#495: Both changes modify context assembly budgeting and compaction behavior.

Suggested reviewers: guibeira

Poem

I thumped through tokens, neat and bright,
Kept empty windows out of sight.
Three retries now hop in line,
A failed child sends a warning sign.
Handles hold files, not text to chew—
These changes bloom in rabbit view.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately reflects the main harness, context-manager, and shell changes in the pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch andersonleal/context-overflow-transient-resume-fixes

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@andersonleal andersonleal changed the title fix(harness,context-manager,shell): context-overflow accounting, resume budget, and loud child failures (MOT-4150) fix(harness,context-manager,shell): context-overflow accounting, resume budget, and loud child failures Jul 21, 2026
@andersonleal
andersonleal merged commit da59575 into main Jul 21, 2026
46 of 48 checks passed
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.

1 participant