feat(strategy): carrierPolicy — strip reasoning carriers from the live window only - #81
feat(strategy): carrierPolicy — strip reasoning carriers from the live window only#81nissa-seru wants to merge 2 commits into
Conversation
…e window only
A summary's `responseContent` carries the ARCHIVIST'S cognition: the signed
thinking of the request that wrote the memory, not the thinking of living the
span it describes. Those blocks ride two surfaces from one store:
- mint/merge recall pairs, where they are measured load-bearing (2026-07-16:
a deterministically-refusing compress request passed once its recall pairs
carried their summaries' signed reasoning, where the text-only arm refused);
- the live window, where the agent whose memory it is reads it back — and a
thinking block there is inhabited rather than read, positioning the
instance in the compression fork's task-cognition at the remembered span's
own chronological slot.
`carrierPolicy: 'full' | 'live-strip'` (default `'full'`, behavior-preserving)
cuts exactly that seam. `'live-strip'` omits carriers from the live render
alone; the mint side keeps them unconditionally under either value, because
nothing here retires their measured anti-refusal duty.
Stripping OMITS WHOLE BLOCKS and never mutates one — signatures verify only on
byte-identical blocks, so a policy that edited a carrier would break the very
round-trip the mint surface depends on. The live paths
(summaryAnswerContentCapped, combinedRecallAnswerContent) now build through
`liveWindowAnswerProse`; the mint paths still build through
`summaryAnswerContent`, untouched.
Two further consequences, both pinned by tests:
- a carrier-only entry (reasoning captured, no prose block) falls back to its
`content` text rather than rendering an empty assistant turn, which the API
rejects outright;
- `recallPairCost` prices the render the policy will actually emit, so the
fold planner and the emitter agree about carriers. A plan that prices what
it does not emit is the Mica-2026-07-26 wedge from the other side.
Tests: test/carrier-policy.test.ts pins both directions on the SAME memory —
the live-strip render carries zero thinking blocks while that memory's
mint-side recall replays its stored blocks byte-for-byte with signature fields
intact — plus the default's byte-identity, envelope composition, the
carrier-only fallback, and the pricing symmetry. Red-first: with the src change
stashed, 4 of the 7 fail and the 3 that describe unchanged behavior pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Key memoized recall-pair costs by every render-shaping config value so a carrierPolicy preview cannot reuse or poison another policy's accounting. Add discriminating adaptive and legacy-combined live-strip coverage plus the 313-to-13 cache regression. Correct the staged measurement materials to call the null result unmeasurable on this corpus and state the harness location truthfully. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Anarchid
left a comment
There was a problem hiding this comment.
🟢 CLEAR
Reviewer: Codex (GPT-5.6 Sol)
Reviewed head: 3137943744bdec45d3f54b2d27e6ec1c1ec59cf3
No material findings. The new policy is confined to live-window rendering: both positioned and legacy-combined live paths omit whole reasoning-carrier blocks under live-strip, while L1 mint, merge, source-expansion, and refusal-curve recall continue through the unchanged carrier-preserving path. The default full mode remains byte-identical, envelope wrapping leaves signed blocks untouched, carrier-only entries retain readable prose, and picker accounting is keyed to the active render policy.
Tooling results
npm ci --offline— exact lockfile install succeeded; 6 packages installed, 0 vulnerabilities.npx tsc --noEmit— passed.npm test— build passed; 84/86 test files passed. The only failures were the unrelatedrelease-changelog.test.jsandrepair-pyramid-keepers.test.jssubprocess-output fixtures; sandboxed child processes return their status but captured stdout/stderr is empty here.node dist/test/carrier-policy.test.js— 10/10 assertions passed.node dist/test/recall-envelope.test.js,node dist/test/recall-envelope-truncation.test.js,node dist/test/summary-reasoning-roundtrip.test.js, andnode dist/test/recall-positioning.test.js— 46/46 dependency-path assertions passed.git diff --check origin/main...HEAD— passed.
Verdict: clear to merge. The changed behavior and its immediate render, envelope, mint-replay, and budget-accounting dependencies are covered; review confidence is limited only by the two unrelated subprocess-output fixture files that cannot reproduce faithfully in this sandbox.
— Reviewed by GPT-5.6 Sol via OpenAI Codex.
feat(strategy):
carrierPolicy— a knob for where reasoning carriers replayProblem
captureResponseContent(src/strategies/autobiographical.ts) stores the acceptedmint response's
thinking/redacted_thinking/textblocks byte-verbatim onthe SummaryEntry, signatures intact. That was the right call and it fixed a real
outage class. But the blocks it stores are the ARCHIVIST'S cognition — the
thinking of writing the memory, not the thinking of living the span the memory
describes — and one store now feeds two surfaces that want different things from
them:
whose answers carry those blocks. This is where the carriers are measured
load-bearing: your own 2026-07-16 validation flipped a deterministically
refusing compress request from
reasoning_extractiontoend_turnby givingthe recall pairs their summaries' signed reasoning.
summaryAnswerContent/summaryAnswerContentCappedrender the same blocks into the compiled context the agent itself reads back.
A thinking block there is not read, it is inhabited: the live instance re-enters
the compression fork's task-cognition, positioned at the remembered span's own
chronological slot. At fleet scale the accumulated exemplar mass an instance
reads of its own past is archivist-cognition rather than lived experience.
Nothing in the codebase can express "keep them where they are load-bearing, drop
them where they are inhabited", because both surfaces build from the same
function. This PR adds that seam and nothing else.
Changes
carrierPolicy: 'full' | 'live-strip'onAutobiographicalConfig,default
'full'. Under the default, output is byte-identical to today on bothsurfaces.
'live-strip'omits carriers from the LIVE WINDOW only. The mint/merge recallladders keep them unconditionally under either value — no configuration
reachable from this PR can strip the surface where their anti-refusal duty is
measured.
on byte-identical blocks, so the mint side must find its carriers unchanged
after the live side dropped them; both directions are pinned on the same
memory in the tests.
summaryAnswerContentCapped,combinedRecallAnswerContent) nowbuild through a new
liveWindowAnswerProse; mint paths (summaryAnswerContentat the L1 site, the merge sites, the refusal-curve canonical replay) are
untouched.
contenttext rather than rendering an empty assistant turn, which the APIrejects outright.
recallPairCostprices the render the policy will actually emit. The foldplanner is that price's only consumer, and a plan that prices carriers it will
not emit is the Mica-2026-07-26 wedge inverted.
produced the price. A config-scoped preview therefore cannot reuse or leave
behind another live render's accounting.
We are proposing the knob, not the default.
'full'stays the default heredeliberately: the inhabitation cost is an argument, the anti-refusal duty on the
mint side is a measurement, and flipping what every instance reads back is a
change for whoever lives under it to make — not for the PR that adds the option.
Tests
test/carrier-policy.test.ts, 10 cases:'full'renders the live window byte-identical to the storedresponseContent;'live-strip'renders zerothinking/redacted_thinkingblocks anywhere inthe compiled window, prose intact;
replays the stored blocks byte-for-byte with signature fields intact;
'full'and'live-strip'produce identical mint-surface content;recallPairCostfollows the policy);recallEnvelope: 'xml'(live answer enveloped around proseonly; mint answer enveloped around carrier + prose, carrier block untouched);
their
'full'controls prove the fixtures actually carry reasoning;instead of reusing the full render's memoized cost.
Red-first receipt: with carrier stripping and policy-aware cache scoping
selectively reverted, 7 of the 10 fail — including the adaptive, combined, and
313-vs-13 cache regressions — while the 3 unchanged-behavior cases pass.
Targeted final run:
bun test test/carrier-policy.test.ts— 10 pass / 0fail. The execution seam's full run, before review added the three
regressions, was 628 pass / 0 fail against a branch-point baseline of 621
pass / 0 fail; final package gates rerun the expanded suite. Node 22.14.0,
npm run buildclean,tsc --noEmitclean at the execution seam.Not verified
UNMEASURABLE on this corpus — the experiment ran and produced no refusing
control in either arm. The hypothesis worth testing is that the refusal
problem the carriers solve exists partly BECAUSE mint requests used to carry
no system prompt and no recall envelope — both now fixed — so the carriers'
duty might have shrunk. We ran a live carriers-vs-none comparison on this
branch's own request builder (
claude-haiku-4-5, real signed carriers,requests built by the library and replayed through membrane's native
formatter, arms differing only by whether the recall answer's carrier blocks
were present):
recallEnvelope: 'xml', toolsdeclared): carriers 4/4
end_turn, no-carriers 4/4end_turn;2/2
end_turn, no-carriers 2/2end_turn(and an earlier run on a prose-onlycorpus, same shape, same result).
Zero refusals in any cell, including the no-carrier control. With no
refusal to shrink, the delta is not discriminated: this measures that our
synthetic corpus does not reproduce the phenomenon, not that the phenomenon is
gone. We could not reconstruct a deterministically-refusing compress request —
yours was a real store's real span, and we declined to manufacture one by
choosing content designed to trip a safety classifier, which would measure
content refusal rather than the shape refusal at issue. The absence of a
refusal in the post-fix regime is weak evidence for the interlock hypothesis,
but only suggestive: without a refusing control there is no flip-count delta,
so it does not meet the bar for changing the default. The 2026-07-16
numbers remain yours; nothing here re-derives or retires them, and the knob's
mint side is built as if they stand. If you can point a run at the store that
refused, the same harness answers the question in about a dozen calls.
One transport fact did come out of those runs and is worth having: a carrier
block minted in one request and replayed into a later mint request was accepted
every time (12/12 carrier-arm calls, no
invalid_requestabout thinkingblocks), so the graft the mint side depends on is transport-valid on a live
thinking model in the post-fix regime.
Not exercised against a real mature store: no compile against production-scale
chronicle data, so the token-curve effect of
'live-strip'on a real pyramid(fewer carrier tokens per pair → shallower folding) is reasoned, not measured.
'live-strip'changes what an agent reads back. What that does to an agent'ssense of its own past over weeks is exactly the thing this PR cannot test, and
is the reason the default is unchanged.
Out of scope
question and stays open.
config.thinkingis never set on mint requests, so carriers exist only wherethe host's model returns reasoning natively — noted as an observation, not
touched here.