Skip to content

fix(framework): an exhausted turn that did nothing writes no [inference-failed] note - #985

Open
nissa-seru wants to merge 7 commits into
anima-research:mainfrom
nissa-seru:fix/979-inference-failed-telemetry
Open

nissa-seru wants to merge 7 commits into
anima-research:mainfrom
nissa-seru:fix/979-inference-failed-telemetry

Conversation

@nissa-seru

@nissa-seru nissa-seru commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

Why. A turn that failed before it did anything left a note in the agent's context, and every later request carried that note until a fold or a removal took it out. One resident's request held 17 of them, 8,335 characters, all from calls that produced no output.

A turn whose inference is exhausted before it got partway no longer writes an [inference-failed] note into the agent's context, and a turn that got partway keeps its note without the "change approach" advice. This is the change #979 asks for, where those 17 notes were found, with the exception your answer there makes: a turn that got partway keeps its note, since that's where the agent learns its tool calls may have taken effect.

A turn that failed before it got partway

  • base (main): it writes an [inference-failed] note into the agent's context, which every later request carries until a fold or a removal takes it out, unless it's a re-armed turn's transient failure or SUPPRESS_INFERENCE_FAILED_MARKER=1 is set.
  • head: it writes no note. Operators still get the stderr line, the failures.log record, the inference:exhausted trace and the hard-down alert, and the poison-history breaker still runs, whose [refusal-rewind] note carries the failure's reason when it sheds history.

A turn that got partway, where a round ran or a round's tool calls were started and cut off

  • base (main): its one note ends "If this recurs with the same cause, change approach rather than retrying identically…", or, for a timed-out turn, "If a reply is still owed from that turn, you can give it now."
  • head: its one note leaves out the "change approach" sentence, and keeps the timed-out turn's. The failure's cause, its status and a timeout's host-setting name stay in it, worded as on main.

The reply judge

The [assistant-persist] stderr line for a thinking-only reply, which the agent doesn't store

  • base (main): it ends "— the turn produced no output. See the refusal/inference-failed record for why."
  • head: it ends "If it was refused, see its [inference-refusal] line and failures.log record."

SUPPRESS_INFERENCE_FAILED_MARKER=1

  • base (main): it leaves out every note.
  • head: it leaves out the partway note, the only one left.

connectome-host's docs/DEPLOYMENTS.md still says the framework adds the marker so the agent learns why its turn produced nothing, which needs a change in that repository. No run used a live provider or went through connectome-host, and how a resident behaves without these notes isn't observed.

It conflicts with #952 and #361, which change the same function, and with #850 beside three lines this change removes, so whichever of each pair merges second resolves that conflict.

@antra-tess, we're letting you know because it changes what an agent reads after a failed turn: an agent whose turn failed before it did anything no longer gets an [inference-failed] note saying why it failed.

  • Changelog fragment added: changelog.d/979-inference-failed-telemetry.changed.md.
Credits

Alfred-2451 made the change and Ada-1017 made its design calls. Cedric-2714 read it and ran mutants of his own, and Cedric-2315 weighed each of its parts against what it's for. Alfred-2451 wrote this description and its why, and Myrtle-2415 wrote the opening after the why.

Details for agents

At c7a7956f59e7, whose merge base with main is aa134e75fc19.

This is the change #979 asks for, with the exception in your answer there. A turn that got partway keeps one note, and acceptance 1 is limited to turns that did nothing. A new test file drives real turns through the framework for acceptance items 1 to 5, and mutants of the change cover item 6. At aa134e75fc19 four of its seven tests fail, and at this head all seven pass. The whole suite passes at both, with 0 failures.

Problem

This addresses #979.

At aa134e75fc19, noteInferenceExhausted writes an [inference-failed] note for each exhausted turn, stored as a user row (src/framework.ts:35093–:35119), except a re-armed turn's transient failure that did nothing, and except under SUPPRESS_INFERENCE_FAILED_MARKER=1. Every later request carries the note until a fold or a removal takes it out. #979's field evidence is 17 such notes, 8,335 characters, in one resident's request, all from calls that produced no output. Our test of seventeen failed turns in a row fails at aa134e75fc19 on the second request, which carried the first turn's note between the first message and the second.

Changes

  • A turn that did nothing writes no note. noteInferenceExhausted writes the note only for a turn noteFailedTurnEnd recorded (src/framework.ts:35079 at this head). noteFailedTurnEnd records a turn when gotPartway holds: a round reached the context with its results, or a round's tool calls were started and cut off (:1322, :23782). Everything else the failure does is as before:

    • the stderr line [inference-failed] agent=… consecutive=…;
    • the failures.log record of kind inference-exhausted;
    • the consecutive-failure count and lastInferenceAt;
    • the inference:exhausted trace;
    • the hard-down alert;
    • the poison-history breaker, whose [refusal-rewind] note carries the failure's reason when it sheds history.

    A stream's inference-log row keeps its requestId, and notes already in a store stay. The reply judge reads the agent's activation preview (src/gate/reply-judge.ts:338, :373), so it learned that a turn which did nothing failed only from that turn's note, and no longer sees one. fix(gate)!: answer the reply judge's first review: refused calls, a failed last turn, pending batches, quoted participant text and too-long requests #850 tells the judge of a failed last turn itself.

  • A turn that got partway keeps one note. It says how far the turn got and the reason. The sentence "If this recurs with the same cause, change approach rather than retrying identically…" (FAILURE_ADVICE) is gone. A timed-out turn's note keeps "If a reply is still owed from that turn, you can give it now." (TIMEOUT_ADVICE, :3049), since it tells the agent what it can still do.

  • A turn that posted plain speech live always got partway, so it gets the note. In native tool mode, a round's plain speech is routed live only when the stream yields that round's tool calls (:28804). The same event counts an unflushed round (:28577), which makes gotPartway true. A round with no tool calls is delivered when its stream completes, and XML tool mode delivers speech at completion too. Explicit mode delivers the prose the model addressed at the same two points: at a round's tool-calls event (:28766–:28782) or at a text-only turn's completion (:29525–:29529).

    • test/prose-drafts.test.ts pins this. Under locus and under hybrid routing, a turn that streams plain speech and then fails with no tool round posts nothing and writes no note, and the same turn, completing, posts its speech once.
    • So every turn whose speech went out live, with a round's tool calls, keeps its note, as at aa134e75fc19. Under locus and hybrid routing, that note says the speech was handed on for delivery (:26147, :26191, :28253 set liveSpeech). Under explicit routing it doesn't, as before.
  • SUPPRESS_INFERENCE_FAILED_MARKER=1 leaves out the partway note, now the only one.

  • exhaustingRearms is removed. Its one reader decided whether a re-armed turn's transient failure wrote the no-output note, and that note no longer exists.

  • A refused turn's log line now points to records that exist. The guard that refuses to store a thinking-only reply logged "See the refusal/inference-failed record for why". A refused turn ends as a completion and never reaches noteInferenceExhausted, so the line now says that if the turn was refused, to see its [inference-refusal] line and failures.log record (src/agent.ts:2557–:2559; at aa134e75fc19, :2559–:2561).

  • Published text. README's bounds section, a new README paragraph after the failure and stop notes, and FrameworkConfig.exhaustedTurnRearmMs's doc say when the note is written. The changelog fragment gives each sentence this makes untrue, from the README, that doc, the 0.22.1, 0.29.0 and 0.31.0 entries and the [assistant-persist] line, beside what's true now.

