Skip to content

fix(pairing): send a tool result that answers a call in its own message - #365

Merged
nissa-seru merged 8 commits into
anima-research:mainfrom
nissa-seru:desmond/bundled-tool-cycle-pairing
Oct 29, 2026
Merged

nissa-seru merged 8 commits into
anima-research:mainfrom
nissa-seru:desmond/bundled-tool-cycle-pairing

Conversation

@nissa-seru

Copy link
Copy Markdown
Contributor

AutobiographicalStrategy and KnowledgeStrategy now send a tool result that answers a tool call earlier in its own stored message. At 3383f8dfd they sent a stub for the call in its place, and the result reached the request nowhere. Pairing repair (enforceToolPairing) judged such a message whole, so it took the result for one that answers no call before it. It now cuts such a message into parts, repairs the parts as before, and folds them back into the one message. A compile holding no such message is repaired as before. Callers see three more changes. toolResultMaxLastN now counts such a result, compile provenance reports such a message complete, and a store holding one discards its calibration multiplier (pricing epoch 6) and sends different requests after the upgrade. At this head the four changed test files and npm test pass; with 3383f8dfd's src, 76 of npm test's 3,573 tests fail, all in those four files.

Problem

A probe at 3383f8dfd (a test file added to a clean checkout, not part of this change) stored Claude [text, tool_use look-1, tool_result look-1, text], mid-conversation or last, and compiled it at 200,000 tokens:

  • The hierarchical, kv-unified and flat-profile configurations of AutobiographicalStrategy sent Claude [text, tool_use look-1, text], then user [stub for look-1]. The stub answers the call, the real result is nowhere in the request, and the text after the result goes before the stub.
  • PassthroughStrategy sent it with its result, as compile()'s split (src/normalize-tool-messages.ts) leaves it: Claude [text, tool_use] | user [tool_result] | Claude [text].
  • The same content as a User message went out the same way on the three configurations.
  • Compile provenance reported the message complete: false, missing: ['content'].
  • A call and its result stored in messages of their own sent the real result, with no stub, on all four.
  • The new test file's cases fail the same way at 3383f8dfd on KnowledgeStrategy's three configurations (under Tests).

Who stores this shape, as context-manager's own doc says it (src/normalize-tool-messages.ts:7–9, :30–33): "claude.ai exports bundle the entire tool cycle into a single assistant message", and connectome-host's importer "already splits at ingest time", so compile()'s split "handles legacy sessions imported before that fix landed". compile()'s split came in 05d4c8af7 on May 16, "runtime tool_result splitting for bundled cycles", and pairing repair's validator in 10160ac5d on July 7.

Changes

  • Pairing repair cuts a message that holds a cycle of its own. cycleParts (src/strategies/autobiographical.ts:2086–2107) makes a part of each run of tool_result blocks that answers a tool_use earlier in the same content that no result there has answered yet, and of what follows it. enforceToolPairing (:19577–19616) repairs the parts with the old body, now repairToolPairing (:19618), and folds each message's parts back into its one entry in order. What the repair placed between two parts, a stub or a result moved up, is folded into the message at that place (:19607–19611). When no entry is cut, it calls repairToolPairing on the entries as they are (:19588–19591).
  • A part the repair empties adds nothing to its message (:19637, :19755). A whole entry the repair empties keeps 3383f8dfd's placeholder.
  • The look-ahead for a displaced result counts a cut message as one entry (relocateOrDropMissingResults, :19732–19739), so a cut moves no result out of its reach.
  • toolResultMaxLastN counts such a result as answering its call. answeredToolCalls (:692) lets a call wait from right after it. The option's doc (src/types/strategy.ts:2189–2191) said "A result answers the earliest call with its id, in an earlier message, that no result has answered yet", and now says "A result answers the earliest call with its id before it, in its own message or an earlier one, that no result has answered yet". Such a result answered no call before, so it was never replaced, and an older result of the same tool can now get its marker.
  • Compile provenance reports such a message complete: true where nothing else alters it.
  • Calibration pricing epoch 6 (:14934), whose predicate holds for a store holding a message cycleParts cuts (:15025). An epoch-5 multiplier is discarded at load in such a store and restarts provisional, and is kept in a store whose results answer no call of their own message (test/calibration-band.test.ts:357).
  • Docs: enforceToolPairing's, toolPruning's and answeredToolCalls' comments, and the fragment.
  • Open pull requests, by git merge-tree at 12:57 UTC on October 29: fix(shards): a sharded body keeps each block in place and goes out as given, and window edges keep a tool call with its results #310 gains four conflicted regions in src/strategies/autobiographical.ts with this change, where it merges with 3383f8dfd without a conflict. fix(reset): send store order while a reset head fills, and keep a reset head's messages whole #315, chore(deprecate): ContextInjection and compile(injections) #140, RenderStats.nesting; the kv-overlap warning stops claiming a double render #130 and feat: folding-strategy migration utility (kv-stable ⇄ kv-unified) #94 conflict with this change in the same files, with as many regions, as they do with 3383f8dfd, and feat(presentation): a window per manager, which the strategy view reads (#291 step 1) #350 has none.
  • Tests: test/tool-cycle-in-one-message.test.ts is new, with 14 cases on each of six selects (AutobiographicalStrategy and KnowledgeStrategy, each hierarchical, kv-unified and flat-profile) and two look-ahead cases. Each of the 84 cases on a select runs membrane 0.8.0's normalizeToolPairs on the compiled messages and asserts it repaired nothing. The two look-ahead cases call the repair on a list of entries and assert on the entries. test/calibration-band.test.ts adds the epoch-6 case. test/tool-pruning-price.test.ts turns round the two tests that pinned the old count. test/tool-pairing-invariant.test.ts's checker holds the repaired entries to the run rule the compile's split sends.

