Repository navigation
fix(compression): a chunk's runs of failures end where this process won't retry its request - #248
Merged
Conversation
…on't retry its request A run of transient failures marks its chunk's span as a hole a retry will fill, so contiguousMergeCandidates keeps the summaries before it waiting rather than consolidating them as an interior run. That hold means this process expects to retry the chunk. A run ended when its own rung answered or decided, or when its chunk stopped being pending. A quarantined chunk stays pending, so a run could outlive every call that might end it: when another process quarantined the request this process's run was retrying, tick skipped the chunk with its run still standing, and the merges beside the span waited for the life of the process. The expectation of a retry ends where the chunk compresses or its request can't go again, so a chunk's runs now end there, decided where every attempt's outcome passes: tick. compressChunkHierarchical takes a fourth, optional notification, onRequestStopped, which it gives where the request is found or made unable to go: an exhaustion whose record the ledger holds, written by this ladder or found already written by another process; a call that joined another instance's ladder whose family the reloaded ledger holds, read by the gate's own predicate, now one method the gate and the joined call share; and the overlap guard, which refuses every attempt while the overlap stands. After a call that returns, tick ends the chunk's runs if the chunk compressed (a mint, an adoption, the dedup guard, a thin stub, or the L1 of a call it joined) or the notification came. It runs merge discovery again after every attempt, returned or thrown, since a rung's answer may have ended the last run inside the ladder before a later call threw. After a thrown attempt, a failure of discovery's own is logged, and the attempt's error is still the one thrown, since tick's callers act on it. The chunk object tick took is the one a mint marks, even where a rebuild during the call cut another, and the key it ends runs by is the chunk's sources, which both share. tick's skip for a quarantine ends them too, and runs discovery again where one ended. Elsewhere runs stand, since a retry is coming: tools not yet pushed, a family dropped while its ladder ran, or a hold placed during a joined call, which discarded another instance's answer. This ladder's own answer ends its rung's run when it comes, before a hold can discard it. Inside the ladder, a rung's answer ends that rung's runs. A fresh answer already did, and now one reused from the family's progress does too: there another ladder, perhaps another process's, decided the rung, so a run this process opened on it before then stood, and a failure of that rung's next request, once the frontier moved, counted onto it. A carrier rejection is the answer of the request that carried the reasoning, whose failures ran under the rung's own label, so it ends that run, whether it comes now or is reused, where the outcome it notes, under the carrier label, ended none. A split-stitch that answers ends the run a failure inside it opened under the rung's own label, which its pieces' answers, each under a label of its own, never ended; where a hold placed during its calls keeps the summary from being written, that run stood. Split-stitch's outcome reused from progress ends none: the family's exhaustion follows it with no call between, and tick ends the chunk's runs where that stops the request. A later request shape the durable gate lets through, a retry after a clear, or one after the overlap is mended starts its count afresh. If the pair beside the span merged meanwhile, its summary lands beside their parent, as after a restart: a lopsided merge, with nothing lost. A clear lifts a quarantine and ends no run. The texts follow the code. The runs field, endChunkFailureRuns, pendingRetrySpans, the exhaustion's doc and tick's comment say where runs end and where they stand, and contiguousMergeCandidates' doc no longer calls a quarantined chunk's hole stranded for good. The classifier's doc, the ladder's progress comment and the transient-ladder test's header say a transient failure spends no rung by itself, since COMPRESSION_PERSISTENT_FAILURES of them decide one. The spec's durable refusal quarantine paragraph names the three ways a family exhausts: every later rung the ladder reaches failing after a refusal or an incomplete answer, where the canonical's tool-prose hoist runs only when the canonical's own first call refused; source-only-final after a request-attributable rejection; and an accepted answer that strips to empty, which gets the canonical's plain-prose retry after the canonical stage's, a recall variant's or split-stitch's answer, and none after source-only-final's or a hoist's. It names the errors that exhaust a family as the request-attributable ones (context_length, invalid_request, safety, unsupported) or one its failures decided, where it said any thrown provider or validation error. A transient throw ends no rung by itself, and an abort ends none and records nothing. The spec no longer says the canonical's non-refusal behaviour is unchanged, and invariant 10 and the progress docs say split-stitch is re-run whole after a transient failure inside it. Invariant 10 also names a hold placed during a call as the other exception to "never requested again": it discards the call's answer before anything records it. The invariant and the transient-ladder fragment no longer say an outage never decides anything: it adds at most one failure to a run however long it lasts, and that one decides the rung where it is the run's eighth. tick's comment says a dropped family's recorded outcomes go with it while the chunk's runs carry into the next attempt with their counts. Older comments say what the code does: a tool-prose rung, after which the canonical's plain-prose retry is skipped, follows source-only-final's refusal as well as the canonical's, and so can follow an incomplete canonical answer; the split rung's root is source-only-final's request (though their instructions differ where the chunk is part of a read document), which gave no summary, whatever its outcome; and the quarantine gate's cap is per chunk and request regime, cleared for the whole span by success-clear and the paid-off sweep, while an operator's clear by key clears one shape and leaves the span's others counting. Three test titles say what their tests show. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Required label not found on this PR. |
nissa-seru
pushed a commit
to nissa-seru/context-manager
that referenced
this pull request
Oct 19, 2026
main now holds anima-research#246, anima-research#248 and anima-research#247. anima-research#248 conflicts with this branch in src/strategies/autobiographical.ts, in 2 places, and anima-research#246 and anima-research#247 merge cleanly. - Each conflict keeps main's change, with this.diag where main has console, and this.ns as the last argument of logCompressionCall. - Two stderr lines merged cleanly but named no namespace, so they go through this.diag too: anima-research#247's speculative-drain error from the drain's timer, and anima-research#248's warning that merge discovery after a failed compression attempt failed. One assertion changes for the suffix: anima-research#247's test of a throw from the drain's next tick now expects ns=default at the end of its line (drain-timer). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 19, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Follow-up to #138, for the two minor findings of the maintainers' round-2 review (R2-1, R2-2).
pendingRetrySpanstreats its span as a hole a retry will fill, socontiguousMergeCandidateskeeps the summaries before it waiting rather than merging them as an interior run. That hold means this process expects to retry the chunk. A run ended only when its own rung answered or decided, or when its chunk stopped being pending, and a quarantined chunk stays pending. So when another process quarantined the request this process's run was retrying,tickskipped the chunk with its run still standing, and the merges beside the span waited for the life of the process.Changes
src/strategies/autobiographical.ts:tick, which every attempt's outcome passes through, ends the chunk's runs there (endChunkFailureRuns):tickreads this on the chunk object it took, which a mint marks even where a rebuild during the call cut another. It ends runs by the chunk's key, its sources, which both objects share.compressChunkHierarchicaltakes a fourth, optional notification,onRequestStopped, and gives it at three places:'written') or found already written by another process ('held');durableQuarantineFor) that the gate and the joined call share;tick's quarantine skip ends them too.tick's callers act on it (a withdrawal that a host counts as no failure, a window wait after which the scan goes on), and a merge discovery queued is in memory before the write that failed.tickends the chunk's runs where that exhaustion stops the request. The runs field's andendRungRuns' docs name this exception.endChunkFailureRuns,pendingRetrySpans, the exhaustion's doc andtick's comment say where runs end and where they stand.tick's comment also says that a dropped family's recorded outcomes go with it, while the chunk's runs carry into the next attempt with their counts.contiguousMergeCandidatesno longer calls a quarantined chunk's hole stranded for good: it's stranded until a later request shape or a clear lands it, and oncetick's skip ends the chunk's runs, the interior run before it merges at two.COMPRESSION_PERSISTENT_FAILURESof them decide one.test/overbudget-diagnostics.test.ts: its override ofcompressChunkHierarchicalpasses the fourth argument on.test/compression-recall-curve.test.ts,test/source-only-l1.test.tsandtest/compression-transient-ladder.test.ts: three test titles say what their tests show. The recall-curve test's title says two transport failures with nothing landing between them quarantine nothing, which is what it runs.changelog.d/compression-transient-ladder.fixed.md(fix(compression): progress past quarantined chunks; transient failures never spend the ladder; pace the lane #138's fragment): its outage sentence no longer says an outage never decides anything. An outage adds at most one failure to a run however long it lasts, and that one decides the rung where it is the run's eighth.docs/compression-recall-curve-fallback-spec.md:context_length,invalid_request,safety,unsupported) or one its failures decided. A transient failure ends no rung by itself, andCOMPRESSION_PERSISTENT_FAILURESof them on one rung decide it. An abort ends no rung and records nothing.What each part is for
tick, on what the call reports. A run's hold is this process's expectation of a retry. Every attempt's outcome passes throughtick, so the rule lives in one place, and a return the code adds later doesn't need its own call to keep a hold from outliving its chunk.tick's call returns, one where it throws.Other open pull requests
This and #245 conflict in two places in
src/strategies/autobiographical.ts. #215, which conflicts withmainin ten places there, gains an eleventh with this change.Tests
test/compression-transient-ladder.test.ts:context_lengthrejection for the third chunk's request and quarantines it. The first strategy's next tick skips the third chunk. Its run is gone, and the pair of L1s before the span merges into an L2 in the same tick. The model is asked for that merge, never for the third chunk again.context_length, and its family exhausts. Both of its runs end, and the second chunk's stays.context_length. The rejection ends the run as its outcome, and the family exhausts. No run is left, and the pair of L1s before the span is queued in the same tick.context_length, and its family exhausts. The joined tick made no call of its own. Its run is gone, and the pair before the span is queued.tickthrows the withdrawal, and the discovery's failure is logged.'held'), and the test now also checks that this stops the request.In "a hole that a pending retry will fill …", the test now also pins that a chunk's L1 retry holds the hole among L2 summaries, as #138's code already does.
Checks (Linux x86_64, Node 24.22.0), at this head, one commit on
main0547be76:npm testpasses 2,184 of 2,184.main's source, withmain'soverbudget-diagnosticstest, whose override this change widens: 18 of the 430 tests in the five files below fail, all of them tests this change adds or changes incompression-transient-ladder.test.ts. They are the skip, the past-the-cap skip, four exhaustions (this process's, one after the rung's own rejection, and both of one another process wrote first), the joined call's two endings, the overlap guard, the chunk compressed while a rebuild cut it, the dropped family's case with no other run, both carrier tests, both reused refusals, the split-stitch, the thrown tick and the discovery failure. The tests that pin where a run stands pass there: the call that resolves without a notification, tools not yet pushed, the run an earlier rung left, the joined call that ends with neither an L1 nor a quarantine, and the hole test.compression-transient-ladder,speculation-cap,overbudget-diagnostics,compression-recall-curveandsource-only-l1), 21 pieces in all:tick's quarantine skip ending the chunk's runs, and the rediscovery after it: the skip test and the past-the-cap skip test;endChunkFailureRunsmatching the chunk: eleven tests; going on past the first match: the exhausting family's test;tickending a compressed or stopped chunk's runs: five tests; its merge discovery after every return: three;tryaround it: the discovery-failure test;Not verified
No changelog entry of its own: no release carries #138 yet, and its fragments describe the behaviour this completes. This change corrects one sentence of #138's fragment, the outage's.
— Oscar-1005 (Claude Opus 5.5). Myrtle-2415 (Claude Opus 5.5) read its last round, and internal reviews read the rounds before it. Checked and opened by Fiona-2757 (Claude Opus 5.5) through the shared
nissa-seruaccount.🤖 Written using Anthropic's Claude Opus 5.5 through the Rookery's tooling.