Tests

test/inference-failed-telemetry.test.ts drives real turns through the framework with a stand-in provider:

  • three exhausted turns that did nothing leave three failures.log records and three stderr lines, and nothing in the context but the three messages (acceptance 1);
  • a framework reopened on the same store holds no note (2);
  • seventeen failed turns leave each next request's text as the request before it plus the new message, and the eighteenth answers (3);
  • a failure's category, status and provider code are in failures.log and stderr, and its requestId is in the inference log (4);
  • a turn that got partway leaves exactly one note, with no advice, and a timed-out one keeps TIMEOUT_ADVICE (5);
  • SUPPRESS_INFERENCE_FAILED_MARKER=1 leaves out the partway note.

Its streams are defined in the file, as in 24 of the suite's test files. ToolRoundThenErrorStream drives a real turn whose tool round ran before its stream failed, which the note now needs. In test/inference-failure-bounds.test.ts, whose tests call noteInferenceExhausted directly, makeHarness({ partway: true }) seeds the record noteFailedTurnEnd leaves for such a turn.

In test/prose-drafts.test.ts, a test drives a text-only turn whose stream fails where it would have completed, under locus and hybrid routing, and asserts that nothing is posted and no note is written, and that the same turn, completing, posts its speech once.

Red first: with this file added to aa134e75fc19, four of the seven fail: the three did-nothing tests, each on the notes it found, and the partway test, on the advice. The other three pass there, since they pin what stays.

Mutants of this change at this head, run against the new file, each failing exactly the tests named for it (acceptance 6):

  • restoring a row for a turn that did nothing fails the three did-nothing tests;
  • restoring the advice on the partway note fails the partway test;
  • writing the partway note twice fails the two partway tests.

A mutant that posts a locus or hybrid turn's plain speech as it streams, before the round's tool calls or its completion, fails the prose-drafts test on its first case, a failing locus turn, on the speech it posted. Cedric-2714's mutant that does so under hybrid routing alone fails it on its failing hybrid case.

Cedric-2714 ran two mutants of his own at 227878ad5, across the 15 test files that name the note, with his predictions written first. Restoring the note for a turn that did nothing failed 36 tests, the three in the new file and 33 elsewhere, each at its assertion on the note. Narrowing partway to a round that ran, so that a round whose tool calls were cut off no longer counts, failed the 4 tests that pin that case.

Existing tests. Those that asserted a note for a turn that did nothing now assert none, each after a premise that its turn failed. Where the note was their signal that the turn failed, they read stderr or failures.log instead. Tests whose subject is the note's own text now drive a turn that got partway, so that text stays pinned: its 600-character bound, its cause and reason on one line, "The call was made again", and a 408's wording.

c7a7956f59e7 removes a branch of noteFailedTurnEnd that cleared the agent's record when a turn didn't get partway. It had nothing to clear: at each of the four places a failed turn ends, noteInferenceExhausted takes and deletes the record right after, and nothing that runs between the two throws, so no record outlived the turn that set it. At this head, on Linux with Node 24.22.2, chronicle 0.6.0, context-manager 0.25.0, membrane 0.8.0 and mcpl-core 0.4.0, npm run build and npx tsc --noEmit pass, the 16 test files that name the note pass 654 of 654, and the three mutants above fail as listed.

The whole suite, as this repository's CI runs it, npm install, npm run build, then node scripts/test-as-ci.mjs, on ubuntu-latest with Node 22.12.0:

  • at this head, in this pull request's run on its merge with aa134e75fc19, whose tree is this head's: 10,361 tests, 10,298 pass, 0 fail, 63 skipped;
  • at aa134e75fc19, in main's own run: 10,351 tests, 10,288 pass, 0 fail, 63 skipped.

The ten more are the new file's seven, the new prose-drafts test, and one case for a turn that got partway in each of inference-failure-observability and inference-failure-bounds.

Not verified

  • No run against a live provider, and none through connectome-host.
  • How a resident behaves once turns that did nothing stop writing a note. The reply judge's part of that is in Changes above.
  • connectome-host's docs/DEPLOYMENTS.md (:148–:150 at its main, 1526663c18) says the framework "adds an [inference-failed] marker to the agent's chronicle so the agent learns why its turn produced nothing", and that the switch disables it. It will need to say the note is for a turn that got partway, which is a change in that repository.

…ce-failed] note

anima-research#979: every exhausted turn wrote an [inference-failed] note into the agent's context as a user row, which stayed until a fold or removal took it. A resident whose provider was down or whose context was too long gathered one per failed turn, and each was sent with every request after it. Per the maintainer's answer on anima-research#979 (issuecomment-6671382711), a turn that did nothing now writes no row: its failure is telemetry alone. A turn that got partway, that is one noteFailedTurnEnd recorded because a round ran or had its tool calls cut off, keeps one note, since that note is where the agent learns its calls may have taken effect, without the "change approach" advice.

- src/framework.ts:
  - noteInferenceExhausted writes the note only for a turn noteFailedTurnEnd recorded. The "produced no response, so nothing was sent" wording goes with the case it described.
  - FAILURE_ADVICE goes. A timed-out turn's note keeps TIMEOUT_ADVICE ("If a reply is still owed from that turn, you can give it now.").
  - exhaustingRearms goes: its one reader was the note's `!(rearmed && !ended)`, which `ended` now decides alone. The set, its three adds, its clear in cancelProviderAdmission and its delete in releaseAgentRuntime go with it.
  - The stderr line, the failures.log record, the streak, lastInferenceAt, the inference:exhausted trace, the hard-down alert and the breaker are unchanged. SUPPRESS_INFERENCE_FAILED_MARKER=1 still leaves out the note, now the partway one.
  - The doc comments that described the note say what it is now, and two comments' clauses about the old note and its advice are cut.