What this leaves

  • Some whole messages go out as a placeholder where 3383f8dfd sent a result. This is 3383f8dfd's rule for an entry the repair empties, applied where this change keeps or moves a real result. It happens in two shapes. In one, a later message repeats a result for a call the cut message answers itself: this change keeps the copy in the message and drops the later one, so the later message goes out as "[tool call omitted]". In the other, as in [call t0, call t1, result t1] then [result t0], the look-ahead moves t0's result up beside its call, and the emptied next message goes out as "[tool result moved during context repair]". Over 3,000 random entry lists, 197 send more such placeholders than at 3383f8dfd and none fewer, 61 of the 197 also send more real results, and no list sends fewer real results or more stubs.
  • Pairing repair pairs by entry, here as at 3383f8dfd, and leaves a result standing after other blocks of its entry for membrane's normalizer to place. A cut message carries that into its parts. In one holding a result for the previous entry's call after its own calls, as [use t1, use t2, result t0, text, result t1] after [use t0, text], the compile's split sends result t0's message between the calls and their results, and membrane hoists them. 3383f8dfd sends that message with stubs for t1 and t2 after it, also hoisted. This happened in 27 of the 3,000 lists.
  • Under kv-unified, a store holding such a message can go on to select differently from 3383f8dfd, not only send its results. In the counter harness under Tests, at 4d0e3bfe0, one generated store's resident configuration selected differently in 56 of 132 compiles, and with the harness's kv-unified acceptance calls skipped, in none. In 40 compiles, all in one kv-unified part of that harness at budget 3, the selection sends raw a region 3383f8dfd sends as summaries, and 1 to 4 more messages go out as "[tool call omitted]": results of a sharded message whose calls the adaptive render sends as text only. fix(shards): a sharded body keeps each block in place and goes out as given, and window edges keep a tool call with its results #310, still open, makes a sharded body keep each block in place.

Tests

