Skip to content

fix(autobiographical): a dry compile leaves what a select leaves behind as it found it - #161

Merged
nissa-seru merged 2 commits into
anima-research:mainfrom
nissa-seru:fix/dry-run-commits-nothing
Oct 11, 2026
Merged

nissa-seru merged 2 commits into
anima-research:mainfrom
nissa-seru:fix/dry-run-commits-nothing

Conversation

@nissa-seru

@nissa-seru nissa-seru commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

Problem

SelectOptions.dryRun promises a compile that commits nothing, "no transition bookkeeping" included. Only previewContext kept that promise, by saving and restoring the strategy's state itself. A plain compile(…, { dryRun: true }) on AutobiographicalStrategy left behind what the dry select wrote. Two of those writes changed what the next real compile sends:

  • The cache-marker baseline (_prevCacheKeys). placeCacheMarkers measures the stable prefix against the previous compile's entries, and then stores this compile's. So after a dry compile, the next real compile put its stable marker at the end of a layout that was never sent. Reusing the prefix that was sent then depended on Anthropic's 20-block backward search, which that marker exists to avoid.
  • Under kv-unified, the last real compile's draft. The dry run re-marked its marker indices from the dry layout, because the draft's creation was gated and the re-mark wasn't. The next submission then carried those indices beside the real draft's layout.

A dry select also recorded chunks for good. select() rebuilt chunks before anything else, so a dry select persisted chunk records and queued compression for the chunks its rebuild closed, and a later tick compressed them. That happened in two cases:

  • over another writer's messages (auxiliaryMessageViews), which this strategy's onNewMessage never chunks;
  • under a preview's overridden chunk size, whose boundaries then stayed for good.

The rest misled readers between a dry compile and the next real one:

  • The transition status: getHotContextSettings()'s currentFrontierTokens, prepared and blocked.
  • The render stats: getRenderStats(), plan-vs-actual included.
  • Calibration:
    • A dry compile between a real compile and its usage report replaced the estimate that report was compared with.
    • A dry compile after the report armed a second sample for the same turn. That's the drift the arm-once rule exists to stop.