- src/types/framework.ts: exhaustedTurnRearmMs's doc said a re-armed turn's failure other than a transient one writes a note. It now says a failure of a re-armed turn writes one only when the turn got partway, as any turn's does.
- src/agent.ts: the empty-block sanitizer's comment no longer says such a request writes a note on every attempt. The thinking-only guard's comment and stderr line pointed to an [inference-failed] notice for a refused turn. A refusal ends its stream as a completion and never reaches noteInferenceExhausted, so they name the [inference-refusal] line and failures.log's refusal record instead. Shown by a probe: a refused turn traced inference:completed and no inference:exhausted, wrote no [inference-failed] row, and left those two stderr lines and a failures.log record of kind refusal.
- test/inference-failed-telemetry.test.ts, the issue's acceptance:
  - three exhausted turns that did nothing leave three failures.log records and stderr lines, and nothing in the context but what was said;
  - operators keep the failure's category, status and provider code in failures.log and stderr, and its request id in the inference log;
  - a framework reopened on the store holds none of them;
  - seventeen failures leave each next request as what was said before plus the new message;
  - a turn that got partway leaves exactly one note, with no advice;
  - a timed-out partway turn's note keeps TIMEOUT_ADVICE;
  - SUPPRESS_INFERENCE_FAILED_MARKER=1 leaves out the partway note.
- Tests that pinned a note for a turn that did nothing now pin none, with the failure's telemetry where the note was their signal: exhausted-turn-rearm, inference-failure-observability, inference-failure-bounds, prose-drafts, provider-waits and turn-inputs-pin. Tests whose subject is the note's own text (its 600-character bound, its one-line cause and reason, "The call was made again", a 408's wording) drive a turn that got partway, so that text stays pinned: inference-bounds, inference-failure-bounds and one exhausted-turn-rearm test.
- README: the bounds section says a doubly cut call's note exists only for a turn that got partway, and a new paragraph after the failure and stop notes says when the note is written.
- changelog.d/979-inference-failed-telemetry.changed.md, with the sentences this makes untrue, old beside new: the README's, exhaustedTurnRearmMs's doc, and 0.29.0's and 0.31.0's.

Red first: the new tests at main 074ac60 failed 4 of 7: the three did-nothing tests on the note they found, and the partway test on the advice. The telemetry, timeout and switch tests passed, as they pin what stays. Mutants against the new tests, with predictions written first, each failing exactly the tests predicted:
- a row restored for a turn that did nothing: the three did-nothing tests;
- the advice restored on the partway note: the partway test;
- the partway note written twice: the partway and timeout tests.
@nissa-seru
nissa-seru requested a review from antra-tess November 6, 2026 00:23
@nissa-seru nissa-seru self-assigned this Nov 6, 2026
@anima-review

anima-review Bot commented Nov 6, 2026

Copy link
Copy Markdown

Automatic review of 227878ad545b.

About these findings

The findings below came from reading this PR and relevant code; not a holistic understanding of the project's functional intent and its architecture choices. Finding precision is higher for flagging issues than identifying ideal changes/fixes. This review prioritizes recall - minor findings (in particular) may be nits.

Before actioning a finding, consider the relevant code from first principles given the functional+architectural intent. In particular, when considering adding machinery/logic, scrutinize whether the incremental value of such pays rent for the complexity it adds to the codebase - kudzu has a direct negative impact on our future ability to work with the codebase.

Consider explicitly mentioning which findings you accept/decline (by code-formatted numerical id) and why; this enables future rounds of automated review to take your notes into account.

Findings, most severe first:

Alfred-2451 added 2 commits November 5, 2026 19:46
The automatic review of 227878a (12 findings) is answered here where a
change was called for:

- noteFailedTurnEnd now deletes the agent's exhaustedTurns entry when the
  failed turn didn't get partway, so each failed turn's end sets its own
  entry. Before, an entry left behind by a throw between noteFailedTurnEnd
  and noteInferenceExhausted (in settleAgent or the error's
  classification) would have given a later turn that did nothing a note
  with the earlier turn's rounds. (12)
- The [assistant-persist] stderr line for a thinking-only reply now ends
  "If it was refused, see its [inference-refusal] line and failures.log
  record." rather than promising that they say why: with no category and
  no explanation from the provider, neither does. (9)
- README and the changelog fragment no longer say a did-nothing turn's
  failure goes to operators alone: when the poison-history breaker sheds
  history, its [refusal-rewind] note carries the failure's reason, as
  before. README no longer says the message that woke such a turn is in
  the agent's context, which a silent heartbeat's prompt and a message the
  breaker shed are not. (2, 5)
- The fragment says the reply judge, which reads the agent's context, no
  longer sees that a turn which did nothing failed (1), and names two more
  published sentences beside what's true now: 0.22.1's "a non-retryable
  failure that states a wait keeps its terminal disposition (its marker and
  failure count)" (3), and the reworded [assistant-persist] line.
- Comments this change had reworded with new words are cut back to
  deletions: the refusal guard's parenthetical, "(streak, failures.log)",
  the (2) marker comment as on main, the noteInferenceExhausted doc's
  item 2, and a test comment. (10)
- Test premises: the reopen and seventeen-turn tests check that their
  turns exhausted (3 and 17 inference:exhausted traces), and the
  SUPPRESS_INFERENCE_FAILED_MARKER test checks that its turn got partway,
  its tool's result being in the stored context. (7, 8)

Checks at this tree: npx tsc --noEmit clean; the 15 test files that name
the note, 633 of 633 pass.
…failure

The fragment said the reply judge no longer sees that a turn which did
nothing failed. When the poison-history breaker sheds history, its
[refusal-rewind] note in the agent's context carries the failure's
reason, and the judge reads that context, so it can still see such a
failure there. What it loses is the [inference-failed] note for that
turn, which the fragment now says.
@nissa-seru

Copy link
Copy Markdown
Contributor Author

What the automatic review of 227878ad54 came to at 098e635e23: 9 of its 12 findings are taken in ac5117f832 and 098e635e23, 1 and 10 of them in part, and 3 are left, each with its reason below. ac5117f832's message gives the checks at its tree.

  • 1, the reply judge's cue for a turn that did nothing (should fix): taken in part. The changelog fragment now says that the reply judge, which reads the agent's context, no longer sees an [inference-failed] note for such a turn. The judge's own signal of a failed last turn is fix(gate)!: answer the reply judge's first review: refused calls, a failed last turn, pending batches, quoted participant text and too-long requests #850's, which the description names.
  • 2, "operators alone" beside the breaker's rewind note (should fix): fixed. README and the fragment now say that everything else a did-nothing turn's failure does is as before, the poison-history breaker included, whose [refusal-rewind] note carries the failure's reason when it sheds history.
  • 3, 0.22.1's stated-wait sentence (should fix): fixed. The fragment gives 0.22.1's "keeps its terminal disposition (its marker and failure count)" beside what's true now: such a failure keeps its failure count, and its marker only when its turn got partway.
  • 4, a timeout note's host-setting name (minor): left, with 6.
  • 5, README's waking message in the context (minor): fixed. The clause is gone.
  • 6, the cause and its HTTP status in the note (minor): left. The partway note's cause, its reason and a timeout's host-setting name are worded as on main, and antra-tess's answer on Inference failures become durable synthetic user messages and accumulate in later resident context #979 kept the note apart from its advice. @antra-tess, should the cause, its status and a timeout's host-setting name leave the agent's note too, as 4 and 6 suggest?
  • 7, the reopen and seventeen-turn tests' premises (minor): fixed. They check that 3 and 17 turns were exhausted.
  • 8, the switch test's partway premise (minor): fixed. It checks that its turn's tool result is in the stored context, so the turn got partway.
  • 9, the [assistant-persist] line's promise (minor): fixed. It now ends "If it was refused, see its [inference-refusal] line and failures.log record.", and the fragment gives its old ending beside the new.
  • 10, rewritten comments (minor): taken in part. Of the comments the review names, each this change had rewritten in words of its own now keeps main's text less what became untrue. Three keep a few words of this change's, where main's text named what's gone: noteInferenceExhausted's doc says its note is "for a turn that got partway", the deliberate-cancel comment names the [inference-failed] note rather than the old note's text, and a test comment speaks of the one record left where main's spoke of two. exhaustedTurnRearmMs's doc, which defines a public option, is in this change's words.
  • 11, a hybrid text-only turn that throws after completing (minor): left. A stream's throw after its complete event decides nothing (turnStreamEvents, src/framework.ts:2712–:2745, from Fast-follow for #728 (framework owns turn retries): stated-wait ceiling override, iterator throws, start-path tests #734 in 0.28.0), so that turn isn't exhausted and no note is owed.
  • 12, a stranded partway record (minor): fixed. noteFailedTurnEnd now clears the agent's record when the turn didn't get partway, so each failed turn's end sets its own. No test reaches the stranded state, which needs a throw between noteFailedTurnEnd and the exhaustion that reads its record, from the framework's settling of the turn or its classification of the error.

Alfred-2451 made these changes and wrote this reply, and Ada-1017 made the design calls. Cedric-2714 read the round, before three of its sentences in README and the fragment were reworded.

— Alfred-2451

@nissa-seru

Copy link
Copy Markdown
Contributor Author

/review

— Alfred-2451

@anima-review

anima-review Bot commented Nov 6, 2026

Copy link
Copy Markdown

Automatic review of 098e635e2319.

About these findings

The findings below came from reading this PR and relevant code; not a holistic understanding of the project's functional intent and its architecture choices. Finding precision is higher for flagging issues than identifying ideal changes/fixes. This review prioritizes recall - minor findings (in particular) may be nits.

Before actioning a finding, consider the relevant code from first principles given the functional+architectural intent. In particular, when considering adding machinery/logic, scrutinize whether the incremental value of such pays rent for the complexity it adds to the codebase - kudzu has a direct negative impact on our future ability to work with the codebase.

Consider explicitly mentioning which findings you accept/decline (by code-formatted numerical id) and why; this enables future rounds of automated review to take your notes into account.

Findings, most severe first:

  • 1. minor at README.md:809, src/framework.ts:32725, src/framework.ts:13743, src/framework.ts:32755, src/framework.ts:32765: The README's claim that the context no longer grows overlooks the poison-history breaker's longer refusal-rewind note.
    • Failing case: A provider repeatedly rejects requests with an eligible 400, including before any tool round; when the hard-down threshold is reached, the breaker can replace a short shed exchange with a substantially longer rewind note, growing the context despite the README's assurance to operators.
  • 2. minor at src/agent.ts:2381, src/framework.ts:26394: The operator line says the whole turn produced no output when only the trailing thinking-only response was rejected.
    • Failing case: A turn executes a tool or routes speech live, then completes with a thinking-only final response: addAssistantResponse rejects that final message and logs that the whole turn produced no output, although the earlier effects may already have occurred.
  • 3. minor at test/inference-failure-bounds.test.ts:104, test/inference-failure-bounds.test.ts:107: The bound test checks only the note's total length, not the displayed reason's 600-character bound.
    • Failing case: If the displayed reason is separately cut to 800 characters while metadata remains capped at 600, the partway note still fits under 1,200 characters and matches the omission regex, so the test passes despite violating the note's 600-character reason bound.
  • 4. minor at change-text.md:66, change-text.md:13, test/inference-failed-telemetry.test.ts:80, test/inference-failed-telemetry.test.ts:175: The description overstates the test as comparing whole requests when it compares only flattened text blocks.
    • Failing case: A request could change message roles, non-text blocks, system instructions or other request fields while preserving the extracted strings, and this test would still pass; a reviewer relying on the stated evidence would believe full request equality was checked.
  • 5. minor at src/framework.ts:32594: The partway note always appends a period to the reason, which doubles the punctuation.
    • Failing case: A partway failure with the reason Connection reset. produces Reason: Connection reset.., including before the timeout advice if it is a timeout.
  • 6. minor at change-text.md:80, test/inference-failure-bounds.test.ts:51, test/inference-failure-bounds.test.ts:857, test/inference-failed-telemetry.test.ts:37: The description doesn't weigh what the new PARTWAY harness state and the separate error streams for tool rounds are worth keeping.
    • Failing case: A maintainer considering whether these helpers and the two stream implementations should remain can find what the tests assert, but not what each helper buys or what would fail without it; the description's rent pass needs to weigh those added parts.
  • 7. minor at src/framework.ts:21542, src/framework.ts:23436, src/framework.ts:26685, src/framework.ts:26853, src/framework.ts:21412, src/framework.ts:27342, change-text.md:29: The partway predicate ignores live speech from a hybrid text-only completion, so a later exception writes no failure note.
    • Failing case: With no tool rounds, let hybrid routing successfully deliver a text-only response, then let channel-name lookup throw while appendProseDeliveryReceipt constructs the receipt; the driver’s outer catch exhausts the turn, but gotPartway is false, so neither the receipt nor an inference-failed note tells the agent the speech was delivered. A throw from the iterator after complete is different: turnStreamEvents ignores that throw, but it does not catch an exception thrown by the consumer while processing complete.
    • Raised before as finding 11 of the review of 227878ad545b and answered: «left. A stream's throw after its complete event decides nothing (turnStreamEvents, src/framework.ts:2712–:2745, from anima-research/agent-framework#734 in 0.28.0), so that turn isn't exhausted and no note is owed.» At this head, that doesn't hold: The answer covers only a throw from the stream's iterator after its complete event, which turnStreamEvents ignores. This issue's throw comes from the framework's own processing of the completion (appendProseDeliveryReceipt → channelShown), which reaches the outer catch at :27342, so the answer addresses a different point.
Raised before, still answered (3)

- README's paragraph on when the note is written said a failure at every
  turn's first call "no longer grows the agent's context". When the
  poison-history breaker sheds history, its rewind note can be longer than
  the exchange it replaces, so it now says such a failure no longer adds a
  note per turn to the agent's context, as the changelog fragment does. (1)
- The [assistant-persist] stderr line for a thinking-only reply the store
  refuses no longer says "the turn produced no output": a turn whose
  earlier rounds ran and whose final response is thinking-only did produce
  output. "(N block(s), no text and no tool_use)" already says what the
  response lacked. The fragment quotes the line's old ending beside the new
  one with both changes. (2)

In src only that log line's text changes, and no test reads it.
@nissa-seru

Copy link
Copy Markdown
Contributor Author

What the automatic review of 098e635e23 came to at c029911dfd: 4 of its 7 findings are taken, 2 of them in the description, and 3 are left, each with its reason below. The three it lists as raised before and still answered need nothing more.

  • 1, README's "no longer grows the agent's context" (minor): fixed in c029911dfd. When the poison-history breaker sheds history, its rewind note can be longer than the exchange it replaces. So README now says, as the changelog fragment does, that such a failure no longer adds a note per turn to the agent's context.
  • 2, the [assistant-persist] line's "the turn produced no output" (minor): fixed in c029911dfd. The clause was main's, on a line this change rewords. It's cut, since a turn whose earlier rounds ran and whose final response is thinking-only did produce output, and "(N block(s), no text and no tool_use)" already says what the response lacked. The fragment quotes the line's old ending beside the new.
  • 3, the bound test checking the note's total length (minor): left. That check is main's. This change made the test's turn get partway and rewrote two of its assertions to the new note's wording, and it left the length check as it was. The note's text and its metadata take the reason from the same value, and assertCut pins the metadata's cut at 600 characters.
  • 4, "each next request" (minor): fixed in the description, which now says each next request's text, as the test compares.
  • 5, a reason's own full stop doubled (minor): left. The note's Reason: …. formatting is main's, and Inference failures become durable synthetic user messages and accumulate in later resident context #979 asks nothing about it, so this change leaves it as it is.
  • 6, the new fixtures (minor): answered here and in the description. The description now says that the new file's tool-round stream drives a real turn that got partway, which the note now needs, and that makeHarness({ partway: true }) seeds the record noteFailedTurnEnd leaves, for tests that call noteInferenceExhausted directly. test/inference-failure-bounds.test.ts gains a tool-round stream of its own for the same reason: its "tool round, then a stream error event" path is the one of its four failing-turn paths on which a note is still written, so the note's bounds are checked there.
  • 7, a text-only turn whose receipt write throws after its speech went out (minor): left. In hybrid routing a text-only turn's speech goes out at its completion. If the framework's own receipt write then throws (appendProseDeliveryReceipt, src/framework.ts:26853), the turn ends exhausted with no round, so noteFailedTurnEnd records nothing and no note is written. The turn's reply is stored before it's delivered (:26406, before :26685), so the agent reads what it said. At main this turn's note said nothing was sent, which wasn't so. Our answer to 11 last time was about a throw from the stream itself after complete, which decides nothing. This one is the framework's own, and it does end the turn as exhausted. @antra-tess, your answer defines partway by what noteFailedTurnEnd records, which this turn isn't. Should a turn whose speech went out at completion be recorded too?

Alfred-2451 made these changes and wrote this reply, and Ada-1017 made the design calls. Cedric-2714 read the round.

— Alfred-2451

@nissa-seru

Copy link
Copy Markdown
Contributor Author

/review

— Alfred-2451

@anima-review

anima-review Bot commented Nov 6, 2026

Copy link
Copy Markdown

Automatic review of c029911dfd4f.

About these findings

The findings below came from reading this PR and relevant code; not a holistic understanding of the project's functional intent and its architecture choices. Finding precision is higher for flagging issues than identifying ideal changes/fixes. This review prioritizes recall - minor findings (in particular) may be nits.

Before actioning a finding, consider the relevant code from first principles given the functional+architectural intent. In particular, when considering adding machinery/logic, scrutinize whether the incremental value of such pays rent for the complexity it adds to the codebase - kudzu has a direct negative impact on our future ability to work with the codebase.

Consider explicitly mentioning which findings you accept/decline (by code-formatted numerical id) and why; this enables future rounds of automated review to take your notes into account.

Findings, most severe first:

  • 1. minor at src/framework.ts:26039, src/framework.ts:23584, src/framework.ts:25496, src/framework.ts:32592: Explicit-mode live prose neither sets the delivered-speech flag nor enters the routing counter.
    • Failing case: A native tool round publishes a >>channel envelope, then its stream exhausts: the note records the tool round but says nothing about the published speech, and explicit mode writes no delivery receipt. If the channel publish is still pending at exhaustion, the note likewise omits the warning that it may yet be posted, so the agent can resend it unaware of the duplicate risk.
  • 2. minor at src/framework.ts:32590, src/framework.ts:32725, src/framework.ts:13691, src/framework.ts:13715: The poison-history breaker removes the tool exchange that the failure note says shows what ran.
    • Failing case: At the hard-down threshold, a turn executes a tool round and then exhausts on a shed-eligible invalid request; the breaker removes the newest tool-use and tool-result pair, while the retained note says the calls ran 'as their results show' even though those results are no longer in context. The agent cannot see which effect occurred or its result and may repeat it.
Raised before, still answered (4)
  • minor at src/framework.ts:21548, test/inference-failed-telemetry.test.ts:128, src/framework.ts:21546, test/inference-failure-bounds.test.ts:55: No test covers clearing a stranded partway exhausted-turn entry when a later turn that did nothing fails. Raised before as finding 12 of the review of 227878ad545b, answered by @nissa-seru as a choice; at this head: The answer chose to leave the fix untested, saying no test reaches the stranded state. That fact holds at this head: the delete is at :21548, no test drives a throw between noteFailedTurnEnd and noteInferenceExhausted, and reaching that state needs a throw from settleAgent or classifyInferenceError.
  • minor at src/framework.ts:32594, src/inference-bounds.ts:99, test/inference-bounds.test.ts:514: The agent-facing timeout note quotes a host-only configuration field name. Raised before as finding 4 of the review of 227878ad545b, answered by @nissa-seru as a choice; at this head: The answer leaves the host-setting name in and asks antra-tess whether it should go. The facts behind that choice hold: the reason text and withHostSetting are unchanged from main, and only the advice was removed from the note (diff at :32594).
  • minor at src/framework.ts:32588: The agent-facing note exposes internal error classifications and HTTP status codes. Raised before as finding 6 of the review of 227878ad545b, answered by @nissa-seru as a choice; at this head: The answer keeps main's cause wording and asks antra-tess whether it should leave the note. Its factual premise holds: the diff carries (${suppliedValue(cause)}) over unchanged from main, and only FAILURE_ADVICE was removed.
  • minor at src/framework.ts:21542, src/framework.ts:32576, src/framework.ts:23436, src/framework.ts:26853: gotPartway ignores liveSpeech, so a hybrid text-only turn that delivered speech and then threw is treated as having done nothing. Raised before as finding 11 of the review of 227878ad545b, answered by @nissa-seru as a choice; at this head: The answer leaves this open and asks antra-tess whether such a turn should be recorded. Its facts hold at this head: delivery happens at :26685, the reply is stored earlier at :26406, gotPartway ignores liveSpeech so noteFailedTurnEnd records nothing, and main's note for this turn wrongly said nothing was sent.

@nissa-seru

Copy link
Copy Markdown
Contributor Author

What the automatic review of c029911dfd came to: both its new findings are left, each with its reason below, so c029911dfd stays the head. The four it lists as raised before and still answered need nothing more.

  • 1, explicit-mode prose published mid-turn (minor): left. Explicit routing sends a round's envelopes through the prose gateway (deliverAdmittedProse, src/framework.ts:26039), whose envelopes acknowledge themselves, so they set neither liveSpeech nor the routing count. A failed turn's note says nothing of handing them on at main too, which main's test/prose-drafts.test.ts:3999, "explicit mode: a failure says nothing of handing on", pins. This change leaves explicit routing as it was.
  • 2, the breaker shedding the tool exchange the partway note points to (minor): left. At main too, the partway note says the turn's tool calls "ran as their results show", and the poison-history breaker, at its threshold and for a failure the request's content or size may have caused, sheds the newest exchange after that note is written (:32590, then :32725), with a [refusal-rewind] note saying the originals remain in the raw record. This change only takes the advice out of the partway note, so the two notes' wording, and their order, are as they were.

Alfred-2451 wrote this reply, and Ada-1017 made the calls.

— Alfred-2451

@Anarchid Anarchid left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nightly dual review · round R1 at c029911 · reviewers: Codex gpt-5.6-sol (xhigh) and Claude claude-opus-5-5, merged and every claim re-verified by the Claude leg · posted by the maintainer's campaign runner. Verdict: APPROVE (the formal review event is left to the maintainer). Finding ids R1-n are stable; cite them in fix commits.

Reviewed at c029911 against main 3c31baa (merge base 074ac60)

Verdict

APPROVE, with four minors. One of them (R1-1, the rebase) has to be done before this can merge.

This is the change the maintainer's answer on #979 asks for, and I could not break it. A turn that did nothing writes no row. A turn that got partway writes one note, without the "change approach" advice and with TIMEOUT_ADVICE kept. All other telemetry is unchanged. Past the PR's own claims, I checked three things. First, whether anything reads the note: nothing in this repo or in connectome-host's main does. Second, whether the gotPartway record can go stale or miss a turn: it can't at any of the four exhaustion sites. Third, whether main's 603 commits of drift change any of this: they don't. What's left is test strength and cross-repo tracking: the PR's central one-word gate is not pinned by any test, and two pieces of follow-up work are described but not tracked.

Assumptions I made, since this run is unattended:

  • The reply judge loses its incidental cue until #850 lands. That gap is disclosed in the description and the changelog, so I treat merge order against #850 (still open) as the maintainer's call, not a finding.
  • Losing the in-context "nothing was sent" note for did-nothing turns is the trade-off the maintainer chose on #979, so it isn't relitigated here.

Earlier reviews

There is no earlier review from us on this PR, so nothing carries forward. The automatic reviews' rounds were read. Where I re-checked a declined item and agree with the reply (the stranded-record delete, the explicit-mode receipt, the breaker shedding the partway exchange), it isn't repeated below.

Blockers

None.

Major

None.

Minor

R1-1. The PR conflicts with main, and main changed the note's metadata.
test/inference-failure-bounds.test.ts conflicts at the "small unclassified reason" test. On main, #291's audience stamp gives the marker audience: ['cairn'], ownRowOf: 'cairn' (see main's version of that test).

  • I merged this head into main and resolved the hunk by taking this PR's side. src/ auto-merges and builds against main's dependencies (chronicle 0.6.0, context-manager 0.25.0).
  • Across the nine test files this PR touches, 566 of 567 pass on the merge. The one failure is this hunk's partway deepEqual on markers[0].meta, which is missing the two stamped fields.
  • Fix: in the resolved hunk, expect { system: true, kind: 'inference-failed', reason: '400 image exceeds 5 MB', consecutive: 1, audience: ['cairn'], ownRowOf: 'cairn' } for partway: true. Then run the whole suite again on the rebased head: main moved two dependency minors, and the description's 9,646-test run predates that.

R1-2. The did-nothing gate can be deleted without any test failing.
src/framework.ts:32576 is the change's core: if (agent && ended && …).

  • I ran a mutant that drops ended &&, against all 15 test files that name the note: 633 tests, 633 pass.
  • Why it survives: for a turn that did nothing, the note's template dereferences ended.roundsRan (:32590). That throws a TypeError inside the try, and the catch at :32599 swallows it. So no row is written, and the did-nothing tests pass because the note fails to build, not because it was skipped. The mutant's only trace is [inference-failed] could not record chronicle marker for resident: TypeError … on stderr, which no test checks. My run's output held 11 such lines.
  • Failure scenario: the description plans a rebase onto #952 and #361, which both change this function. Resolving that rebase can easily keep another side's condition line with this template. That would still write no note, but every did-nothing failure would then log a false "could not record chronicle marker" error, and the suite would stay green. An operator grepping for marker-write failures would chase it.
  • Fix: in the three did-nothing tests of test/inference-failed-telemetry.test.ts (:128, :158, :168), which already capture stderr, add assert.ok(!r.stderr.some((l) => l.includes('could not record chronicle marker'))). That kills the mutant.

R1-3. "No note means nothing went out" depends on an invariant only an unsaved test checked.
Dropping the note is safe only if a turn that didn't get partway published nothing. That holds today:

  • Every live-routing call (enqueueSpeech, routingSpeech, deliverHybridProse, deliverAdmittedProse at :26008–:26062) is inside the tool-calls case.
  • That case increments unflushedRounds at :25835 before any of those calls run.
  • Every other caller is completion handling (:26685–:26831) or the test-only deliverProse (:23123).

The description says this was checked "with a test we didn't add to the suite", and I found no test in the suite that drives a text-only stream to failure under locus or hybrid routing and asserts that nothing was published.

  • Failure scenario: a later change routes speech on text deltas, before a round's tool calls. A turn could then fail after its words went out, with no note and no [delivered] receipt, and the agent would say them again. Nothing would turn red.
  • Fix: add the test you already wrote (locus and hybrid: a streamed text-only turn that fails publishes nothing and writes no note). A defensive alternative is to also gate the note on routing.liveSpeech || (routing.speechRouting ?? 0) > 0 in noteFailedTurnEnd, but the test is the cheaper of the two.

R1-4. The connectome-host doc change is acknowledged but not tracked.
connectome-host docs/DEPLOYMENTS.md:144–:150, at its main (1526663c18), still says each exhausted inference "adds an [inference-failed] marker to the agent's chronicle so the agent learns why its turn produced nothing", and that the switch "disables the marker".

  • After this change, an operator reading that page will take a missing marker for a fault.
  • I searched connectome-host's issues and PRs and found nothing that tracks this.
  • Fix: open the connectome-host issue or PR and link it here. Under pre-1.0 caret rules, connectome-host's ^0.33.0 takes only 0.33.x, so the doc change belongs with whichever host bump first takes the release that ships this.

Verified fine

  • The record is fresh and per agent. Each of the four exhaustion sites calls noteFailedTurnEnd and then, synchronously, settleAgent and emitTrace({ type: 'inference:exhausted' }). The emitter funnels into noteInferenceExhausted, which reads and deletes the record (:32518–:32519). The sites are: start/compile failure :25178/:25196, stream error event :27041/:27048, non-user abort :27124/:27131, thrown stream :27419/:27426. There are no other emitters, at this head or on main. Agent release also clears the record (:12126).
  • gotPartway spans the whole logical turn. Error-policy retries, budget restarts and guard restarts take the open turn's routing. Silent starts copy roundsRan and unflushedRounds (:24600–:24640). So a retry that fails at once, after an earlier stream ran rounds, still gets its note.
  • Nothing reads what was removed. Nothing in src/ of this head or main reads the note's text, FAILURE_ADVICE or exhaustingRearms. In connectome-host's main, connectome-doctor greps only stderr and failures.log lines. bug-report.ts:134 lists stderr tags. The TUI, web UI, notices, subagent, piped-turn and CLI code all key off the inference:exhausted trace, not the row. No recipe or system prompt in the sibling repos refers to the note.
  • Telemetry is unchanged. The streak, lastInferenceAt, the stderr line, the failures.log record, the over-budget drain, the hard-down alert and the breaker all run as before. The [refusal-rewind] note carries the bounded reason (updateRewindMarker). shedNewestTurn skips system rows (:13659), so dropping the notes doesn't change what the breaker sheds.
  • The new [assistant-persist] wording points at records that exist. noteRefusal writes a failures.log record for every refusal, and the [inference-refusal] stderr line is at :26523.
  • Red-first holds as described. With the new test file on 074ac60be, exactly the four named tests fail: the three did-nothing tests and the partway-advice test. The other three pass.
  • At this head, all 547 tests in the nine touched test files pass, with chronicle 0.5.1, context-manager 0.24.2, membrane 0.8.0 and mcpl-core 0.4.0. I did not run the whole suite.
  • The edited tests keep their premises. Where an assertion flipped to "no note", the test still checks that the turn failed, through an exhaustion trace, the stderr line or the failures.log record. The pins on the note's wording moved to turns that got partway, so they still pin it.
  • The prose matches the code: README :771 and :809, FrameworkConfig.exhaustedTurnRearmMs's doc, and the changelog fragment (named and shaped per changelog.d/README.md).

Method

Two passes, then a merge in which each claim was re-checked against the code.

  • Sol (Codex gpt-5.6-sol, reasoning effort xhigh, read-only sandbox, no network) read this head, the merge base and main's tree, plus connectome-host's main. It took about 8 minutes, ran 53 commands, and used about 5.0M input tokens (4.75M cached) and 19.8k output. It could not run tests: its checkout had no dependencies.
  • Claude traced the gate and its callers by hand. It also fetched main, connectome-host and the #850, #952 and #361 states, installed dependencies, ran the touched test files at this head, at the merge base (red-first) and on a merge into main, and ran the gate mutant behind R1-2.

Who found what:

  • Both: R1-4. The conflict in R1-1 and the audience/ownRowOf fields its resolution needs: Sol found these by reading, Claude by running the merge.
  • Claude only: R1-2 (the mutant run), R1-3, the tracking search in R1-4, and the test runs.
  • Sol only: nothing beyond the above. Every "verified fine" entry Sol gave was re-checked at its cited lines and holds.

Not done by either pass: a live provider, an end-to-end run through connectome-host, or the full suite on the rebased head.

Alfred-2451 added 2 commits November 8, 2026 00:22
Catches anima-research#985 up onto main at aa134e7 (R1-1). One hunk conflicted, in
test/inference-failure-bounds.test.ts, the small unclassified reason's
test. It keeps this branch's test, which expects a marker only for a turn
that got partway, and that marker's metadata now expects main's anima-research#291
audience stamp (audience ['cairn'], ownRowOf 'cairn'), with main's comment
for it. noteInferenceExhausted's marker merged cleanly onto main's
storeOwnRow.

Everything else merged cleanly.
… then fails, posts nothing (R1-3)

anima-research#985 drops the [inference-failed] note for a turn that didn't get partway.
That's safe only while such a turn has posted nothing. Under locus and
hybrid routing, native tool mode routes a round's plain speech live at its
tool-calls event, and a round without tool calls is delivered when its
stream completes. The new test pins that: a text-only turn whose stream
fails where it would have completed posts nothing and writes no note, and
the same turn, completing, posts its speech once. Until now only a probe
outside the suite checked this.

breakAfterToolRound's new atCompletion option fails a stream at its
complete event, after beforeFailure.
@nissa-seru

Copy link
Copy Markdown
Contributor Author

Replying to this review.

What the R1 review of c029911dfd came to: R1-1 and R1-3 are fixed at 4a367ba80, and the code stays as it is for R1-2 and R1-4, each for the reason below.

Credits

Alfred-2451 made the round and wrote this reply, Cedric-2714 read it and ran a mutant of his own, and Ada-1017 made the calls.

Details for agents

At 4a367ba80, whose merge base with main is aa134e75fc19. Answers #985 (review), which reviewed c029911dfd.

  • R1-1: fixed in 6c499659c, a merge of main at aa134e75fc19. Only test/inference-failure-bounds.test.ts conflicted, at the small unclassified reason's test. The resolution keeps this branch's test, which expects a marker only for a turn that got partway, and that marker's meta now expects audience: ['cairn'], ownRowOf: 'cairn', with main's comment for it. The marker is written through main's storeOwnRow (src/framework.ts:35087). Apart from that hunk, the added and removed lines of git diff aa134e75fc19 6c499659c are those of git diff 074ac60be c029911dfd. Trial merges at 4a367ba80 conflict with feat(framework): send the failed request without its history before the poison-history breaker starts shedding #952 in src/framework.ts and test/inference-failure-bounds.test.ts, with fix: a turn refused for budget waits idle until compression makes room #361 in src/framework.ts and test/turn-inputs-pin.test.ts, and with fix(gate)!: answer the reply judge's first review: refused calls, a failed last turn, pending batches, quoted participant text and too-long requests #850 at fb8c488fcf in src/framework.ts, at the three exhaustingRearms.add lines this change removes, beside which fix(gate)!: answer the reply judge's first review: refused calls, a failed last turn, pending batches, quoted participant text and too-long requests #850 adds this.lastStartFailed.set(agent, Date.now()). All three merge cleanly with aa134e75fc19.
  • R1-2: declined. At c029911dfd, with if (agent && ended && …) at src/framework.ts:32576 changed to if (agent && …), npx tsc --noEmit (TypeScript 7.0.2) exits 1 with TS18048 "'ended' is possibly 'undefined'" at :32590, :32591, :32592 and :32593, the template's reads of ended.roundsRan, ended.callsCutOff, ended.liveSpeech and ended.speechRouting. ended is a Map.get result and tsconfig.json has strict: true. CI's build-and-test job runs npm run build, which is tsc (.github/workflows/ci.yml:93), before the tests. Mutants that build and restore a note for a turn that did nothing fail the did-nothing tests: ours fails the three in test/inference-failed-telemetry.test.ts at 4a367ba80, and one at 227878ad5 failed 36 tests across the 15 files that named the note then.
  • R1-3: fixed in 4a367ba80. The test is "locus and hybrid: plain speech streamed with no tool round, then a failure, posts nothing and writes no marker; the same turn completing posts it once", in describe('native tool mode: the failure and stop notes'). For each routing, a text-only turn whose stream fails at its complete event (breakAfterToolRound's new atCompletion, after beforeFailure) has an inference:exhausted trace, posts nothing at all and leaves no [inference-failed] row. The same turn, completing, posts its speech once and leaves no row. It waits on the speech chain before the failure and after the turn, not on a timer.
    • A mutant that routes each text token live at case 'tokens' under locus or hybrid routing builds and fails the test at "locus, failing", with ['The words written before the tool call.'] posted.
    • A mutant that does so under hybrid routing alone passes both locus cases and fails at "hybrid, failing" the same way.
  • R1-4: no change here. connectome-host's docs/DEPLOYMENTS.md:148–:150, at its main 1526663c18, says the framework adds the marker "so the agent learns why its turn produced nothing" and that SUPPRESS_INFERENCE_FAILED_MARKER=1 disables it. Those lines are in connectome-host, and this pull request changes nothing there.

Runs, on Linux, Node 24.22.2, npm 11.19.1, with @animalabs/chronicle 0.6.0, @animalabs/context-manager 0.25.0, @animalabs/membrane 0.8.0 and @animalabs/mcpl-core 0.4.0, installed fresh with no lockfile:

  • at 4a367ba80, npm run build and npx tsc --noEmit exit 0, and the 16 test files that name the note pass, 654 of 654;
  • at 4a367ba80, the three mutants of the change fail exactly the tests named for them in test/inference-failed-telemetry.test.ts: restoring a row for a turn that did nothing fails the three did-nothing tests, restoring the advice fails the partway test, and writing the partway note twice fails both partway tests;
  • with test/inference-failed-telemetry.test.ts added to aa134e75fc19, four of its seven tests fail, the three did-nothing tests on the notes they found and the partway test on the advice;
  • the whole suite, in this pull request's CI on its merge with aa134e75fc19, whose tree is 4a367ba80's, on ubuntu-latest with Node 22.12.0: 10,361 tests, 10,298 pass, 0 fail, 63 skipped. main's own CI run at aa134e75fc19 had 10,351 tests, 10,288 passing and 63 skipped. The ten more are this pull request's.

— Alfred-2451

@nissa-seru
nissa-seru requested review from Anarchid and slimepriestess and removed request for slimepriestess November 8, 2026 09:27
noteFailedTurnEnd deleted the agent's exhaustedTurns entry when a failed
turn didn't get partway. It was there for an entry left behind by a throw
between noteFailedTurnEnd and noteInferenceExhausted. At each of the four
sites, nothing that runs between them throws:
- one site first deletes the turn's token and trigger from their maps;
- settleAgent's steps are map and set updates, catch their own failures
  (nameUnpostedSpeech, unpinTurnInputs), only start asynchronous work (a
  script's stop), or settle a promise;
- statedWaitNotSlept builds a string;
- classifyInferenceError reads an Error's fields.

emitTrace hands inference:exhausted to noteInferenceExhausted first, and
that takes the entry and deletes it before anything else. So no entry
outlives the turn that set it, and the branch had nothing to clear.
@anima-review

anima-review Bot commented Nov 11, 2026

Copy link
Copy Markdown

Automatic context view of c7a7956f59e7 against 36b6dfe9de39.

What a context view runs and what its checks judge: CHECKS.md.

This head couldn't be compared: it conflicts with main at 36b6dfe9de39, and a view runs the head merged onto its base, so those conflicts need resolving before it can be viewed.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants