Changes that cut per-pass main-thread work on long sessions, and the fixes the review found. The branch keeps the review history as separate commits; the final behaviour is:
Native input copy (OpenCode 2), fix(v2) commits. The context hook copied the whole native history on every pass so that capture could record it for the idle host fold and the compaction hook. idleFoldCanArm(cacheTtl) (in v2/fold/idle.ts, shared with HostFoldIdle.idle) is false only for a lifetime that never expires. For such a session the copy is made only to make the capture current, and at most once for each model and set of effort controls:
NativeFoldReplay.captureCurrent is true while the last successful capture of the session in this process has the same model and effort controls. Each control is placed by the last host message id before it and the count of messages without an id after that one, not by the coordinates a capture binds on its copy. Such a pass skips the copy.
copyWanted / settleCopy: when a copy did not give a current capture (the copy threw, the capture did not run, for example because a checkpoint was served, or it failed), the same inputs are not copied again until the served head changes.
noteSkippedCapture: a skipped pass whose capture is not current, or whose served head (also when no head is recognised) or boundary changed, marks the capture unproven, so a host fold refuses it and uses the ordinary history path until a pass captures again.
Each pass logs v2 native input: copied|skipped reason=….
Idle host fold for subagents, fix(v2). A subagent has no history, so the fold's summary fell back to an empty history block (#838). The idle fold is armed only for a session whose stored mode is primary.
Signed-thinking fit snapshot, perf(transform) commits.takeSignedThinkingFitSnapshot gets the pass's context limit, the last measured input, and the estimated size of the messages that request did not include (the newest assistant message and everything after it). It skips the copy when the limit minus both is more than SIGNED_FIT_SNAPSHOT_HEADROOM_TOKENS (250,000). An unknown limit or input keeps the copy. A held pass is always inside an assistant tool loop. A held pass that overflows without a copy is refused, which is the existing path for a failed copy.
Entry digest cache, perf(lkg) commits.LkgEntryDigestCache measures the session's need first, then gives shares in increasing order of need: each session gets the smaller of its need, the per-session cap, and an even split of what is left. Candidates are held only up to the largest share a session can receive. The limits are 768 MiB total, 192 MiB per session and 64 sessions.
Why
On sessions of 12,000 to 17,000 messages, three paths were the largest magic-context main-thread costs: a full encode and revive of the history on every pass, a full structuredClone on every held signed-thinking pass, and the re-hashing of every message outside a 16 MiB even share of the digest cache. #834 has the before and after profiles and the step data behind the 250K margin.
Measured on the final version: each long session with a never-expiring lifetime copied its native input once after each plugin load and skipped on every later pass (for example 1 copy and 71 skips over 100 minutes); sampling profiles show 0 samples in the copy path.
Tests
New tests, each checked to fail without its change unless noted:
src/v2/hooks/native-copy-ttl.test.ts: a never-expiring session stops copying once its lifetime is stored; an expiring session copies on every pass; a new effort control or a model change copies again; an effort control after an instruction row without an id does not make every pass copy; a copy that throws is not tried on every pass; a session whose capture cannot become current copies once and then skips, with the log lines; a manual compaction after the capture is marked unproven still gets the session's history as its summary. src/v2/fold/capture-skip.test.ts covers captureCurrent, noteSkippedCapture, copyWanted and settleCopy, including a refused host fold and its recovery after the next capture, a retry after the served head changes, and a pass with no recognised head. src/v2/fold/idle.test.ts covers idleFoldCanArm. src/v2/hooks/fold-contention-review.test.ts now uses cache_ttl: "5m", because it reaches the capture path. The existing fold-review-regressions.test.ts (repeated instructions and effort markers across a native fold) passes; an intermediate version of this change failed it, which led to the bounded copy.
src/v2/hooks/event-loop.test.ts: a subagent, and a session whose mode is not stored, never arms the idle fold (Expected: 0, Received: 1 without the change); the existing primary-session test still arms.
src/hooks/magic-context/signed-thinking-fit-snapshot.test.ts: the copy is skipped far from the window and kept near it, with unknown usage, or with a large unmeasured tail; through the transform, a held pass far below a 1M window makes no copy.
src/hooks/magic-context/lkg-entry-cache-work.test.ts: a long session among many small ones keeps all it needs; a small session's unused budget goes to the long sessions; more than 16 sessions do not evict a long session; a session over the cap keeps correct digests and a leading share. The candidate bound only lowers transient memory, and no test observes that.
bun run typecheck and biome check on the changed files are clean. Suites run on 0.49.35 plus these changes at 36971f5ea (0.49.35 is the release the changes run on live). The last two commits, c53baad21 and e411e27bb, change only OpenCode 2 files under src/v2/. At e411e27bb they were checked with the nine test files that cover them (native-copy-ttl, capture-skip, fold-review-regressions, event-loop, idle, fold-contention-review, native-replay, native-replay-review, native-replay-v3): 76 pass, 1 fail. The failing test, v2 never arms host folding for a subagent or a session whose mode is not stored, fails the same way at c53baad21 when these nine files run in one process, and passes when its file runs alone:
packages/plugin fast suite, on 36971f5ea: 9,793 pass, 103 skip, 11 fail. 5 fail the same way without the changes in our checkout (a writer-admission test and 4 bundle tests that need built outputs). 6 are long tests that failed under load; their files pass when run alone, with and without the changes.
packages/pi-plugin suite, on 36971f5ea: 3,806 pass, 53 skip, 20 fail. 16 are in 3 files (pi-historian-crash, issue-686-image-budget, dreamer/pi-session-api) that fail the same way without the changes in our checkout. The other 4 (two tests, and two load errors between tests that also stopped 46 tests from running) are in 4 files that pass when run alone, with and without the changes.
Harness coverage (required)
OpenCode 1: covered — the snapshot and digest cache changes apply (shared transform and digest cache), tested by the packages/plugin suite; the native copy and idle fold changes are not applicable (OpenCode 2 only)
OpenCode 2: covered — packages/plugin suite, and run live on an OpenCode 2.2.5 service
Pi: covered — the digest cache change applies through noteEntry, tested by the packages/pi-plugin suite; the other changes are not applicable, as the surface does not exist there (pi-plugin/src/native-replay-pi.ts, which the advisory names, rewrites captured native items and has no per-pass copy or capture)
OMP: NOT covered — the digest cache change applies through the same Pi package code; the Pi suite includes OMP cases, but no OMP host was run
Rust module: not applicable — surface does not exist there
Confidence Score: 5/5
The PR appears safe to merge; no new actionable issues remain.
Summary
Cuts repeated history copying and hashing on long sessions.