On Linux x86_64 (Debian 13, 12 CPUs) with Node v24.22.1 and npm 11.19.1, npm ci from the lockfile installs @animalabs/chronicle 0.5.0, @animalabs/membrane 0.8.0, @types/node 22.19.15 and typescript 7.0.2.

  • At this head, 390a9f8bd, in a clean checkout: tsc exits 0, the four changed test files pass 174 of 174, and npm test's two globs (dist/test/*.test.js, dist/test/adaptive/*.test.js) pass 3,573 of 3,573.

  • Red first: with 3383f8dfd's src/strategies/autobiographical.ts and src/types/strategy.ts and this head's tests, 76 tests fail and the other 3,497 pass. They are the new file's 12 cycle cases on each select and its look-ahead case, the epoch-6 case, and tool-pruning-price's two turned-round tests.

  • The probe under Problem, at this head: on all four configurations, the cut message's result is sent with no stub and provenance complete: true, the hierarchical request is byte-identical to passthrough's, and the separately stored call and result send the same bytes as at 3383f8dfd.

  • Each piece removed, at this head on the four changed files:

    removed tests that fail
    the cut (a message is repaired whole) 74: the 12 cycle cases on each select, the look-ahead case, and the pruning count
    a call waiting from right after it (it waits from the next message) 7: the limit-0 case on each select, and the pruning count
    epoch 6 (it stays 5) 1: the epoch-6 case
    unitOf, through which the look-ahead and the emptied-part rule see a cut message as one entry 13: the past-another-result and further-result cases on each select, and the look-ahead case
    a further result starting no part 6: the next-message case on each select
    no placeholder in a part Rule A empties 6: the further-result case on each select
  • 3,000 random entry lists through enforceToolPairing, at this head and at 3383f8dfd: no placeholder inside a message and no empty entry at either. Here 6,485 real results and 5,995 stubs, against 5,075 and 7,405, with more real results in 1,140 lists and fewer in none. Membrane 0.8.0's normalizer over the same lists, at b11bd90f0, whose src is this head's, and at 3383f8dfd, synthesizes, textifies and hoists nothing here that it doesn't at 3383f8dfd, and hoists 970 times against 1,624. In 95 lists it defers one block where it hoisted at 3383f8dfd, each in a user message holding its own cycle.

  • A counter harness (not part of this change) ran 579,000 compiles over kv-unified, flat-profile and hierarchical configurations at 3383f8dfd and at 4d0e3bfe0, and again at a966e15f3, whose src differs from this head's only in comments, and whose compiles were byte-identical to 4d0e3bfe0's, compile for compile.

    • Every compile of a store holding no such message was byte-identical at both.
    • Of those whose store holds one, 14,628 differ. 14,532 send fewer stubs, and sent tokens fall in 14,600 (median 58) and rise in 28.
    • The harness's prefix breaks (a request not beginning with all of the previous request but its last message) are equal at both in every part and budget but one kv-unified part's budget 3, with 2,334 at 3383f8dfd and 2,336 here, of 5,264 checks.

Not verified

  • Real stores: every compile here was of a generated store, the probe's, the harness's or the random lists. No store from a claude.ai import was compiled.
  • Who runs on such a store: whether any deployment's store holds a message with a cycle of its own isn't known.
  • What other writers store: agent-framework and connectome-host weren't run.
  • The first request after the upgrade for a store holding such a message sends different bytes, so no run measured its cache miss or how its window moves while the calibration relearns.
  • Reused tool_use ids: no new case stores two calls with one id.
  • Platforms: every run here was on Linux.

  • Changelog fragment added: changelog.d/tool-cycle-in-one-message.fixed.md.

Desmond-2579 built the change and ran the probes, the lists and the harness. Greta-2425 wrote the 3,000-list generator, counted membrane's events over the lists, and read each commit the internal review's runs didn't cover. An internal review ran four times. Beth-2389 wrote the counter harness. Oscar-1005 made the design calls. Lottie-2661 made the runs at 390a9f8bd and wrote this description.

Desmond-2579 added 8 commits October 29, 2026 06:58
… result

enforceToolPairing's Rule A keeps a tool_result only when the entry just
before it holds its call. So in a message whose own content holds a call
and then its result (a claude.ai export's tool cycle, or a user message
carrying one), the result was dropped as answering nothing, and the call
was then stubbed, by the tail or at the next entry. compile() runs
splitMixedToolMessages for sessions imported in that shape, and membrane's
normalizer re-roles a user message's calls, but both run after the
repair, so the message lost its results in every compile of the
autobiographical family: on both selects, and in KnowledgeStrategy, which
doesn't override the repair. Passthrough never ran the repair and kept
them.

The repair now judges such an entry in parts: each run of tool_result
blocks that follows a tool_use in the same content is a part, and so is
what follows it (cycleParts). Results before any call stay in the first
part and are judged against the entry before, as they were. Rule A,
Rule B and the tail run over the parts unchanged (repairToolPairing), and
each entry's parts fold back into it in order, with any stub entry Rule B
placed between two of its parts folded in as a run of results. So a
result answers the calls just before it in its own message, a call its
own results don't answer is stubbed beside them, and the entry stays one
entry, which the compile's split then cuts as before (attribution judges
an entry by whether it covers its stored message), and compile
provenance now reports such a message carried whole where it reported it
missing content. A compile in which no
entry holds such a cycle takes the old path as it was. In one that does,
the look-ahead for a displaced result counts parts, so it reaches one
entry less for each extra part between.

