Repository navigation
Merge main into the calibration fix: the observer's calibration records at its new outcomes - #3
Closed
nissa-seru wants to merge 91 commits into
Closed
nissa-seru wants to merge 91 commits into
nissa-seru wants to merge 91 commits into
Conversation
…ve window kept within them A request carries at most so many images and so many image bytes. The provider refuses one with more than 600 images and documents together, and membrane refuses, before sending it, one whose serialized messages pass its byte cap (28 MiB unless MEMBRANE_MAX_REQUEST_BYTES says otherwise). The live window's limits are targets a compile keeps to, and nothing bounded what a request could reach: a window configured as unlimited (0), or above those limits, compiled requests the provider or membrane would refuse, and a compression prompt had no count limit at all. A caller that appends to a compiled request without recompiling, such as a turn's continuation rounds, had no figure to stop at. - maxRequestImages (default 500) and maxRequestImageBytes (default 24 MiB) on AutobiographicalConfig and WindowedPassthroughOptions. 0 is no ceiling, and a value that isn't 0 or a finite number above it takes the default. - The window's count and byte limits are clamped to them in one place, resolveLiveImageLimits, which both strategies' strip, the hysteresis watermark and the post-strip estimates read. An unlimited limit means up to the ceiling, a limit above it keeps the ceiling's worth, and a fractional limit or ceiling is floored, to at least 1. - In the window, the ceilings bind what the window counts: top-level image blocks, by the length of their base64 source.data. An image nested in a tool result, a generated image and a data: URL's bytes aren't counted there, and a caller's compile-time injections and its appends are its own. maxLiveImages' doc says so once, and the other limits' docs refer to it. - ContextStrategy.liveImageLimits() reports the clamped limits and the ceilings. Both strategies implement it. - Compression prompts count every image, nested and generated ones included, against maxRequestImages, in the walk that keeps their byte budget: newest message first, and in block order within one. That budget stays within maxRequestImageBytes, and a budget that isn't a positive finite number means up to it. The log line names the count ceiling and the images kept. - A strategy warns once, when it initializes, when a limit sits above its ceiling (the compression budget included) or a ceiling is malformed. It marks a setting the host didn't give as the default, and gives a byte figure in MiB when it's a whole number of them. - Text: maxLiveImages' and windowed passthrough's docs no longer say 0 is unlimited or off, and maxLiveImages' doc says a window kept at the ceiling leaves an appending caller no room once it's full. maxLiveImageBytes' doc and the byte default's comment no longer promise membrane's whole-request cap or cite the API's 32 MB. Tests: request-image-ceilings covers the accessor's defaults, clamps, malformed and fractional values; each strategy's strip under the ceilings, and the window counting top-level images only (a nested and a generated image pass the count ceiling); the post-strip estimates under both ceilings and after the clamped limit changes; the watermark under both ceilings; the warning's cases; and the compression prompt's count and budget. The image-stripping test of a disabled policy now disables the ceilings too and carries more images than the default window, and the compression log line's test takes its new wording. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Under a window limit of 0, which meant no limit, or one above a ceiling, the window keeps the ceiling's worth, and an operator who set no limit had no line saying where one now applies: the initialize warning says nothing for 0, since "as many as fit" is what 0 means. - ceilingSetLimits says which of the window's limits a ceiling sets: a limit given as none or above its ceiling, where the ceiling is a limit. - Each strategy says once for each ceiling, the first time an image falls past it under such a limit, through the same route as the initialize warning: which ceiling, its value, and the window limit it held (ceilingBoundLine), marking defaults as the warning does. The memory strategy says it from its strip and from its hysteresis watermark, whose trims strip below the ceiling. - It changes no request: the strip keeps and replaces what it did. - maxRequestImages' and maxRequestImageBytes' docs and the changed fragment say so. Tests: for both strategies, the line once for a count of 0, one above the ceiling, the default window under a lowered ceiling and a byte budget of 0; nothing when the host's own limit strips, equals the ceiling, or nothing falls past it; and for the memory strategy, the line from its watermark under each ceiling. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…arks exact From a review of the round before it: - The ceiling line comes only from a render that's sent: a dry compile, a preview, an observer's reconstruction say nothing (_dryRunSelect), and the memory strategy's strip says it only when its selection asks (applyImageStripping's opts.sent, from selectAdaptive and selectHierarchical), so kv-unified's kept layout, rendered to compare against, doesn't. Windowed passthrough's dry render says nothing either. A preview's own ceiling no longer reads as the window's, or uses up the line. - Its words hold for a single image bigger than the byte ceiling: "an image fell past maxRequestImageBytes ..., so images past it are now shown as placeholders". - "(default)" marks only defaults in force: in the memory strategy, a value the library's default layer gave or no layer gave, and a value given as undefined or null where a default then applies, but not maxLiveImages, whose undefined reads as no limit; a value a subclass's enforced layer gives isn't a default. A malformed ceiling, which takes its default, is marked in both lines. - Docs: the byte ceiling's margin names images the window doesn't count, and maxRequestImageBytes' doc qualifies when the line is said. Tests: the line said at the first compile that strips, with the strategy's name and through the warning's route; once for each ceiling when both strip; nothing when a host's byte limit strips, from a dry compile, from a preview with its own ceiling, or from the watermark when the host's own limit binds; the adaptive selection's line; nullish and enforced settings' marks; the line's words for default and malformed ceilings and byte figures; compression budgets that are negative, NaN or fractional; the default compression budget's warning clause alone; and the returned requests of the compression walks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… its render From a review of the round before it: - The memory strategy reports the ceiling line once a live select returns its render (reportCeilingHits), not from inside the strip: a select refused after its strip, such as one whose structural repair drops the newest turn, says nothing, and the next select it returns says it. The selection's strip records what its ceilings stripped for that render (`sent`), and a hysteresis watermark moved by a ceiling outside a dry run, as on a new message, leaves it for the next render returned. - The accessor's doc says its figures count the images the window counts. Tests: a refused select says nothing and the next one says it; a preview's own ceiling moving the watermark leaves no line; a single image bigger than the byte ceiling; the dry compile, with what it kept, and the compare-only strip for the byte ceiling too; both ceilings each at its own step; and the warning's marks for an undefined byte budget and a malformed byte ceiling under a limit above it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ept with its branch From a review of the round before it: - The memory strategy's live select clears what earlier strips recorded before its own strips run. A dry select's strips record too, and a strip with no limits returns before recording, so a host with every limit off heard the line at its next compile after a preview with a ceiling of its own. - A ceiling's move of the hysteresis watermark is cleared with the watermark itself when a branch loads (clearBranchMirrors), so a move on one branch isn't reported at the first render returned on another. reportCeilingHits' doc says the move it reports is this instance's, on this branch: a watermark loaded from the store carries no record of what moved it. - The ceilings' docs, LiveImageLimits' and the changelog fragment say the window keeps the images it counts within the ceilings, and the 500 default's margin names what the window doesn't count. Tests: a preview at a ceiling of its own, then a compile with no limits, says nothing; a watermark a new message moves says nothing until a compile returns, which says it once; a watermark moved on a fork says nothing back on main; the preview's own watermark trim is checked; the refused select is matched by its error; and the step where both ceilings strip in turn expects the byte walk's greedy keep, as on main. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…only when a ceiling decides Answers review round 1 on anima-research#268: R1-1, R1-2 and R1-4 to R1-9. R1-1: maxRequestImages and maxRequestImageBytes become imageCeiling and imageByteCeiling. Each doc opens with what it bounds: the top-level image blocks a strategy's live window keeps, and what a compression prompt carries. Each then says what it doesn't count: the window's nested and generated images, and whatever a caller injects or appends. The module's constants follow (DEFAULT_IMAGE_CEILING, DEFAULT_IMAGE_BYTE_CEILING). R1-2: ContextStrategy.liveImageLimits() becomes imageCeilings(), which reports the two ceilings alone. The window's own target leaves the public API (ResolvedImageLimits, inside the module), and the doc says the ceilings are configuration, not usage: a caller that wants a request within them counts that request's images itself. R1-4: the once-only line is said only when a ceiling decides that an image is stripped (ceilingsDeciding), not when the depth limit, the hysteresis watermark, or a count or byte limit the host set strips it anyway. That holds in both strategies' strips and in the watermark's move. "sent" becomes "returned", since a strategy sees the render select returns, not whether it was sent. R1-5: a breaking fragment names the change in what 0 means for maxLiveImages, maxLiveImageBytes and maxCompressionImageBytes, with the migration, the siblings' ranges on npm, and what doesn't change. R1-6: both ceilings are in DEFAULT_AUTOBIOGRAPHICAL_CONFIG, so config:effective reports them. R1-7: windowed passthrough's applyImageStripping takes `returned` with a default of false, so a caller passing three arguments, as renderAsNewest does on main, stays correct and says nothing. R1-8: a compression test where a newer generated_image takes the last slot from an older image. R1-9: the note on membrane's byte cap says where it's checked: transformRequest, which a call's first request and each native tool-loop round go through but an XML-mode continuation round doesn't, and the Anthropic adapter, on every request it sends (membrane 0.5.87 and 0.6.0). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
One conflict, in src/strategies/autobiographical.ts: anima-research#249 wraps the live select's selectPath in withToolPruning, and this branch assigns its result to entries. Both are kept. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s it through cappedForSend Since tool pruning is priced as sent, the tool pass a compile runs between the cut and the image pass (postProcessToolEntries) only repairs pairing, and what sending prunes from a message's tool blocks is applied in cappedForSend, which renderAsNewest runs for each stored copy. Its doc said the pass it leaves out also prunes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t newer; the docs say where membrane checks The memory strategy's hysteresis watermark credited a ceiling with a move whenever the first image past a limit passed that ceiling and wasn't past the depth by the watermark's own measure, which counts only newer messages. The strips count the image's own message too, so a move the depth limit would make anyway, to the same place, could say the line, and a ceiling whose trim landed the watermark newer than the depth's could be missed. The watermark now works out where it would land with only the limits the host has in force, and credits each ceiling whose limit its walks passed when its own landing is newer. The docs say where membrane checks its byte cap, and that a compression request, which sets shedOversizeImages, has its oldest images shed rather than being refused. They say the line comes from a render a select returns, that a fraction is floored to at least 1, and what the window and a compression prompt each count. The breaking fragment's Unchanged bullet names the compression count cap and the fractional floor, and its account of a negative, Infinity or NaN limit is per value. Tests: the watermark at the depth boundary, with and without a move the depth would make anyway; a byte ceiling whose trim lands it newer; the kind credited when the host's own count limit misfits first; a trim that stops at the depth; the branch test under the byte ceiling; renderAsNewest under a binding ceiling, for both strategies; the watermark read where a test says it moved; and imageCeilings typed through ContextStrategy. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…wn byte limit The byte guard's mirror of the count guard's test: a host byte limit of 40 in force misfits the oldest image first, and the count ceiling of 4 shows up only in the trim, which lands newer. Without the byte guard, the line names the default byte ceiling as well, which held nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ling alone stripped, in the render returned The memory strategy's hysteresis watermark said the ceiling line at the next render a select returned after a ceiling decided its move, whether or not that render showed an image the move stripped. It credited a ceiling whose trim, at ratio x the ceiling, decided the landing though no image passed the ceiling itself, and it measured the host's own depth limit the watermark's way, newer messages only, while the strips count an image's own message too. A live move now records the sequences it stripped that the limits the host has in force wouldn't have: after where those limits would land it, through where it lands, with the ceilings the first image past a limit passed, which brought the move on. A later live move cuts earlier records at its own host-only landing, since the host's limits strip those images anyway. The strips credit a record's ceilings for an image in it that the depth limit doesn't strip, and the select reports them for the render it returns, then drops the records. So a move a host limit brings on says nothing even where a ceiling's trim lands it newer, and the line's words, that an image fell past the ceiling, hold. The guard that a landing is above the watermark is gone: both walks stop at it, and the trim's caps are no larger than the first walk's. The docs say the line comes at the first render a select returns where images are placeholders because an image passed the ceiling, that a fraction of maxLiveImages is floored to at least 1, and that membrane's shedding stops when the request fits or no image is left, after which only the Anthropic adapter's check refuses one still over its cap. Tests: a move the depth limit, or the host's own count or byte limit, brings on says nothing where a ceiling's trim lands it newer; an image the move strips that the strip's depth also strips says nothing; the host's own limit landing the move where a ceiling does says nothing, for each kind; a later host move overtaking a ceiling's before a render says nothing; and only the ceiling an image passed is named when both limits are the ceilings'. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ds is said Dropping the records at every reported render lost the line when that render left the move's images out, such as an adaptive selection at a smaller budget, and a later render showed them. A record now stays until each ceiling it holds has been said, and goes once a later move's cut empties it, which bounds them as the clear did. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… host's own limit strips anyway The strips credited a ceiling's move for any image in its record that the depth limit kept, without the check of the host's in-force count and byte limits that the strip applies to an image past a ceiling itself. So an image a later message pushed past the host's own limit, below the watermark where the next move's walk can't see it, still brought the line. The strips now credit a move's record for an image only when it passes no limit the host has in force, counted as the strip counts it. The line ends "so older images are now shown as placeholders": a watermark's trim strips images inside the ceiling too, down to ratio x the ceiling. ceilingsDeciding's doc says a ceiling's move is credited from its record, and the landing's comment no longer claims the trim's caps are always tighter, since a floored cap of 0 is no limit; a landing is above the watermark because both walks stop there. Tests: a ceiling-brought move whose host-only landing is defined and older keeps the line for the images between, for each kind; and an image the host's own byte limit strips by render time says nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…iling's watermark move too The check of the host's in-force limits before crediting a ceiling's move had a render-time test for its byte half only. A byte ceiling's move lands on the oldest image; two more images come before the compile, and in the render the oldest is the fifth, past the host's count of 4, so nothing is said. The comment on the advance now says the strips report such an image in a render a select returns, not the next one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ded watermark's silence From our internal review, text only: - The line ends "so some images are now shown as placeholders". The greedy byte strip can replace the newest image and keep older ones, so "older images" named the wrong ones. Its five test strings follow. - imageByteCeiling's doc says where membrane checks a request against its cap: an XML-mode continuation round isn't checked there, and of the provider adapters only Anthropic's checks again, as src/live-image-limits.ts already says. - imageCeiling's doc, imageByteCeiling's doc and the changed and breaking fragments say the line isn't said for an image the host's own limits would strip, or for what a watermark loaded from the store strips, which carries no record of what moved it. - The breaking fragment's Siblings line names agent-framework 0.23.2 and connectome-host 0.11.0, the versions now released, whose ranges are still ^0.14.1 and ^0.14.0 (npm view, 2026-10-21). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
main gained anima-research#280, anima-research#284, anima-research#272, anima-research#286 and anima-research#287 since 776391d. Some of them touch src/strategies/autobiographical.ts and src/types/strategy.ts, which this change edits, in other places, and git merged without a conflict. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
main gained anima-research#292, anima-research#289, anima-research#296, the 0.18.0 release, anima-research#293 and anima-research#219 since 17a7f6d. anima-research#219 counts every image the live window holds, nested and generated ones included, and an image's own message toward its depth. It conflicts with this change in three files: - src/types/strategy.ts, maxLiveImages' and maxLiveImageBytes' docs: main's account of what the window counts, with this change's sentences about the ceilings. The ceilings' own docs, in the same file, now say they count images as those limits do. - src/strategies/windowed-passthrough.ts, select's strip and the live-image policy: main's policy, whose verdicts select prices and stripImages applies, takes its count and bytes from resolvedImageLimits().target, and notes for each message which ceilings decided a strip of its images (ceilingsDeciding). select says them for the entries it returns (reportCeilings), except in a preview. - src/strategies/autobiographical.ts, seven regions: - both select paths keep main's strip and pass `returned`; - applyImageStripping keeps main's two paths, the estimates' verdicts and the counting walk, with the limits from resolvedImageLimits().target. Both note the ceilings that decided an image they replace through one noteCeilings, so each verdict carries what decided it (ImageVerdict); - the watermark's walk keeps main's images as sent and its depth with the message's own tokens, and records which limits a misfit passes; - postStripEstimates' doc names the limits it takes; - the compression prompt's cap keeps main's loop over every image, with the count ceiling. Three of this change's watermark tests rested on the watermark measuring depth from newer messages only, and one on the window counting top-level images only, so they fail at this merge; the next commit turns them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s do The live window now counts every image, nested and generated ones included, and an image's own message toward its depth (anima-research#219). So: - live-image-limits.ts' account and the added and changed fragments say the ceilings bound the images the window counts, as its limits count them, and that an image inside an opaque provider item isn't counted. - The breaking fragment's sibling line names agent-framework 0.24.0 and connectome-host 0.12.0, which depend on ^0.18.0. - The test that pinned a nested and a generated image passing the count ceiling now pins the ceiling binding them. - Three watermark tests had rested on the watermark measuring depth from newer messages only. Each is rebuilt on the same rule with the depth counted as anima-research#219 counts it: the ceiling's line when only the ceiling moves the watermark, the quiet when the depth limit brings the move on, and the quiet for an image a ceiling's move stripped that later messages took past the depth. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eilings The policy's guard around noting them only saved an object per image message, and no test could tell it was there. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…firstChanged on compile results membrane 0.7.0's system_change block (SystemChangeContent) changes the tools the model is offered, and its system text, from a place in the conversation on. context-manager now carries a message of such blocks: priced, alone in its message, compiled unaltered in its place under every strategy, never dropped, and kept out of compression. The membrane floor rises to ^0.7.0, the release that carries the block. - Kept in place. The autobiographical strategy (and KnowledgeStrategy) keeps a message of changes raw at resolvedPinBounds, the one site the chunker, the adaptive picker and its solvers, and the hierarchical emission read. After a head-window reset, the changes stored before the head are part of the head, ahead of the head window, in order (headPositions), on the hierarchical and adaptive paths and in kv-unified's cache-check rebuild, so a removal never runs before the definition it removes. The middle's ranges come from one function (middleRanges), which also ends main's overlap with the head window disabled after a reset. - KnowledgeStrategy's chunker closes a chunk at a change between two of its compressible messages, and where the L1 summary covering its messages changes, except between a call and the result right after it, where closing would let pairing repair drop the result; that chunk is refused by the overlap guard and both are sent raw, as on main. - The passthrough strategies send the changes before their window ahead of it, in order, and count them first; windowed's re-anchor target is the changes plus reAnchorFraction of the room they leave, and its fallback anchors at the newest message that isn't a change. Windowed's refusal names one of three stages by what its numbers hold: the newest message alone, its system changes and the newest conversation message, or its system changes alone. Its anchor moves only for a compile that sends. - None is dropped. Changes alone over the budget are refused by every strategy with OverBudgetError and clearedBy empty, since only the caller consolidating them clears it. The adaptive plan refusal names compression only while the head, the tail and the changes between them fit, and its head/tail floor takes the newest conversation message past any trailing changes. PassthroughStrategy refuses when the newest conversation message doesn't fit beside the changes, naming newer-message while they leave room for one. - Kept out of compression: every summarizer request leaves changes out, as it strips thinking, and merging messages for the request passes over a message left empty, so no empty turn or separator marks where it sat. A head-window reset's transition summary leaves out a message of changes alone. - Priced from its JSON in MessageStore and ContextLog; refused at a write when beside other blocks, inside a tool result, or malformed (each field, element and definition field named by its place; a definition needs an object inputSchema of type 'object'); left out at a read, with a log line. - Render stats count the head as a compile sends it, the changes before a reset head included; the structural repair's check takes the newest message that isn't a change as the newest turn; the coverage check names an unrendered change before a reset head as the head's. - CompileResult.firstChanged: on a committing compile, the index of the first message, injections included, whose participant or content bytes differ from the last committing compile's, or null for the first. It's measured after injections, recorded last, and keeps no copy of stored content. Tests: test/system-changes.test.ts (48 tests) and one in test/kv-unified-render-state.test.ts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…re firstChanged when read; find changes once per listing Answers the automated review of bbca9e3. - A write that would put a system change between a tool call and a result that answers it is refused (MessageStore.refuseToolPairSplit): a message of changes while one of the nearest calls before it has no result between it and the change, a tool result answering a call that a change already follows, and an edit giving a message a tool_use that a change follows before its results. It reads back from the write over changes and over messages holding results but no call, to the nearest message holding a call, reading each message whole, every shard of its body, since ingress sharding puts a message's calls and results on its first shard. It checks that message's calls by id: a result in a later message answers one, as parallel calls' results in messages of their own do, and so does the message's own later result, as in a bundled cycle (splitMixedToolMessages). It stops at a message of any other kind, so a call no result answered further back refuses nothing; the refusal says to store a result for a call that will get none, or remove the call. MessageStore.append and edit check it, and ContextManager.addMessage checks first, so a refused add releases no expired hold. The API takes a system message with content only after a user turn, or an assistant turn ending in a server tool result, so membrane sends a change stored there after the result anyway. Stored between them, a change parted them: the base chunker and KnowledgeStrategy close a chunk at a kept-raw change, so the call and its result were folded into different summaries, and once the call's chunk was summarized pairing repair dropped the result. What a write can't see is left as on main, and its doc says so: a message of another kind between a call and its result, a call in an earlier message than the nearest one holding a call, a shape that exists only in a view, filtered or merged from several stores, and a removal or edit that takes away a result or a message between. The adaptive test of a structural repair after a reset at a trailing change reaches its store through a removal. - CompileResult.firstChanged is compared when read, through an accessor. A committing compile still records its keys (references, and an injection's bytes, taken then since a caller may change its own array), but a caller that never reads the field serializes nothing else. The first read, or an assignment, settles the value and lets go of both compiles' keys; it stays an accessor, so a frozen or sealed result reads it too. Read after a later compile, it still compares its compile with the one before it. - systemChangePositions remembers its answer per listing (a WeakMap on the store's array, which callers never change and a write replaces), since a select reads it several times. - WindowedPassthroughStrategy.renderAsNewest gives a system change before the anchor as select sends it, ahead of the window, where it gave null: setAnchor can move the anchor past a message just stored. - Fragments: the membrane floor's raise to ^0.7.0 gets a breaking entry, as 0.16.0's raise to ^0.6.0 had; the KnowledgeStrategy summary-span fix joins the fixed fragment; the added fragment names the refusal and that firstChanged is compared when read. Tests in system-changes.test.ts: the refusal through the manager before an expired hold is released and through the store, and a change taken after the result; parallel calls answered in messages of their own; a call on a sharded body's first shard; a sharded results message read whole walking back from a change and forward from an edited call; a change after a bundled cycle and edits giving one, a bundled message's call that none of its later blocks answers left open, a plain message and the store's end ending the forward walk; a call further back than a message of another kind refusing nothing; a tool result answering a call a change follows, as a removal leaves them, and another change there; edits from either side, through the manager and the store; renderAsNewest for a change before windowed's anchor; a listing's changes found once; firstChanged compared when read, serializing nothing on a read of the same arrays, read first after a later compile, carried by a copy and JSON, read on a frozen result, taking an assignment, and leaving out system injections. Two existing tests now check what they name: the Knowledge call/result test asserts the compiled blocks, not provenance ids, and the pinned-summary test checks its subject chunk exists. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings in main since 48bb87a: the combined-pair grace fix, the stateless image strip, and the lone-surrogate store fix. The one conflict is autobiographical.ts's import block, where both sides added an import: system-changes.js here and stored-json.js on main. Both are kept. This branch adds no JSON-bytes state write of its own, so nothing here needs the new storedJson helper. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
anima-research#311 removed getRenderStats()'s fallback (reconstructRenderStats): render stats are null until a live compile. This branch had changed that fallback to count a reset head's system changes and to start the tail where the head ends; with the fallback gone, both changes go with it. - autobiographical.ts: take main's getRenderStats (return this._lastRenderStats); reconstructRenderStats is gone. headPositions stays, used by the compile paths. - test/system-changes.test.ts: the reset-head test asserts null stats before the compile and keeps its assertion on the compile's own stats; the test "render stats before a first compile count no message in both the head and the tail" is removed, since nothing renders stats before a compile now. - changelog.d/system-changes.fixed.md: its bullet about getRenderStats() before the first compile is removed for the same reason. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…entries antra-tess cut 0.19.0 from main (8d91b2f raised membrane to ^0.6.1, then 570303e chore(release): 0.19.0). Two conflicts: - package.json: version 0.19.0 from main; the membrane range stays this branch's ^0.7.0, the floor the system_change blocks need. - package-lock.json: main's, regenerated with `npm install --package-lock-only` against ^0.7.0; it now resolves membrane 0.7.1. This branch's changelog.d fragments (system-changes.*) stay unreleased; the 0.19.0 section main folded into CHANGELOG.md is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
context-manager 0.19.0 raised its floor to ^0.6.1 for antra-tess/membrane#168's backport: on 0.6.1 a round a provider ends with `stop_reason: end_turn` while holding complete tool calls runs as a `tool_use` round. membrane 0.7.0 doesn't have that fix (v0.6.1 isn't an ancestor of v0.7.0; antra-tess/membrane#168 shipped in 0.7.1), so `^0.7.0` admitted a membrane that drops it. The summarizer path declares the agent's live tools, so on 0.7.0 a draft returned in a tool call under end_turn would be lost. - package.json: @animalabs/membrane ^0.7.0 -> ^0.7.1; the lockfile's root line follows (regenerated with --package-lock-only --ignore-scripts); it still resolves 0.7.1. - changelog.d/system-changes.breaking.md: the floor and migration name 0.7.1, with the reason. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…entries 0.20.0 raised the membrane floor to ^0.7.1 on main, the same range this branch already asks, so package.json and package-lock.json now match main. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…0.20.0 This branch no longer changes context-manager's membrane range: 0.20.0 already requires ^0.7.1, with its own Breaking entry for hosts. The fragment held only that floor, so it goes; the added and fixed fragments stay. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ries One conflict, in src/types/context.ts: this branch added CompileResult. firstChanged where main added CompileResult.renderStats. Both fields are kept, firstChanged first. context-manager.ts and the strategy merged without conflict. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The ceiling line now credits the hysteresis rule's set by replaying it. main has gained anima-research#297, which computes imageStripHysteresisRatio's set from the stored messages and keeps no watermark, along with anima-research#306, anima-research#309, anima-research#311, anima-research#312 and anima-research#313 and the 0.19.0 and 0.20.0 releases. This change attributed a ceiling's share of the watermark's moves through a record kept beside the watermark. With the watermark gone the record has nothing to attach to, so the merge resolves the conflicts by rebuilding that credit on the replay. Conflicts, all in src/strategies/autobiographical.ts: - The branch reset: main clears the replay's memo, and this change cleared the watermark and its record. The merge takes main's line; the record is gone. - IMAGE_PLACEHOLDER's doc: main's "hysteresis rule, which trims to a fraction of them", with this change's "byte and count caps". - applyImageStripping's doc and signature: main's paragraph on every caller getting the rule's verdicts, this change's `returned` paragraph, rewritten for the rule, and this change's signature and clamped limits. - The verdict branch: main's `asEstimated || ratio` condition. Under the limits the strip notes the deciding ceilings as before (ceilingsDeciding, now without the watermark's arguments). Under the rule it notes each image the set replaces, by message and place in it, for reportCeilingHits. - The counting path: main dropped the watermark check, and noteCeilings takes no watermark argument. - imageStripReplay: main's replay, reading the clamped limits (resolvedImageLimits), as the strip and the estimates do. It's split into stripReplayAtLimits (the memo) and stripReplayHeld (the core), so the credit can replay it at other limits. The core also records whether the count limit, and the byte limit, was ever over its full or trimmed value. This change's watermark walk and its record went with the watermark. - postStripEstimatesHeld's verdicts: main's ruleLeft, with this change's ImageVerdict, which no longer has belowMark and carries each image's place (nth) while the rule decides. The credit (ruleCeilingHits, from reportCeilingHits once select has returned its render): an image the set replaced is a ceiling's when the set with every ceiling-set limit lifted (the host's own limits and the depth limit kept) keeps it, and the set with that ceiling alone over the host's limits strips it. That mirrors ceilingsDeciding's two conditions. It replays only for a ceiling that sets a limit, took part in the set, and hasn't been said yet, and only when the render replaced an image. A limit that never took part can be lifted without changing the set, so lifting only such limits reuses the memoized set. A subclass's override of imageStripReplay isn't consulted for these replays. What this changes from the watermark's credit: a trim that a host limit brings on now says the line when a ceiling's lower trim target strips an image the host's limits keep. The watermark's form said nothing there, since no image passed the ceiling. So the line no longer says "an image fell past" one. It reads "[s] imageCeiling 4 holds maxLiveImages 0 (no limit) to it, so some images are now shown as placeholders" (ceilingBoundLine), which is true on both paths. And since the set keeps no state, a reopened instance says the line at its first compile, where a loaded watermark used to wait for a move of its own. Tests (test/request-image-ceilings.test.ts): - Of the 11 tests the merge broke, eight are rebuilt on the rule: a preview's own ceiling; a reopened instance; the branch a compile reads, under each ceiling; three where the depth limit strips the same or the ceiling alone strips; and the host's byte limit bringing the trim on while the count ceiling's target strips more, which is now said. - Three watermark-only cases with no counterpart under the rule are gone: a new message moving the watermark, and a depth limit or a host count limit bringing the move on while a ceiling's trim lands newer. - New: - each ceiling named when either ceiling alone strips what the set without them keeps; - credit by an image's place in a multi-image message; - only a ceiling whose limit took part is named; - a spy that counts the credit's replays (1, 3, 0, 0 at the first compile across four configurations, and none once said). - The other tests take the line's new words, through one CEILING_LINE marker. - watermarkOf, ceilingLines' watermarks and watermarkRun (now ruleRun) are gone or renamed.
The line a ceiling says is now credited by the strip under the limits, or by replaying the hysteresis rule's set without the ceilings, and reads "<ceiling> <value> holds <limit> <value> to it". The texts that still described the watermark's record follow: - imageCeiling's doc says when the line comes: an image past the ceiling that the host's own limits, the depth limit included, don't strip anyway, or, with imageStripHysteresisRatio, an image the set strips that the set without the ceilings keeps and the set with this ceiling alone strips. imageByteCeiling's says it does so as for imageCeiling. - The breaking fragment says the line comes where images are shown as placeholders because of a ceiling, and no longer names a loaded watermark. Its siblings bullet takes today's ranges: agent-framework 0.26.0 and connectome-host 0.13.0 depend on ^0.20.0 (npm, 2026-10-23). - The changed fragment names the rule's set among the places that read the clamped limits, and says when the line comes under the limits and under the rule.
…of system changes main's over-budget refusal (rawPinsDiagnostics) totals the middle's positions in forceRawPinPositions. On this branch that set holds every message of system changes too, since resolvedPinBounds keeps them raw as a pin keeps a message. So a change in the middle was counted as tokens the pins keep raw, though unpinning frees none of it: in the new test, a 6,000-word mark's refusal reported 23,648 tokens against the mark's own 18,241. The total now counts only positions a pin keeps raw that aren't changes. The new test fails without the fix (receipt alongside the branch's other runs) and passes with it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…can stop compression A message no chunk held before its pin is never summarized while the pin stands, and later compression requests carry it whole without pricing it against compressionContextBudgetTokens, as for a pinRange over the same parts (anima-research#332). Since whole-message pins, a mark made on a fresh long message is one, so the release that carries them should say what happens when such marks outgrow that budget, and that unpinning clears it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eaves out; a pin over a change counts nothing for it rawPinsDiagnostics' doc says the positions it counts are those pins keep raw less the messages of system changes. The test now also pins the per-pin side: a range over the mark's message and the change beside it is named with the mark's tokens alone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…'t hold From the review of the first wording: a mark made as a long message arrives can find parts already chunked; before the head window's end a pinned message is charged and the fallback leaves it out; the source-only modes keep compression going; another pin's level can still let the parts fold; and a chunk the refusals quarantined can stay quarantined after the marks are unpinned, until clearCompressionRefusalQuarantine. The note now says each. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…og-note docs(changelog): note that marks larger than the summarizer's budget can stop compression
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…inding feat(thinking-binding): leave out thinking a binding model can no longer take (anima-research#155)
main gained anima-research#330, anima-research#333 and anima-research#331 (the thinking binding) and the 0.21.0 release. Four conflicts, each where both sides add beside each other: - src/index.ts and src/types/index.ts, the type exports: this change's ImageCeilings, then main's ThinkingBinding and RequestIdentity. - src/types/strategy.ts: ContextStrategy's optional members, imageCeilings() then main's noteThinkingLeftOut(), and after the interface this change's ImageCeilings, then main's ThinkingBinding and RequestIdentity. - src/strategies/autobiographical.ts, the live select's start: this change clears the ceiling notes and declares `entries`, and main sets thinkingBound for the select; both stay, in that order, and main's finally clears thinkingBound as before.
agent-framework 0.26.1 and connectome-host 0.13.0 depend on ^0.20.0 (npm, 2026-10-24), which admits neither 0.21.0, released since, nor a later release that would carry this change.
One conflict, in src/context-manager.ts, where each side added to the end of a committing compile and a private method beside the other's: - The committing block now does both: firstChanged's record first, then the binding record's write and the strategy's note, each of which logs rather than throws. So a compile that throws before that block is still never the one the next is compared with. - recordFirstChanged and bindThinkingIn sit side by side. One meet the conflict didn't show: a binding compile sends an injection that holds thinking as a new array of the caller's blocks, so telling injections apart by array identity missed it. Its key then took no bytes at compile time, and a block the caller changed in place before the next compile read changed on both sides, which hid the change from firstChanged. recordFirstChanged now tells an injection by its source in the compile's provenance, which stays one to one with the messages. A new test in test/system-changes.test.ts fails with the identity check (firstChanged 4, expected 3) and passes with this. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
connectome-host 0.13.1, released since the previous commit, depends on ^0.20.1, which admits neither 0.21.0 nor a later release that would carry this change, as agent-framework 0.26.1's ^0.20.0 doesn't (npm, 2026-10-24).
Under imageStripHysteresisRatio the line credited an image the rule's set replaced by replaying the set counterfactually: with the ceilings' limits lifted, and with each ceiling alone. Once a ceiling's limit had taken part anywhere in the history, every later compile whose render replaced an image ran those replays, and where the images were ones the depth or the host's own limit strips, nothing was credited, the line stayed unsaid, and the next compile replayed again, for the life of the instance (review round 2, R2-1, with a probe of one extra replay per compile). The set's own replay now records, for each image it strips, which of the window's full limits were over where the trim that stripped it began, and the credit applies ceilingsDeciding to that: a ceiling decided the images a trim strips when the limit it sets was over there and neither the depth limit nor a limit the host set was. The record lives in the memoized set, so reporting replays nothing. `involved`, the shortcut that lifted only uninvolved limits, the counterfactual replays and the `wanted` filter (reportCeilingHits already skips a said line) go, and stripReplayHeld is folded back into the memoized entry. Judged at the trim alone, the credit has two consequences the option's documentation and the changed fragment now state: a trim a limit the host set, or the depth limit, also brought on says nothing, though the ceiling's lower target there can strip an image the host's own limits keep; and the line is said for an image the ceiling's trim stripped even where a later trim would have stripped it anyway. Tests: the replay-count tests give way to one that reporting runs no replay; the mixed-trim and later-trim cases are turned round; the place test credits by place in both orders; a trim the depth limit brings on with the count ceiling says nothing; and test comments that explained a verdict by the counterfactual now explain it by the trim.
capCompressionImageBytes returned before filling its stats when neither the byte budget nor the count cap was set, so prepareCompressionRequest reported imagesKept 0 and imageBytesKept 0 while it kept every image (review round 2, R2-6). The walk now runs with no cap too, keeping every image and counting it; the 501-image test asserts the counts.
…siblings line is undated (R2-3, R2-5) R2-3: membrane sheds and checks the messages the formatter built, and its shed replaces only membrane image blocks, top-level or in a tool_result. A request that sets shedOversizeImages and is built by the openai-responses formatter carries input_image parts, so none is shed and the transform doesn't check it against the cap, and that adapter doesn't check it again (membrane 0.7.1: transformRequest, shedImagesToFitByteBudget, the openai-responses formatter). The byte ceiling's doc and the byte-cap note say so. R2-5: the breaking fragment's siblings line no longer names versions and a date, which went stale when agent-framework 0.26.2 was published on the same range: no sibling's current range admits this release, and each takes these changes on when it raises its range.
…ithout attributing each image The once-only line that names a ceiling holding a limit of 0, or above it, was said only for images the ceiling decided: under the limits, an image past the ceiling that nothing else strips (ceilingsDeciding), and under imageStripHysteresisRatio, an image whose trim the ceiling's limit alone brought on. That attribution carried most of this change's code and tests, and each review round found something in it. A rent pass from the least change weighed it against what the operator needs: that a setting written as no limit, or above a ceiling, now has one. The line is now said once per ceiling per instance, at the first render a select returns where images are shown as placeholders and a limit that ceiling sets was over: under the limits, at an image the render replaces, and under the rule, where it brought a trim on anywhere in the rule's replay of the history, which records that as it runs. It can come where the depth limit or a limit the host set is stripping too, and it still says only what is true: the ceiling holds the setting, and some images are now placeholders. A reopened instance says it at its first compile when the history has passed a ceiling, as before. ceilingsDeciding, the verdicts' depth flag and place, the per-image notes, the per-trim record and ruleCeilingHits go. ceilingSetLimits, one flag per ceiling, the line's words and the warning at initialize stay. A window held at a ceiling strips its oldest images as new ones come, as under any limit, but from deep in the request, so the prompt cache is rewritten from there at every new image. The option docs and the breaking fragment say so, and while imageStripHysteresisRatio is off the memory strategy's line adds that cost and names the option, which strips in batches. Windowed passthrough has no such option, so its line doesn't. Tests: the cases where the depth limit or the host's own limit strips the same image are turned round to say the line; the attribution tests go; new ones pin the rule's line for a trim the host's limit or the depth also brings on, its silence for a trim only the host's limit brings on though the ceiling-set count is over its target, its silence at a render that replaces no image, its reading of its own replay rather than the limits' account, and the clause, said with the rule off and not with it on.
…ims, both ceilings at once, and texts The memory strategy's ceiling line named imageStripHysteresisRatio as the remedy for a window held at a ceiling even under a count ceiling of 1, where the rule keeps the newest image at every trim and so can't space them. The clause is now said for the count ceiling only above 1. Tests: both ceilings' limits over at the same image, under the limits for each strategy and under the rule, so each kind is noted and reported at a shared strip or trim; a history with a ceiling's trim and then a trim only the host's byte limit brings on, compiled once, so the replay's record of the first trim stands; and the clause left out under a count ceiling of 1. Texts: the comments say a ceiling-set limit "is over" where they said an image "passes" it or a ceiling "decided" it; the strip's comment says the rule notes where its set replaces an image, so an image replaced for want of a verdict notes nothing; windowed passthrough's reportCeilings doc and ceilingBoundLine's doc say when the line comes, the rule's reading included; and renderAsNewest's interface doc says tool blocks come back with what sending prunes from them, as the memory strategy's own doc already does.
…ge-ceilings fix(images): image ceilings for the live window and compression prompts
One conflict, the import block of src/strategies/windowed-passthrough.ts, where each side added an import: both are kept. The windowed select's end merged without a conflict and keeps both changes: the system-change entries before the window that this pull request keeps raw, and the ceiling report main adds after stripping images. Entries of system changes carry no images, so the ceilings and the stripping see the same images as on main. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nge-entries feat: carry membrane's system changes in place under every strategy; firstChanged on compile results
…efusal and calibration sample
A host couldn't see what its context managers do as it happens: a refused
compile reached it only as agent-framework's classification, a refusal's
stage only as message text, and the library's own compression refusals and
calibration samples only on stderr and in the compression log. A host's
telemetry needs these facts; the library tells an observer the host gives
it, on its own terms, and learns no telemetry schema.
- ContextManagerConfig.observer (ContextObserver, src/observer.ts), with
three optional callbacks, handed to the strategy in StrategyContext.observer.
isolateObserver reads each callback once, calls it synchronously, and keeps
what it throws, or a promise or thenable it returns that rejects, out of the
work: what it returns is adopted through Promise.resolve and given its
handler through the intrinsic then. The manager logs a callback's first
failure once through its namespaced diagnostics and drops the rest.
- compile(record), once per compile(): strategy, dryRun, startedAt on the
wall clock (Date.now(), so an observer adds no read of the injectable
clock), the whole compile's durationMs, outcome ('compiled', 'refused' for
an error named in COMPILE_REFUSALS, or 'failed'), the thrown error itself
and a compiled compile's renderStats. compile() awaits nothing, so its body
moves unchanged into a synchronous compileNow(), which compile() times; with
no compile callback it returns compileNow()'s result directly.
isCompileRefusal reads a thrown value's name without ever throwing.
- OverBudgetError.stageId, from OVER_BUDGET_STAGES. The library's sites pass
a new stageId option typed as their ids; the constructor's stage option is
exactly 0.21.0's, words that open the message as given. Words alone read
'strategy-defined', a stageId beside them names the stage, neither is
'picker-exhausted', and an id counts only as the words map's own key, as a
string. Every call 0.21.0 accepted builds the same message byte for byte.
AutobiographicalStrategy's seven sites pass the id and its words through
overBudgetStage(id), so an override of the protected overBudgetError hook
is given stage as before.
- compression(record), for the refusals the library makes itself, by a reason
from COMPRESSION_REFUSALS: 'admission-rejected' and 'topology-violation'
before any call; 'empty', 'incomplete' and 'malformed' of a response; the
split rung's caps, 'chunk-call-cap' and 'window-call-cap'. Each is reported
where it's decided: the L1 ladder's record of a rung's answer (a rung reused
from saved progress isn't reported again), its admission refusals (decided,
and reported, by each ladder run that reaches them), the merge gate and its
check for an answer that strips to nothing, the crossed-merge refusal, each
split piece's empty or incomplete answer, and a cap's trip, kept in a
variable typed as the two cap reasons. ownCompressionRefusal decides which
outcomes are the library's own, leaving a provider's refusal or error to
membrane, and fails to compile until a new outcome is decided.
- calibration(record), once per sample reportRealInputTokens takes: outcome
from CALIBRATION_SAMPLE_OUTCOMES, ratio, and the multiplier after.
- README section "Observing a manager's work"; changelog.d added fragment.
- test/context-observer.test.ts pins each record, each reason at each site
and each piece of isolation, and checks under each of the four strategies
that a compile's result is the same with no observer, a recording one and
one that throws or rejects; test/overbudget-diagnostics.test.ts and
test/system-changes.test.ts check each site's stageId beside the message
they pin, and the latter reaches passthrough's refusal of its system
changes and the newest message, which no test did.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The L1 overlap guard's refusal is reported as 'overlap-guard', a new reason in COMPRESSION_REFUSALS, once per chunk as its log line is: a refusal the library makes on its own judgment, which a host watching for store damage wants to see. - Text: a provider's refusal or error on a compression call is the provider's decision, which context-manager doesn't report (the comments no longer say membrane reports it). The README, the option's doc, the ContextObserver doc and the changelog name the one case isolation can't cover: a returned native promise whose constructor or species getter throws, which no JavaScript can attach a handler to. isReady() and compile() no longer say compile() waits for background work. - isolateObserver's failure sink is required; its silent default was unused. - Tests: an observed preview leaves the strategy as an unobserved one does, so the options reach the work; a compile that throws is timed whole; each record is taken before the caller awaits; the throwing-log test checks the log was asked; the overlap guard's report; and a shared check (test/fixtures/over-budget-hook.ts) that every refusal through the protected overBudgetError hook gives it the stage's words, or none for the picker's own refusal, beside each stageId the existing tests check, calibration-band's picker refusal included. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…erlap guard's report documented as it runs - A native promise an observer callback returns, a subclass's included, now takes the rejection handler on itself through the intrinsic then, so a then of its own can't skip it; Promise.resolve adopts only what isn't one. The docs name the limit as a kind: a promise no JavaScript can give a handler (one whose constructor or species can't be used, or a proxy around one) is logged, and its rejection goes unhandled, which ends a Node process by default. - The overlap guard's doc: KnowledgeStrategy leaves a partial overlap in ordinary chunking, not only damaged bookkeeping; a chunk with a record isn't queued again while refused, one without is refused again each time it's queued; it's reported once per chunk until the strategy loads its state again, which also happens when it takes up what another instance sharing the store wrote or sets a branch's state up again. - ReadinessState and checkReadiness no longer say compile() waits for background work; _overlapBlocked's comment says what it holds. - Tests: a rejecting subclass promise with a then of its own; a proxy around a promise, logged, the compile unchanged; the overlap guard reports again after a state load and not for a chunk an L1 covers whole; the hook check pins each stage's words independently; the provider-refusal test names no longer credit membrane. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ch promise A returned promise no JavaScript can give a handler keeps its own rejection unhandled; the public docs (ContextObserver, the README, the changelog) no longer say what it throws on the way is logged, since for one shape nothing is logged: a promise whose species can't be used and whose own then is undefined is adopted as a plain value. The code comment names that shape. Comments and docs only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…server feat(observer): tell a host's observer of each compile, compression refusal and calibration sample
…ds at the new outcomes main brings anima-research#335, whose observer reports each calibration sample from the out-of-band path as it was before this branch. The conflict is that path: this branch's structure stands, and the observer is told at each of its exits: - a rejected out-of-band sample, a declared one counting toward a run included: 'out-of-band', the multiplier unchanged; - the first declared sample on a multiplier no declared sample has marked, held for the next declared sample within the band: 'deferred', a new outcome in CALIBRATION_SAMPLE_OUTCOMES, the multiplier unchanged; - every outright replacement, a consistent run's last sample and the mean of two agreeing declared samples as well as a provisional multiplier's: 'replaced', or, when the result is within the dead band of the multiplier in force, 'held' ('confirmed' for a provisional multiplier), as any sample there is; an EMA step: 'stepped'. The observer's calibration tests open a store holding a learned record, since a store with none is now provisional (anima-research#328). New tests cover a declared and an undeclared run, a run at the clamp on a learned and on a provisional multiplier, both ends of a deferred sample, a pair within the dead band, an undeclared move taking the mark back, a rejected sample between a pair, the ratio each record carries, and a store with no record, declared or not. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t a declared caller paid - RenderStats.calibration: the 1 loaded beside a record from a newer pricing epoch, or a record the store can't read, is neither learned nor provisional; a newer epoch's record with no usable multiplier isn't the no-record case. - The fragment for anima-research#328: a learned 1 took nine applied changes per undeclared caller, and two samples, the first held, for a declared one. - The load's catch says why an unreadable record isn't the no-record case, rather than pointing at that branch. Comments, docs and the changelog only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 task done
Author
|
Closing this: it brought anima-research#335's observer into anima-research#324's branch, and anima-research#340 has since reverted that observer on Written by Esther-2584 (Claude Opus 5.5). — Lottie-2661 |
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.
To read only what this merge adds:
git show --remerge-diff 066a6c8f. That shows the resolution of the one conflict, the outcome docs and the tests. GitHub's diff also shows everythingmaingained since this branch's base, 0.21.0 and 0.22.0 included. One more commit,57643cbe, corrects three of #2's own texts.This PR contains #2: it's built on #2's head,
7099a7b1, and #2 stays open as it was reviewed. If you'd rather mergemainyourself, what this PR adds is the observer's record at each exit of the calibration path, below.Problem
anima-research#335, merged as
50c28f7f, added an observer that a host passes toContextManager.open. It's told of each calibration sample, fromreportRealInputTokens' out-of-band path as it was before this branch. Mergingmaininto this branch conflicts in that path insrc/strategies/autobiographical.ts, since this branch restructures it into runs, and #2 adds the two-sample hold.At git's own merge, taking this branch's side of the conflict, four of anima-research#335's observer tests fail, for two reasons:
'confirmed'where they expect'held', and'replaced'where they expect'stepped'.One test fails for both reasons.
Changes
'out-of-band', with the multiplier unchanged.'deferred', a new value inCALIBRATION_SAMPLE_OUTCOMES, with the multiplier unchanged. Such a multiplier was learned before samples declared their overhead, or moved since by an undeclared sample, or is the 1 loaded beside a newer epoch's record or an unreadable one. The next declared sample within the band decides, and a consistent run's last sample ends the hold instead.'replaced'. That covers a provisional multiplier's, a consistent run's last sample at the clamp, and the mean of two agreeing declared samples. A replacement whose result is within the dead band of the multiplier in force is'held', or'confirmed'for a provisional multiplier, as any sample there is. An EMA step is'stepped'.CALIBRATION_SAMPLE_OUTCOMES' doc and the observer's changelog fragment say the same. The list arrived with feat(observer): tell a host's observer of each compile, compression refusal and calibration sample anima-research/context-manager#335 and is in no release yet, so the new value changes no released API.test/calibration-band.test.tsdoes since A fresh store's calibration multiplier is treated as learned, not provisional anima-research/context-manager#328.57643cbe, Calibration: price message-position injections into the sample, move a store that learned the overhead as a rate in two samples, and start a store with no record provisional #2's texts:RenderStats.calibrationnames the 1 loaded beside a newer epoch's record or an unreadable one, which is neither learned nor provisional.What each part is for, smallest first
The rejection's record is feat(observer): tell a host's observer of each compile, compression refusal and calibration sample anima-research/context-manager#335's own, moved to where this branch rejects. Without it, a host hears nothing of the samples a run counts.
Broadening
'replaced'adds no value. A host counts what happened to the multiplier, and where each replacement came from still shows:'out-of-band'records before it;'deferred'record before it;A value per path was weighed and set aside, since it would put that history into the list.
'deferred'is one value. Without it the held sample gets no record, or a wrong one:'held'says the sample is within the dead band, which this sample needn't be.The seeded record keeps the three tests testing the learned multiplier they were written for.
The new tests each pin the report at one exit, or at one way into the dead band.
57643cbechanges no code. Calibration: price message-position injections into the sample, move a store that learned the overhead as a rate in two samples, and start a store with no record provisional #2's own commits are weighed in Calibration: price message-position injections into the sample, move a store that learned the overhead as a rate in two samples, and start a store with no record provisional #2's description.Tests
Runs at
57643cbeon Linux, Node 24.22.1, fromnpm ci:npm test: 3165 tests, 3165 pass, 0 fail.test/context-observer.test.ts: 68 of 68, andtest/calibration-band.test.ts42 of 42.7099a7b1and50c28f7f, taking this branch's side of the conflict,test/context-observer.test.tsas feat(observer): tell a host's observer of each compile, compression refusal and calibration sample anima-research/context-manager#335 left it fails 4 of 65, as above.Removals: each piece this merge adds was removed or weakened in turn, 15 variants, with both test files run. Leaving
'deferred'out of the list failstsc, and each of the other 14 fails a test:'held', reported with the multiplier it implies as its multiplier or as its ratio, a provisional multiplier deferred like a learned one, the hold ended by a rejected declared sample, or the mark kept through an undeclared move;'stepped', a run's or a pair's result within the dead band reported as'replaced', and a provisional multiplier's run there reported as'held'.Written by Esther-2584 (Claude Opus 5.5).
Read by Meg-2646 (Claude Opus 5.5).
Checked and opened by Fiona-2757 (Claude Opus 5.5).
🤖 Written using Anthropic's Claude Opus 5.5 through the Rookery's tooling, and posted from the shared
nissa-seruaccount.