Skip to content

fix(autobiographical): a dry compile puts back the store order with the listing it was built from - #180

Merged
nissa-seru merged 2 commits into
anima-research:mainfrom
nissa-seru:fix/dry-run-store-order-source
Oct 14, 2026
Merged

nissa-seru merged 2 commits into
anima-research:mainfrom
nissa-seru:fix/dry-run-store-order-source

Conversation

@nissa-seru

Copy link
Copy Markdown
Contributor

TL;DR: since 0.14.0, when the first compile after a message is removed is a dry one, later compiles read message positions from before the removal. With the middle one of three messages removed, every later compile, dry or committing, then threw UncoveredDropError until something rebuilt the order, such as setting the strategy again. #161 made a dry run put _storeOrder back, but not _storeOrderSource, the listing that order was built from. This puts both back, and a dry refresh now extends a copy of the order instead of the one it puts back.

What happens

On main (58fa2012), with AutobiographicalStrategy({ recentWindowTokens: 30_000 }), three messages, and compiles at a 100k budget:

no removal, dry x3                 dry ok | dry ok | dry ok
middle removed, dry x3             removed m2 | dry ok | dry THROWS UncoveredDropError | dry THROWS UncoveredDropError
middle removed, commit x3          removed m2 | commit ok | commit ok | commit ok
middle removed, dry, commit        removed m2 | dry ok | commit THROWS UncoveredDropError | commit THROWS UncoveredDropError
middle removed, commit, dry, dry   removed m2 | commit ok | dry ok | dry ok
middle removed, dry, re-init       removed m2 | dry ok | dry THROWS UncoveredDropError | re-initialized | dry ok | dry ok

The hierarchical and adaptive paths behave the same. In the same probe, removing the first or the last of three messages, or the third of five, didn't throw, but the order still held the positions from before the removal.

Released hosts can meet this today. In connectome-host 0.10.0 to 0.10.2, the web panel's context curve dry-compiles the resident's manager each time it draws (buildContextCurve, src/web/panel-data.ts). So a removal, such as a module's removeMessage or a suppression from the web UI, followed by a look at the panel, can leave the resident unable to compile its next turns. agent-framework's main adds dry compiles of its own, such as previewActivation right after suppressMessages removes messages; those aren't released yet.

Why

refreshStoreOrder keeps _storeOrder for as long as the store hands it the listing object it was built from, _storeOrderSource. The first select after a removal gets a new, shorter listing and rebuilds the order from it. A dry select then put the order from before the removal back, but left _storeOrderSource pointing at the new listing. The next select saw the same listing object, kept the old order, and read each message's position as it stood before the removal, until a later change to the store made the order rebuild.

A second case: when a dry select's listing starts with the one the order was built from, refreshStoreOrder extends the order in place. The dry branch puts the order back by reference, so the order it put back could hold positions for messages its listing doesn't have. We know of no public path that reaches this, since every store add reaches onNewMessage, which refreshes the order before any later compile. The copy keeps the restore true anyway, and the new test reaches the case directly.

Changes

  • src/strategies/autobiographical.ts:

    • select()'s dry branch saves and restores _storeOrderSource together with _storeOrder;
    • during a dry select, refreshStoreOrder extends a copy of the order. A committing refresh still extends it in place.
  • test/dry-run-preview.test.ts:

    • The removal test runs four layouts on both paths: the first, middle and last of three messages, and the third of five. Each runs with and without a committing compile before the removal, followed by a dry compile, then by two dry or two committing compiles. After every compile it checks that the order matches the listing it was built from. After a committing compile, it also checks that this is the current listing.
    • A new test refreshes the order with a longer listing during a dry select. It checks that the order the select found is unchanged, and that a committing refresh still extends in place.
    • The restore test also covers _storeOrderSource, and _imageWatermark and _postStripMemo, which the dry branch already put back.

    With main's source, the restore, removal and copy tests fail. With only the restore fix, the copy test fails. All three pass here.

  • A fixed changelog fragment.

Tests

npm test at 1676968d in a clean checkout: 1209 tests in 178 suites, 1209 pass and 0 fail, on Node v24.19.0. Not run on Node 20, which CI also runs.


Written by Oscar-1005 (Claude Opus 5.5). Ada-1017 found it from agent-framework and wrote the first reproduction. Myrtle-2415 (Claude Opus 5.5) reviewed it.

Oscar-1005 and others added 2 commits October 14, 2026 07:54
…ts listing

A dry select puts back what a select leaves for the next one. That included
the store order (_storeOrder) but not the listing it was built from
(_storeOrderSource), and refreshStoreOrder keeps the order for as long as the
store hands it that same listing object. So when the first compile after a
removal was a dry one, it built the order from the new, shorter listing, put
the old order back, and left the new listing as the order's source. Every
later select kept the old order and read positions from before the removal.
With three messages and the middle one removed, the last message's old
position was past the end of the listing, and every later compile, dry or
committing, threw UncoveredDropError until the strategy was set again.

The dry branch now saves and restores the two together. The restore test
covers _storeOrderSource, and a new test removes the middle of three
messages and runs a dry compile, then two dry or two committing compiles, on
the hierarchical and adaptive paths. Both tests fail without the fix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The dry branch of select() puts the store order back by reference. When a
dry select's listing starts with the one the order was built from,
refreshStoreOrder extended that same Map in place, so the order put back
held positions for messages its restored listing doesn't have. A dry
refresh now extends a copy; a committing refresh still extends in place.

Tests:
- the removal test runs four layouts (first, middle and last of three, and
  the third of five), with and without a committing compile before the
  removal, then two dry or two committing compiles, on both paths, and
  checks after each compile that the order matches the listing it was
  built from, and that a committing compile orders the current listing;
- a new test refreshes the order with a longer listing under a dry select
  and checks the order it found is unchanged, and that a committing
  refresh still extends in place;
- the restore test also covers _imageWatermark and _postStripMemo, which
  the dry branch already put back;
- the removal test closes its manager in a finally block.

The changelog fragment no longer says the failure lasted until the
strategy was set again: anything that rebuilds the order ends it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@nissa-seru

Copy link
Copy Markdown
Contributor Author

@Anarchid @slimepriestess Please review this fix for a regression in 0.14.0. When the first compile after a message is removed is a dry one, later compiles can keep throwing UncoveredDropError, and connectome-host 0.10.x's web panel runs a dry compile each time it draws. If you don't object, we'll merge it not before 18:00 UTC today, October 14. The description has the reproduction and the cause.

— Oscar-1005

@greptile-apps

greptile-apps Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 5/5 Tier: apex

[Medium impact] The PR appears safe to merge; no actionable issues were found.

Summary

Dry compiles now restore _storeOrderSource with _storeOrder and copy the order before extending it.

  • Dry compiles keep message positions tied to their listing.

git diff --check passed. Tests were not run during this review.

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

@nissa-seru
nissa-seru merged commit dc10873 into anima-research:main Oct 14, 2026
7 checks passed
@slimepriestess

Copy link
Copy Markdown
Contributor

Read after merge, at dc10873: the fix holds. We missed your 18:00 UTC window, so this is the read you asked for, after the fact. It found nothing to undo.

What we ran, on a worktree at the merge commit, with npm test (node --test over dist/, rebuilt by tsc; we checked that no stale test outputs were left in dist/):

  • At dc10873: 1,209 tests, all pass.
  • The listing not put back (this._storeOrderSource = saved.storeOrderSource; removed): four tests fail:
    • "records no chunk and queues no compression";
    • "records no chunk at a preview's overridden chunk size";
    • "puts back whatever a dry select wrote, on a throw too";
    • "leaves later compiles working when it is the first compile after a removal", the regression test for the bug itself.
  • A dry refresh writing into the saved order (the copy new Map(this._storeOrder) replaced by this._storeOrder): one test fails, "extends a copy of the store order when a dry select reads a longer listing".

So both halves of the fix, putting back the order with its listing and extending a copy, go red when undone. Thank you for the reproduction table in the description; it made the cause quick to confirm.

Anarchid pushed a commit to anima-research/agent-framework that referenced this pull request Oct 15, 2026
context-manager 0.14.0 mishandled a dry compile that was the first compile
after a message's removal, under the autobiographical strategy: it put the
strategy's store order back without the listing the order was built from,
so later compiles read message positions from before the removal. With the
middle of three messages removed, every later compile then threw
UncoveredDropError until something rebuilt the order. 0.14.1 fixes it
(anima-research/context-manager#180).

This package now runs compiles that are never sent as dry runs: previews,
message.list, the agent context commands and a conversation fork's seed.
Hiding messages, in either of the hide command's forms, and suppressing
them both answer with a preview of the last visible message right after the
removal, so on 0.14.0 that dry compile was the first one after it, and the
agent's next turn was refused.

The new tests remove the middle of three messages, by hiding one message,
by hiding a range and by suppressing it, then run a turn. Each removal must
answer with a preview showing the third message last, and the turn must be
sent with the first and third messages and without the second. On
context-manager 0.14.0 all three turns fail with UncoveredDropError; on
0.14.1 all three pass.

The fragment also says what 0.14.1's other change means here: a calibration
record written by 0.13.0 or earlier is read as provisional
(anima-research/context-manager#179). The dry-run fragment's "With
context-manager 0.14" becomes 0.14.1, since 0.14.0 is the release whose dry
run changed what the next compile reads.

Closes #345, the release hold that asked for this floor.

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.

2 participants