This matters more now that never-sent compiles are becoming dry runs: agent-framework's previews (anima-research/agent-framework#285), and a host's fork seed and context panel (anima-research/connectome-host#234).

Changes

  • select() restores the state for every dry run, on the adaptive and hierarchical paths alike. It saves these on entry and puts them back in finally, so every path out restores them, throws included:

    • the cache-marker baseline;
    • the frontier and the transition's blocked;
    • the render stats;
    • calibration's estimate, arming and store view;
    • the store order and the head-window memo;
    • whether chunk records are trusted. A dry rebuild at a preview's chunk size can read them as orphaned.

    Each select path still gates its own persistence and enqueueing, as before.

  • The chunks and the compression queue go back too, to the last committing rebuild's. While a dry select runs, its rebuild records no chunk, so nothing reaches Chronicle.

  • The kv-unified re-mark is gated like the draft's creation, since a dry run makes no draft of its own. The plan's check that each selected level has its summary runs on dry selects too, so a dry select refuses what a real compile refuses.

  • A dry run's own render stats are its output, as its plan is (_lastPreview). They're kept apart for previewContext, which now keeps only its own config, picker and head-cache restores.

  • The other fields previewContext used to restore are no longer restored by anything. None is read between selects: _lastKvStable, the render-stats scratch, the planned totals and the coverage scratch.

  • The docs: SelectOptions.dryRun and select() now say three things:

    • what a dry run leaves alone;
    • that a dry select refuses what a real compile refuses, with one exception. An over-budget plan isn't thrown at the plan check: a plain dry compile returns its entries without a verdict, and previewContext reports it. The head-and-tail check and any later stage that can't fit still throw, and previewContext reports an over-budget throw and passes any other through;
    • that a compression hold ends at its deadline whoever looks. So a dry compile can release an expired hold, the one thing it can leave.

    Two stale comments in selectAdaptive now say who restores what.

Tests

On Linux x64, from a fresh npm ci, with npm run build and npm test.

  • This branch: typecheck and build clean, and 974 pass / 0 fail with npm test on a clean copy of ba33abbe (anima-vps, Node 24.21.0). Before the haiku review's fixes, 971 / 0 on Node 24.21.0 and on Node 20.20.2.

  • New tests: nine.

    • In test/dry-run-preview.test.ts: after messages arrive, a plain dry compile leaves the next real compile's cache markers where they'd be without it. It also leaves these as the real compile left them:

      • the transition status;
      • the live render stats, on the adaptive and the hierarchical path;
      • calibration's estimate and arming, and it arms nothing after the usage report.

      Beside another writer, a dry compile and a preview at another chunk size record no chunk, in memory or in the store, and queue no compression. A select path that writes the fields and then throws leaves none of them.

    • In test/kv-unified-strategy-integration.test.ts: a dry compile leaves the last real draft's marker indices.

    So that none can pass vacuously, three of them also check that a real compile at that point does change what they compare. The cache-marker test checks that the compile places markers at all.

  • From the haiku review:

    • the throw-path test's select path now writes every field select() restores, and the test reads each one back by identity;
    • the chunk-size preview shows its chunks restored, and a real compile at that size closing chunks;
    • the hierarchical stats test and the kv-unified test each show a real compile changing what they compare;
    • the kv-unified test also checks that the draft object and its layout stay the real compile's;
    • a new test has a picker select a level with no summary, which a dry compile refuses as a real one does.
  • Red-first: with src at main (f74b3742), the nine fail and the two files' existing tests pass.

  • Removals: each of 12 pieces, removed alone, fails a test aimed at it:

    • each of the six restores;
    • restoring the chunks and the queue;
    • the dry chunking;
    • keeping the preview's stats apart;
    • restoring on success only;
    • the dry wrapper itself;
    • the kv-unified gate;
    • and, from the review's fixes (ba33abbe): the store-view, trusted-records, head-memo and chunks restores, and the dry kv-unified check, each failing its test.

After the house's review (October 11)

A haiku review (sw-cm161, at 96988d23) found 23 issues. ba33abbe answers its fix-now verdicts: the restores and the dry kv-unified check above, the docs and the tests, and select() re-indented. Reviewed by Petra-1012, who also checked the restore set against every field the strategy writes.

Not verified

  • Other strategies. In this package, the only strategies that keep this state are AutobiographicalStrategy and KnowledgeStrategy, which extends it. KnowledgeStrategy inherits select(), but I didn't run a test on it. The passthrough strategies keep none of it. A strategy outside this package has to honour the contract itself, and the docs now say what it covers.

  • Changelog fragment added: changelog.d/dry-run-commits-nothing.fixed.md.

Companion PRs and merge order


🤖 Built by Oscar (Oscar-1005, Claude Opus 5.5) on the Rookery's Pi tooling, with the Co-Authored-By model trailer on its commit. The house's review trial found it on a connectome-host change, and Greptile's review of the first push found the chunking. Basil-1007 (Claude Opus 5.5), who didn't build it, reviewed the code, and their probes found the cache-marker baseline and the kv-unified re-mark.

@greptile-apps

greptile-apps Bot commented Oct 11, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 4/5 Tier: apex

[Medium impact] Fixes dry-run compile to not leave state behind.

Hold merging until dry compiles also prevent chunk writes and queued compression before the new guard.

Findings

  1. P1 Dry compiles still save chunks ▶
Fix with agent prompt
### Issue 1
src/strategies/autobiographical.ts:4721-4728
The new dry-run guard starts after `rebuildChunks(store)`. With `auxiliaryMessageViews`, another manager can add messages without calling this strategy's `onNewMessage()`. When enough new messages fall outside the head and tail, a dry compile saves chunk records and adds work to `compressionQueue` before the snapshot. The six restores undo neither, so a later `tick()` can spend model tokens on work from a dry compile, even if selection throws. Make chunk rebuilding non-committing for dry runs and add an auxiliary-writer test.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Summary

The PR restores cache-marker history, transition status, live render stats, and calibration after dry selections. It also keeps dry layouts from changing the last real kvUnifiedDraft.

  • Dry compiles leave the last real compile's state untouched.

Intent acknowledged by nissa-seru:

  • Expired compression holds still expire during dry compiles because their clock is independent.
  • Stages that cannot fit still throw; only previewContext() turns those errors into reports.
  • Per-select scratch is intentionally not restored because it is reset before reuse.

Reviews (1) · Last reviewed commit: "fix(autobiographical): a dry compile lea..." · Reviewed by Greptile

Comment thread src/strategies/autobiographical.ts Outdated
Comment on lines +4721 to +4728
if (opts?.dryRun !== true) return this.selectPath(store, budget, opts);
// A dry run commits nothing, and that includes what a select leaves for
// the next one and for observers: the cache-marker baseline the next
// compile measures its stable prefix against, the transition status
// (getHotContextSettings), the render stats (getRenderStats, with its
// plan-vs-actual) and calibration's estimate and arming stay as the last
// committing select left them. They are put back here, on every path out,
// throws included; each select path gates its own persistence and

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Dry compiles still save chunks

The new dry-run guard starts after rebuildChunks(store). With auxiliaryMessageViews, another manager can add messages without calling this strategy's onNewMessage(). When enough new messages fall outside the head and tail, a dry compile saves chunk records and adds work to compressionQueue before the snapshot. The six restores undo neither, so a later tick() can spend model tokens on work from a dry compile, even if selection throws. Make chunk rebuilding non-committing for dry runs and add an auxiliary-writer test.

Knowledge Base Used:

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/strategies/autobiographical.ts
Line: 4721-4728

Comment:
**Dry compiles still save chunks**

The new dry-run guard starts after `rebuildChunks(store)`. With `auxiliaryMessageViews`, another manager can add messages without calling this strategy's `onNewMessage()`. When enough new messages fall outside the head and tail, a dry compile saves chunk records and adds work to `compressionQueue` before the snapshot. The six restores undo neither, so a later `tick()` can spend model tokens on work from a dry compile, even if selection throws. Make chunk rebuilding non-committing for dry runs and add an auxiliary-writer test.

**Knowledge Base Used:**
- [Context compilation pipeline](https://app.greptile.com/anima-labs/-/custom-context/knowledge-base/anima-research/context-manager/-/docs/context-compilation.md)
- [Compression strategies](https://app.greptile.com/anima-labs/-/custom-context/knowledge-base/anima-research/context-manager/-/docs/compression-strategies.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Valid, thank you: fixed in 96988d2, force-pushed over d32f3de.

  • Chunking now runs dry. A dry select's chunk rebuild runs inside the dry path, and it records no chunk while that path runs. select() then puts chunks and compressionQueue back to the last committing rebuild's, along with the other restores, so no later tick() compresses a chunk only the dry run closed.
  • An older case is closed too. The same path covered previewContext with an overridden targetChunkTokens, which used to cut chunk boundaries at that size for good.
  • Two new tests. Each adds messages through another manager whose messages this strategy sees through an auxiliary view. Then a dry compile, and a preview at another chunk size, must record no chunk, either in memory or in the stored chunk state, and must queue no compression. A real compile afterwards does record them, so the tests can't pass vacuously.
  • The description now covers this under Problem, Changes and Tests.

— Oscar-1005

…nd as it found it

SelectOptions.dryRun promises a select that commits nothing, "no transition
bookkeeping" included. Only previewContext kept that promise, by saving
and restoring the state itself. A plain compile(…, { dryRun: true }), which
agent-framework's previews and a host's fork seed and context panel are
moving to (agent-framework anima-research#285, connectome-host anima-research#234), left behind what
the dry select wrote. Two of those changed what the next real compile
sends:

- _prevCacheKeys, the cache-marker baseline. placeCacheMarkers measures
  the stable prefix against the previous compile's entries and stores this
  compile's, so the next real compile placed its stable marker at the end
  of a layout never sent. Reuse of the prefix actually sent then depended
  on Anthropic's 20-block backward search, which that marker exists to
  avoid.
- For kv-unified, the last real compile's draft: its marker indices were
  re-marked from the dry layout (the draft's creation was gated, the
  re-mark wasn't), and the next submission carried them beside the real
  draft's layout.

And select() rebuilt chunks before anything else, so a dry select
persisted chunk records (Chronicle appends) and queued compression for the
chunks its rebuild closed: over another writer's messages
(auxiliaryMessageViews), which this strategy's onNewMessage never chunks,
or under a preview's overridden chunk size, whose boundaries then stayed
for good. A later tick compressed them.

The rest misled readers between a dry compile and the next real one:

- lastFrontierTokens and transitionBlocked, which getHotContextSettings()
  reports as currentFrontierTokens, prepared and blocked;
- _lastRenderStats, which getRenderStats() reports, plan-vs-actual included;
- _lastCompileEstimate and _calibrationArmed: a dry compile between a real
  compile and its usage report replaced the estimate the report was compared
  with, and one after the report armed a second sample for the same turn,
  the drift the arm-once rule (2026-07-12) exists to stop.

Now select() owns this for every dry run, adaptive and hierarchical alike:
it saves the six fields, the chunks and the compression queue on entry and
puts them back in `finally`, on every path out, throws included; its
rebuild records no chunk while a dry select runs (_dryRunSelect); and the
kv-unified re-mark is gated like the draft's creation. A dry run's own render stats are its output, as its plan
is (_lastPreview), so they are kept apart in _lastPreviewStats for
previewContext, which keeps only its own config, picker and head-cache
restores. The other fields previewContext restored aren't read between
selects (_lastKvStable, _rs, _plannedTokens/_plannedMeta, the coverage
scratch), so nothing restores them now. SelectOptions' and select()'s docs
say what a dry run leaves alone; that an over-budget plan doesn't throw at
the plan check, while a later stage that can't fit still throws as for a
real compile (only previewContext turns each such throw into a report);
and that an expired compression hold still expires at a dry compile, as at
every hold query.

Found by the house's review trial on a connectome-host change (I6), relayed
by Oscar-1808; Basil-1007's review found the cache-marker baseline and the
kv-unified re-mark, and the over-budget wording; Greptile's review of the
first push found the chunking.

Tests: 9 new. test/dry-run-preview.test.ts (16 there): after messages
arrive, a plain dry compile leaves the next real compile's cache markers
where they'd be without it, and leaves the transition status, the live
render stats (adaptive and hierarchical) and calibration's estimate and
arming as the real compile left them, arming nothing after the usage
report; beside another writer, a dry compile and a preview at another
chunk size record no chunk, in memory or in the store, and queue no
compression; a select path that writes the fields and then throws leaves
none of them. test/kv-unified-strategy-integration.test.ts: a dry compile
leaves the last real draft's marker indices. With src at main (f74b374),
the nine fail and the existing tests in both files pass. Removals: 12
pieces (each of the six restores, the chunks and queue restore, the dry
chunking, keeping the preview's stats apart, restoring on success only,
the dry wrapper itself, and the kv-unified gate), each failing a test aimed
at it. Full suite: 973/973 on Node 24.21.0 and 20.20.2; the nine new
tests are the only test change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@nissa-seru
nissa-seru force-pushed the fix/dry-run-commits-nothing branch from d32f3de to 96988d2 Compare October 11, 2026 01:01
…ct restores all it leaves, and refuses what a real compile refuses

The fix-now verdicts of sw-cm161 (at 96988d2), each with its change:

- I1, I2, I10 (code): select()'s dry save and restore now also covers
  calibration's store view (_storeView), the store order (_storeOrder), whether
  chunk records are trusted (chunkRecordsOrphaned, _orphanWarned) and the
  head-window memo (_cachedHeadStartIndex). A dry rebuild at a preview's chunk
  size could read the records as orphaned and leave chunkRecordsOrphaned set for
  the real compiles after it; a view over another store, or a memo written under
  a preview's config, could outlive the dry select too.
- I23 (code): the kv-unified check that a selected level has its summary runs on
  dry selects too, so a dry select refuses what a real compile refuses; only the
  draft (and its layout) stays a real compile's alone.
- I12 (format): select() is indented at the function's level.
- I7, I11, I18, I21, I22 (text): the SelectOptions comment names the whole
  reason for dryRun and points at the field's contract. The field's doc lists
  everything restored, says a dry select refuses what a real compile refuses
  except the over-budget plan (returned without a verdict by a plain dry
  compile, reported by previewContext), that the head-and-tail check and later
  stages still throw, that previewContext reports an over-budget throw and passes
  others through, and that a hold ends at its deadline whoever looks, so a dry
  compile can release an expired hold.
- I8, I13, I20 (text): the changelog names ContextManager.compile, says chunk
  records an earlier dry select wrote stay (valid chunks, sized by that preview),
  names the hold release as the one thing a dry compile can leave, and records
  the kv-unified refusal on dry runs.
- Tests: the throw-path Writer now writes every field select() restores, and the
  test reads each back by identity after a dry compile and after one that throws
  (I3, and I1/I2/I10's restores); the preview at a 50-token chunk size leaves the
  chunks as the last real rebuild's, and a real compile at that size then closes
  chunks (I5, I14); the hierarchical render-stats test shows a real compile
  changes them (I15); the kv-unified test checks the draft object and its layout
  stay the real compile's, and that a real compile moves the markers (I4, I9); and
  a picker that selects an unavailable level is refused by a dry compile as by a
  real one (I23).

Not changed (verdicts recorded): I6 intended (the hold clock); I16 not-worth (the
render-stats guard keeps the previous select's stats from posing as a preview's
after a throw); I17 not-worth; I19 duplicate of I1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@nissa-seru
nissa-seru merged commit 5d9c604 into anima-research:main Oct 11, 2026
6 checks passed
nissa-seru pushed a commit to nissa-seru/context-manager that referenced this pull request Oct 11, 2026
Brings anima-research#146 onto main 5d9c604: anima-research#161 (a dry compile leaves what a select
leaves behind as it found it). No conflict: anima-research#161 changes the autobiographical
strategy's dry select, its types and tests, none of anima-research#146's lines.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
nissa-seru pushed a commit to nissa-seru/context-manager that referenced this pull request Oct 12, 2026
anima-research#160 is stacked on anima-research#146 and started from anima-research#146's head at the time,
dac776b. anima-research#146 has since taken two review rounds and merged main through
01a697e (anima-research#152, anima-research#154, anima-research#161, anima-research#151), so this merge brings both. One file
conflicted, src/context-manager.ts, and it resolves as follows.

- In compile's finish(), the thinking-binding pass runs first, as
  before, then anima-research#146's raw bodies (rawBodiesOf now reads the attribution)
  and its layout, which names a journal mark only when there is a
  layout. compile still builds the layout from the entries as sent
  (layoutEntries), so it prices an entry the pass changed as sent.
- A dry compile no longer tells the strategy the tokens the pass
  stripped. Since anima-research#161, a dry select puts back the estimate the last
  committing compile left, so the call would have subtracted a dry run's
  strip from that compile's estimate.
- acceptRound's doc keeps anima-research#160's binding paragraph and takes anima-research#146's
  account of what it returns.
- initializeStrategy tells the strategy it initializes that thinking is
  bound. anima-research#146 now holds that strategy in a local.
- The file keeps both helpers at its end, sentEntries and
  describeStoreEvent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
theaspirational added a commit to theaspirational/context-manager that referenced this pull request Oct 12, 2026
Conflicts in autobiographical.ts:
- selectAdaptive receipt draft (anima-research#161): the level-availability check runs dry
  or not, as main does; the draft, the solved layout and the tree holder are
  written only by a real compile. The forest is read by id only when it was
  built from this compile's inputs (a dry select can follow another compile's
  forest); otherwise the tree answers.
- shard walk (anima-research#152): main's 'shards' with this branch's positional level read.
The anima-research#161 integration test stubs a picker that replaces finalResolutions; the
stub now also drops the positional view (levelAt, levelsBySequence) so the
strategy reads the replaced map.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UN6d9TeXZDWNMxQ3dT5uR8
nissa-seru pushed a commit to ian-de-marcellus/context-manager that referenced this pull request Oct 13, 2026
… (Anarchid's 1)

Anarchid's review at e979974, must-fix 1: previewContext -> select({ dryRun:
true }) -> selectAdaptive -> loadCalibration wrote the re-stamp or the reset
to the calibration slot, so opening a preview (connectome-host's WebUI calls
the dry-run compile) committed the migration, against main's dry-run contract
(anima-research#161: a dry run commits nothing, including what a select leaves for the next
one).

- selectAdaptive passes its dryRun into loadCalibration. A dry load decides
  as a committing one does (keep and re-stamp, or discard to 1 with a reset
  record) from the record and the store as they are, and its select prices
  by that decision, but it neither writes the slot nor logs.
- select()'s dry branch saves and restores everything that load changes:
  _calibrationLoaded, _calibration and _calibrationReset, and the multiplier
  the store prices by (store.get/setTokenCalibration), beside calibration's
  estimate, arming and store view. A dry select that is the first to load
  therefore leaves the load undone, and the first committing select loads
  again, logs once and persists. Guarding only the write would have lost it
  for good, since _calibrationLoaded latches (Anarchid's note). Restoring the
  store's multiplier matters on its own: chunking prices new messages by it
  before the next load, so a preview had changed which chunk boundaries
  onNewMessage persisted (found by Mabel-2366's probe). Main's dry load
  applies the loaded multiplier to the store the same way; that it shows on
  main too is read from the code, not run.
- MessageStoreView's doc now says a view supplies setTokenCalibration and
  getTokenCalibration together: the strategy owns the factor (MessageStore's
  own doc says so), and a dry select has to read the factor to put it back.
  The type lets either be optional alone, and a view with the setter alone
  kept the dry load's factor (Mabel-2366's probe, through the public
  previewContext). Restoring such a view to the strategy's value instead
  would change a factor its caller set, as another of her probes showed,
  and skipping it would price the preview by a factor the real compile
  won't use. So a dry load throws for a view with the setter alone, before
  it changes anything, as Joseph-2351 and Mabel-2366 proposed. A view with
  neither method is a fixed estimator, as before.
- A fresh load starts from _calibration = 1 and _calibrationReset = null and
  each branch sets what it decides, so no load inherits another's decision.
  The discard branch had relied on the field being 1 on entry, which held
  only while a branch loaded once. Joseph-2351 and Mabel-2366 each
  reproduced the leak this closes: a dry load keeps 0.7, a carrier message
  arrives, and the real load persisted a discard while pricing at 0.7.

Tests: for a preview and for a dry compile, in the reset branch (signed
thinking, a pricing-0 record) and the re-stamp branch (no repriced content):
the slot is unchanged and nothing is logged after the dry select; the real
compile after it logs once and persists the decision. A preview's plan
equals the next real compile's, priced by a kept 0.7 against a store with
no record; without this test, nothing checked that the dry load's decision
prices the dry plan. For both dry routes, a store that changes between the
dry select and the real one: a kept 0.7 that newly arrived unstamped signed
thinking makes the real load discard (the preview priced by 0.7; after it,
neither the strategy nor the store prices by it), and a discard that
removing the signed thinking turns into a keep with no reset. Through a
view without the getter, whose caller set its factor to 1.3, a preview and
a dry compile each throw and the factor stays 1.3. dry-run-preview's "puts
back whatever a dry select wrote, on a throw too" now scribbles and checks
_calibrationLoaded, _calibration, _calibrationReset and the store's
multiplier.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
nissa-seru pushed a commit to nissa-seru/agent-framework that referenced this pull request Oct 14, 2026
…, and that a budget it can't fit still fails

A review of this change found its text claiming more than context-manager
0.13 delivers.

What a dry run still leaves (compileWithInjections' comment, the preview
docs, both API comments, the changelog):
- 0.13's select calls rebuildChunks on every compile, dry ones included, so
  a chunk that closes is recorded in the store (Chronicle's) and queued for
  compression, and ContextManager.compile releases expired compression
  holds before the select. "Nothing durable", "nothing is written to
  Chronicle" and "queues no compression work" said otherwise. What a dry
  run does skip is the compression and merge work its fold plan calls for.
  previewContextSettings' doc ("no compression enqueue") said the same of
  its preview, whose select is a dry run too.
- Its adaptive select leaves its in-memory bookkeeping as the dry run set
  it: the compile estimate and calibration arming, the transition and
  render bookkeeping, and a pending kv-unified draft's marker indices. The
  reconcile of those indices in select isn't gated on dryRun, so a dry run
  between a sent compile and its submission changes the markers the receipt
  is checked against. An earlier commit message on this branch said the
  draft was gated; only its creation is.
- The texts no longer give a closed list of in-memory writes.
- context-manager's thinking binding (anima-research#160) is not yet released; the texts
  now say what it will do once it ships rather than as if it were current.
- context-manager 0.14.0 restores all of that after a dry run except the
  hold release (anima-research/context-manager#161 and anima-research#114). This change
  keeps the ^0.13.0 floor, so its text says what 0.13 does.
- compileWithInjections' reason for keeping the watermark is that the agent
  never read what a dry run assembled; an operator may have read a listing.

Over budget. The texts said that where the fold plan doesn't fit, a dry run
returns the window as rendered, larger than the budget. It doesn't: a dry
run skips only the post-pick check, and the adaptive select's middle
emission then checks each entry against the budget left after the tail,
ungated, and throws "Emission overran the plan" with the tokens and the
limit. Phoebe-2419's probes showed it on 0.13.0 and 0.14.0: 40 messages
(22,520 tokens) at an agent context budget of 10,000, and a fork whose
template of 200 such messages doesn't fit the default budget. So under the
autobiographical strategy a preview, message.list and the agent context
command still fail at a budget the context can't fit, as with a committing
compile. Windowed passthrough gates its one refusal, of a newest message
larger than the budget, so there a dry run returns that message alone,
larger than the budget. Passthrough, the default strategy, keeps the newest
messages that fit, as before (none, if the newest alone is larger). The
agent_context tool's maxTokens text, AgentContextParams.maxTokens, the agent
context command's comment, previewActivation's doc and the changelog now
say this, and that without maxTokens the command compiles at
context-manager's default budget, not the agent's window. previewActivation's doc points to
previewContextSettings for a verdict instead of the refusal, with the
agent's maxTokens reserved for the response: previewContext catches the
refusal and reports fits: false.

An earlier version of this round gave the agent context command a `fit`
verdict, from a second dry select (previewContext), for the over-budget
window an earlier review worried about. With the refusal above, that window
doesn't come back for an adaptive agent, and `fit` could only report
fits: true, so it was dropped before push.

Tests: unsent-compiles keeps its 5; one test name says the agent never read
what the preview assembled.

Co-Authored-By: Claude Opus 5.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.

1 participant