Two places decided pairing the old way, and change with it:
- answeredToolCalls, which toolResultMaxLastN, the across-windows closure
  and calibration epoch 4's predicate read, let a message's calls wait
  only from the next message on, "pairing repair drops a result in its
  call's own message". They now wait from right after the call, as the
  store's callsInOrder already counts a cycle. A message without such a
  cycle counts as before; the closure already skips a pair within one
  message. toolPruning's and toolResultMaxLastN's docs are corrected.
- The calibration's estimate is the render stats' total, taken before the
  repair, so a raw message holding a cycle was priced with its results
  while the request carried stubs, and such a store learned a multiplier
  that covered results it didn't send. CALIBRATION_PRICING_EPOCH is 6, and
  its reprice predicate holds where a store holds a message with a tool
  result after a tool call in its own content: there an older multiplier
  is discarded at load and restarts provisional, which the first sample
  the band accepts replaces outright (reportRealInputTokens). Elsewhere a
  multiplier stamped 5 is kept and re-stamped.

Tests (tool-cycle-in-one-message, on hierarchical, kv-unified with the
host's policy, and flat-profile): a cycle in an assistant message and in a
user message, mid-conversation and last, is sent with its own result,
membrane 0.8.0's normalizeToolPairs synthesizes and textifies nothing, and
provenance reports the message carried whole; a call its own results
don't answer is stubbed beside the result that is; a message whose results
answer none of its calls has each call answered; under toolResultMaxLastN
0 the cycle's result gets the marker; calls and results in messages of
their own send as stored. calibration-band: an epoch-5 multiplier is
discarded in a store holding such a message, and kept and re-stamped in
one without.

Two tests pinned the old count, and are turned round in
tool-pruning-price: "leaves a result in its own call's message out of the
count, as pairing repair drops it" now counts it (under a limit of 1 the
earlier call's result gets the marker and the cycle's is sent), and epoch
4's predicate's "a result in its call's own message" case now asserts that
epoch 6's predicate holds for that store: there epoch 6 discards an older
multiplier whatever epoch 4 answers, so epoch 4's answer decides nothing.
Two corrections to the cut that lets a message holding a tool call and its
result send that result: where it cuts, and how far the repair's look-ahead
reaches once it has cut.

