Skip to content

feat(strategy): carrierPolicy — strip reasoning carriers from the live window only - #81

Open
nissa-seru wants to merge 2 commits into
anima-research:mainfrom
nissa-seru:fix/carrier-policy
Open

feat(strategy): carrierPolicy — strip reasoning carriers from the live window only#81
nissa-seru wants to merge 2 commits into
anima-research:mainfrom
nissa-seru:fix/carrier-policy

Conversation

@nissa-seru

Copy link
Copy Markdown
Contributor

feat(strategy): carrierPolicy — a knob for where reasoning carriers replay

Problem

captureResponseContent (src/strategies/autobiographical.ts) stores the accepted
mint response's thinking / redacted_thinking / text blocks byte-verbatim on
the 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:

  • Mint-request recall. Compression and merge requests replay recall pairs
    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_extraction to end_turn by giving
    the recall pairs their summaries' signed reasoning.
  • The live window. summaryAnswerContent / summaryAnswerContentCapped
    render 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' on AutobiographicalConfig,
    default 'full'. Under the default, output is byte-identical to today on both
    surfaces.
  • 'live-strip' omits carriers from the LIVE WINDOW only. The mint/merge recall
    ladders keep them unconditionally under either value — no configuration
    reachable from this PR can strip the surface where their anti-refusal duty is
    measured.
  • The strip omits whole blocks and never mutates one. Signatures verify only
    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.
  • Live paths (summaryAnswerContentCapped, combinedRecallAnswerContent) now
    build through a new liveWindowAnswerProse; mint paths (summaryAnswerContent
    at the L1 site, the merge sites, the refusal-curve canonical replay) are
    untouched.
  • 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. The fold
    planner is that price's only consumer, and a plan that prices carriers it will
    not emit is the Mica-2026-07-26 wedge inverted.
  • Pair-cost memoization is scoped to the policy, envelope, and label that
    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 here
deliberately: 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:

  • default 'full' renders the live window byte-identical to the stored
    responseContent;
  • 'live-strip' renders zero thinking / redacted_thinking blocks anywhere in
    the compiled window, prose intact;
  • the SAME memory's mint-side recall, captured off the transport in the same run,
    replays the stored blocks byte-for-byte with signature fields intact;
  • 'full' and 'live-strip' produce identical mint-surface content;
  • pricing symmetry (recallPairCost follows the policy);
  • the carrier-only fallback;
  • composition with recallEnvelope: 'xml' (live answer enveloped around prose
    only; mint answer enveloped around carrier + prose, carrier block untouched);
  • adaptive and legacy combined live-render paths both strip every carrier while
    their 'full' controls prove the fixtures actually carry reasoning;
  • a config-scoped policy swap reprices the same summary from 313 tokens to 13
    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.ts10 pass / 0
fail
. 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 build clean, tsc --noEmit clean 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):

    • post-fix regime (host system prompt riding, recallEnvelope: 'xml', tools
      declared): carriers 4/4 end_turn, no-carriers 4/4 end_turn;
    • pre-fix regime (no system prompt, no envelope, tools undeclared): carriers
      2/2 end_turn, no-carriers 2/2 end_turn (and an earlier run on a prose-only
      corpus, 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_request about thinking
    blocks), 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's
    sense 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

  • Flipping the default (see above).
  • The mint surface. Whether carriers are still needed there is charge-1's
    question and stays open.
  • config.thinking is never set on mint requests, so carriers exist only where
    the host's model returns reasoning natively — noted as an observation, not
    touched here.

nissa-seru and others added 2 commits August 29, 2026 14:38
…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 Anarchid left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 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 unrelated release-changelog.test.js and repair-pyramid-keepers.test.js subprocess-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, and node 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.

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