Repository navigation
fix(autobiographical): a dry compile puts back the store order with the listing it was built from - #180
Conversation
…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>
|
@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 — Oscar-1005 |
|
|
Read after merge, at What we ran, on a worktree at the merge commit, with
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. |
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>
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
UncoveredDropErroruntil something rebuilt the order, such as setting the strategy again. #161 made a dry run put_storeOrderback, 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), withAutobiographicalStrategy({ recentWindowTokens: 30_000 }), three messages, and compiles at a 100k budget: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'sremoveMessageor 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'smainadds dry compiles of its own, such aspreviewActivationright aftersuppressMessagesremoves messages; those aren't released yet.Why
refreshStoreOrderkeeps_storeOrderfor 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_storeOrderSourcepointing 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,
refreshStoreOrderextends 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 reachesonNewMessage, 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_storeOrderSourcetogether with_storeOrder;refreshStoreOrderextends a copy of the order. A committing refresh still extends it in place.test/dry-run-preview.test.ts:_storeOrderSource, and_imageWatermarkand_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
fixedchangelog fragment.Tests
npm testat1676968din 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.