The cut. cycleParts cut at any run of results after any call, without
reading ids, so a message whose result answers the previous message's call
([call B, result A]) or answers nothing ([call 1, result 9]) was cut too.
The second then folded the repair's "[tool call omitted]" text into a
message the old repair sent without it, and epoch 6 repriced stores holding
only those shapes. A run of results now starts a part only when one of its
results answers a call earlier in the same content. That is the notion the
store's callsInOrder and toolResultMaxLastN's count (answeredToolCalls)
already use for a result answered in its own message, so the count, the cut
and epoch 6's predicate agree: every result the count calls answered in its
own message reaches the request, either in the part right after its call's
part or moved there by the look-ahead, which runs at that part before the
repair's orphan rule reaches the result's own part. A cut never splits a run
of results or a run of other blocks, so a raw replay form, which blocks of
one such run share, stays within one part (said in cycleParts' doc).

The look-ahead. relocateOrDropMissingResults searched 6 entries of the array
it was given, which in a compile with a cut message is the parts array, so a
message's extra parts could push a displaced result out of reach: the call
was stubbed and the orphan rule dropped the real result, where main moved it
to its call. The window now counts units, a message's parts being one unit
and every other entry, a stub entry the repair inserted included, its own.
With no cut each entry is its own unit, and the window is main's.

The fold now gathers each message's parts in order and assigns its content
once, so a message can't be emitted twice.

Tests: the new file runs on KnowledgeStrategy too, the "last" user case is
last (asserted), results are asserted whole, and it adds a message holding
two cycles, a result answering its call past another result of its message,
an orphan result folding in no repair text, and the look-ahead through a
subclass exposing enforceToolPairing. The epoch-6 reset asserts the
restarted multiplier is provisional.
The fold that gathers a cut message's parts back into its one entry pushed
the message at its first part and every entry without a message of its own
where it stood, so an entry the repair spliced between two parts of one
message, such as a stub for a call whose result is nowhere, went out after
the whole message. In [call A, call B, result B, text, call C, result A,
text, call D, result D] with no result for C, the look-ahead moves result A
up and empties its part, the repair stubs C before that part, and C's stub
was sent after D's result, so the compile's own output left C unanswered
after its call, and the request was well formed only once membrane's
normalizer had reshaped it. The fold now folds an entry that sits between
two parts of one message into that message at its place, by each message's
last part.

Tests: a message holding a call whose result is nowhere between two cycles
of its own, on each select, with the compile sending its stub in the message
right after the call's (the membrane check, which counts a stood-in or
textified result, can't see it); KnowledgeStrategy on the kv-unified and
flat-profile selects too; the unanswered-call case for a user message; the
two-cycle case asserts order and no repair rather than the split's shape;
the look-ahead reaches a result seven entries after its call and stubs one
eight entries after; the epoch-6 case keeps a multiplier in a store whose
results answer no call of their own message.
… keeps

The header of test/tool-pairing-invariant.test.ts said every tool_use is
answered only in the entry after it, and every tool_result answers only the
entry before it. Since pairing repair judges a message holding a tool cycle
of its own in parts and folds them back, a call can be answered later in its
own entry's content, and a result can answer a call before it there.
…adds nothing

Two corrections from reading the repair over random entry lists against main.

A call now waits in cycleParts until a result answers it, so a second result
for a call its message already answered starts no part of its own. Such a part
moved where pairing repair answered the message's next call: in
[call A, result A, call B, text, result A] followed by [result B], Rule B for
B fired at the further result's part instead of at the next message, which
answers B as main sends it, so the look-ahead moved that message's result into
the cut message and left it as "[tool result moved during context repair]".
The cut, the count's earliest-waiting-call rule and epoch 6's predicate now
agree for unique ids on which result answers a call in its own message.

A part the repair empties now adds nothing to its message: Rule A and the
look-ahead leave it with no content, where a whole entry they empty keeps its
placeholder. A cut message always keeps a call, so its folded content is never
empty. Before, a further result dropped by Rule A, or a result moved up to its
call, left "[tool call omitted]" or the moved-result text inside the message.

The fragment's split clause now says compile() runs the split on every
message, and an unanswered call is answered as pairing repair answers any
call: by its result moved up where the look-ahead finds one, else by a stub.

Tests, on each select: the further-result case with the next message's own
result kept there; a further result leaving no placeholder; the past-another
case asserting no moved-result text.
…e pairing checker

Tests and text only.

The further-result case adds a message where Rule A empties a part: in
[call A, call B, result A, text, result B, result B] the look-ahead moves one
result B up beside A's, and Rule A drops the other, leaving its part empty;
nothing of it reaches the message. The next-message case asserts that
message's own content, not only its result's id. The pairing invariant
test's assertPaired accepts a call answered later in its own entry and a
result answering a call earlier in its own, as its header states; its cases
hold no such entry.

Text: relocateOrDropMissingResults' doc says a part of a cut message the
move empties is left empty; the fragment's count sentence says a result
answers the earliest call with its id that no result has answered yet; two
doc paragraphs rewrapped.
…plit sends

assertPaired accepted a call answered anywhere later in its own entry, so it
passed one entry [use A, use B, result A, text, result B], which the
compile's split sends as Claude [use A, use B] | user [result A] |
Claude [text] | user [result B], B's result two messages after its call. It
now holds what the split makes adjacent: a call is answered in the run of
results right after its run of calls, or, where its run ends its entry, in
the next entry; a result answers a call in the run just before its own, or,
where its run starts its entry, in the previous entry. The header says so.
Its cases pass at this tree and with main's autobiographical.ts.
The header claimed the repair's output keeps the run rule the compile's
split sends as adjacent messages. It doesn't, at main or with this change:
pairing repair pairs by entry, and leaves a result that stands after other
blocks of its entry for membrane's normalizer to place. The header now says
that, and says that assertPaired holds these cases' repaired entries to the
stricter run rule. Comment only.
@nissa-seru
nissa-seru merged commit fdede52 into anima-research:main Oct 29, 2026
9 checks passed
tel-0s added a commit that referenced this pull request Oct 30, 2026
… raw window's messages left out, a tool cycle in one message sent with its result, compression that waits on tools kept queued, the canonical map linear, test files run at once

No textual conflicts. Read for presented mode, PR by PR. Nothing in this
range needed changing.

- #360 summaries across windows (shipped in 0.24.1): selectByWindowRule and
  summariesAcross work on the `messages` each select already reads
  (store.getAll() of the strategy view, the window once a host presents).
  Head and recent positions, pin bounds and answeredToolCalls all index that
  listing. Nothing reads the record, and nothing is stored or removed: the
  dry run goes through saveDryRunState, and summariesNotAcross clears parent
  links on copies only. Its "windows" are the select's head and recent raw
  windows, not the window index. The index decides which rows the listing
  holds, and the rule works inside that listing. demoteSplitSummaries and
  releaseRowsThatLeft write the summaries at load; this rule reads them at
  select time and writes nothing. A leaf the window doesn't hold has no
  position and is skipped. A summary still over a row presented again at the
  window's end (an open that doesn't repair: auditOnly, or 'report') is left
  out once the recent window sends that row, so the row isn't sent twice.
  No change.
- 0.24.1 release: version taken (package.json, lockfile; chronicle ^0.6.0
  kept). The release consumed summaries-across-windows.fixed.md and
  summaries-across-fallbacks.added.md, which were on main only, so no
  fragment of this branch goes. Main's unreleased fragments (#362, #365,
  #367) and the 291-* fragments stay.
- #362 canonical map linear: persistedCanonicalSummariesById groups the
  stored summaries by id. sameAuthoredSummary doesn't compare parent links,
  so unfold's copies with cleared pointers still resolve. No change.
- #363 test slim: tests and the runner script only. The files both sides
  touched (derive, fold-journal, head-window-reset,
  manager-initialization-fence, compression-head-as-of) merge with this
  branch's cases intact. No store path is shared across test files, so the
  parallel runner doesn't collide on this branch's files.
  dist/test/autobiographical.test.js, compiled from a file #363 deletes, was
  removed from the untracked build output before the runs. No change.
- #365 tool cycle in one message: cycleParts and enforceToolPairing repair
  rendered entries. answeredToolCalls and the epoch-6 predicate read the
  listing the select reads, the window once a host presents, as epochs 1-5
  do. The calibration samples come from compiles of that window, so it is
  the right set. Nothing is stored. No change.
- #367 compression waits on tools: toolsDeferredChunks and
  toolsDeferredMerge are in memory and are cleared by resetChunks, which
  every rebuildChunks now starts from. On this branch that includes
  onPresented (presentRows), onRetracted (windowLost), releaseRowsThatLeft
  and demoteSplitSummaries. So presentRows clears the marks as an add does
  on main, and the next tick marks them again. A deferral makes no call, so
  _spanLeftWindow and the merge's dropped-source check are untouched. A
  windowLost rebuild bumps the queue generation, so a deferral that lands
  after it is neither marked nor put back. presentRows, retractRows and the
  removal paths read no readiness. Windowed passthrough (tune-out's
  subconscious anchor) and passthrough keep { ready: true }. getWaitingWork
  joins the fenced queries in assertFenced beside this branch's
  generateTransitionSummary. No change.

Tests, against the linked chronicle feat/presentation edfb787 (napi rebuilt
after its merge of chronicle main 07210d4):
- Main's 64 changed test files plus on-presented, on-retracted,
  window-batches, window-index, window-index-branches, window-status,
  head-window-reset, manager-initialization-fence and derive (70 files):
  2163 tests, 2160 pass, 3 fail.
- Full suite (dist/test/*.test.js, dist/test/adaptive/*.test.js,
  --test-force-exit): 3591 tests, 3585 pass, 6 fail.
The 6 (3 of them in the targeted run) are compression-recall-curve's
"a quarantined chunk run again ... keys in another order" (its premise),
tool-pruning-price's adaptive and hierarchical "prices a tool_use input the
input cap truncates as truncated", and recall-envelope's three golden-byte
cases. Each assumes the store gives an object back with its keys sorted, as
chronicle 0.5.0 does. The linked chronicle carries chronicle main's #67 (keep
key order) and gives the keys back in the order written. All 6 fail the same
way at 8032c9b, this merge's first parent, built against the same chronicle
(172/175 and 16/19 in those files), so this merge doesn't cause them.

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

Copy link
Copy Markdown
Contributor Author

@antra-tess Flagging for your awareness this PR, which made the following changes (in brief), in AutobiographicalStrategy and KnowledgeStrategy:

  • A message holding a tool call and, after it in its own content, that call's result: the result was dropped and the call got the stub [this call's result isn't shown in this context. If you need its output and the tool only reads, call it again; you'll get what it returns now.] → the result is sent with the call.
  • With toolResultMaxLastN set, such a result now counts as answering its call, so an older result of the same tool can now become [result of tool '<name>' left out: …].

— Lottie-2661

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant