Skip to content

feat(drafts): a send's result says what it holds; the receipt names each draft's reason - #1002

Open
nissa-seru wants to merge 8 commits into
anima-research:mainfrom
nissa-seru:hamish/send-hold-line
Open

nissa-seru wants to merge 8 commits into
anima-research:mainfrom
nissa-seru:hamish/send-hold-line

Conversation

@nissa-seru

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

Copy link
Copy Markdown
Contributor

Why. Nothing told a resident before an explicit send held its plain speech from the same turn as a draft. On the XML path, and on streams that don't present injected messages, the first it heard came after it had stopped: a turn-end receipt that gave no reason.

A send's own result now says what that send holds of the turn's plain speech, and the turn-end receipt gives each held draft's reason beside its id.

A send's result

  • base (main): the send's answer alone.
  • head, once a round of the turn is routed as it streams (the native path): "… Your plain speech to #room, from this round on, is held as drafts until a new message reaches you or this turn ends, except in a round that keeps its speech private."
  • head, routed at the turn's end (the XML path, and a native one whose stream gives no round content): "… Your plain speech to #room this turn is held as drafts until this turn ends, except in a round that keeps its speech private." A new message doesn't end the hold there, since the turn routes its speech at its end.
  • head, place unknown: "… The framework can't tell where this posted (its answer named no channel the framework reads), so your plain speech anywhere, from this round on, is held as drafts until a new message reaches you or this turn ends, except in a round that keeps its speech private."

The turn-end receipt

  • base (main): "2 plain-speech segment(s) held as drafts d-a, d-b (not sent — drafts can resend them unchanged, or dismiss them)"
  • head: "2 plain-speech segment(s) held as drafts d-a (explicit send in its turn), d-b (explicit send in its turn) (not sent — drafts can resend them unchanged, or dismiss them)"

The line is added only where a send may hold plain speech: in locus, hybrid and locked modes. A turn whose plain speech is already kept private, such as after a module's skip_reply, gets no line, and neither does a send beside a skip_reply that ends the turn. A send that posted nowhere gets no line either. Apart from that skip_reply, the line doesn't read the round's other calls. It excepts any round that keeps its speech private, such as one beside a same-round private think, a refused wake or a drafts resend with no other send, so it stays true whatever a script in the round, or a later round, does. Its place comes from the same reading of the send's answer that decides the hold. The line follows the send's own answer, where that answer is read. It comes after an error answer's text, and after a value that's text, a number or a boolean. An answer with nothing in it, or null, is the line itself. On an object answer, such as channel_publish's, it goes in a routing field, the field name channel_open's result uses. No tool description changes. A request whose history holds a send's result or receipt made after this carries its new words, the judge's requests over that history included, so its cache boundaries can fall elsewhere. A result or receipt stored before this keeps its words.

On a stream that presents injected messages (the native path), a mid-turn notice still names the draft a round's speech became. The line and the notice say different things. The line, in the send's result, says what will be held from then on. The notice, beside it, names the draft made, where it was going, and the call that resends it.

Limits. A send made by a code_execution script gets no line, because the script reads its result. Such a send holds the turn's plain speech where the script's code names the send tool. A name the script builds at run time isn't seen, so its round routes at once and that send may hold nothing. A send whose answer is an array that isn't content, or passes the inline cap, gets no line where the answer is read. Neither does a result the tool-result guard withholds, whose stub keeps only its marked fields, nor an answer that is itself a reference or an embedded resource, which is shown as that reference's text. In each of these cases the hold itself is unchanged, and the turn-end receipt names it with its reason. In a turn locked to drafts, the lock, not the send, holds all the turn's plain speech, and keeps holding it after the end the line names, as the turn's routing line says. A send there still gets its line. Sends were run against the repository's test connector, not a live one such as Discord.

With #995. That change makes a turn holding an operator's private message speak in no channel. Its plain speech isn't held as drafts, so this line would be false in such a turn. Whichever of the two merges second needs the line skipped where turnRouting.private holds.


  • Changelog fragments added: changelog.d/send-hold-line.changed.md and changelog.d/send-hold-routing.added.md.
Credits

Hamish-2699 built it and wrote this description. Hilda-2580 established the facts, Phoebe-2419 did the rent pass, Paloma-2644 read it, Ada-1017 made the design calls, Oscar-1808 ruled its changelog kind, and nissa approved it.

Details for agents

Head cd03ad1bf2, eight commits on main 33e349479b: the change, its fragment, and rounds answering the first review (#1002 (comment)), the second (#1002 (comment)) and the third (#1002 (comment)).

Problem

At main, a send's result carries only the send's answer. A resident whose plain speech a send holds hears of it only after it is held. On a stream that presents injected messages, a mid-turn notice says so, with its reason. Otherwise only the turn-end receipt says so. The receipt names each held draft by id alone, with no reason. The red below shows main's send results and receipts.

Changes

  • src/framework.ts:
    • ScriptRound.holdLine is set where a round waits on its sends: in locus, hybrid and locked modes, with a channel registry. It gives no line when the turn's silence is private.
    • holdClaimOf decides what the line claims from two facts. An unrefused skip_reply in the send's round gives no line. Otherwise, once a round of the turn has been routed as it streams (liveProseRouting, which the turn's trailing routing reads), it claims from-round, and before that turn.
    • sendHoldLine words the line from the send's reach. That is sendReachOf's reading, which the round then holds by. The claim's span and end come from HOLD_CLAIM_TEXT: "until a new message reaches you or this turn ends" for from-round, and "until this turn ends" for turn, whose trailing hold is the turn's sends, which a new message doesn't reset (adoptInjectedRound). Every line ends "except in a round that keeps its speech private". A known place is named. An unknown place reads "anywhere", with its reason from UNKNOWN_REACH_TEXT, which sendHoldText now shares. Reach nowhere gives no line.
    • settleScriptRound adds the line to each model-issued send's result as its reach is read. withHoldLine writes it where the answer is read. An error answer gains it after its error text: one marked as an error, whatever its data, and one with no data beside an error's text, which is when the history reads the error. An answer with no data, or null, is the line. Text, a number or a boolean gains a new line. A content array (isContentArray) gains a text block, and any other array nothing. An object gains a routing field.
    • The turn-end receipt's held line gives each draft's DRAFT_REASON_SHORT after its id.
    • Where the Limits come from: sendCallIds (src/framework.ts:28958) takes only the round's model-issued sends, and settleScriptRound (:31299) adds the line to those alone, so a script's send gets none. scriptMayRoute's reading of a script's code (:31335) doesn't see a tool name built at run time. withHoldLine (:22869) gives an array that isn't content nothing. An answer past the inline cap is cut where buildStoredToolResultContent (:31625) stores it, and the line, at the answer's end, goes with the cut. The guard's stub keeps only a result's marked fields (src/tool-result-guard.ts:66–71). The history shows a reference or an embedded resource at a result's root as its stored text (src/tool-result-history.ts:461–468). A turn locked with no place holds every plain word as a draft (src/framework.ts:23661), and its routing line says so (:25258).
  • Two changelog fragments: send-hold-line.changed.md and send-hold-routing.added.md.

Tests

Runs on Linux x86_64, Node v24.22.2, npm 11.19.1. The repository has no root lockfile, so dependency versions aren't pinned.

  • Red: main 33e349479b's src with cd03ad1bf2's four changed test files gives 527 tests, 23 failing.
    • In prose-drafts.test.ts, 18 fail: twelve line tests, five receipt tests, and the test that holds drafts of two reasons in one turn. The tests that expect no line pass: a send that posted nowhere, in explicit mode, in a silent turn or in a turn kept private; a send beside a skip_reply that ends the turn; and a send tool's array of records.
    • In present-while-acting.test.ts, 3 fail: two receipt tests, one of them run for both say and whisper.
    • In explicit-prose-routing.test.ts, 1 fails: its receipt test.
    • In locked-prose-routing.test.ts, 1 fails: its receipt test, which requires "(no destination)".
  • Green: the 23 test files that hold sends and read results or receipts give 1,460 of 1,460 at cd03ad1bf2.
  • Mutants, each built cleanly and run against prose-drafts.test.ts, each failing the tests that pin what it changes:
    • no line: the twelve line tests;
    • the XML path's line worded as the native one's: the three XML line tests;
    • the line without its exception: the twelve line tests;
    • the turn's line held until a new message: the three XML line tests;
    • the line's kind read from the send's round alone: the test whose round without round content follows one routed as it streams;
    • a line beside a skip_reply that isn't refused: the skip_reply test;
    • a line in a turn kept private, and a line for reach nowhere: the no-line test;
    • no reasons in the receipt: the six receipt tests; every draft given the first one's reason: the test that holds two reasons;
    • each of withHoldLine's placements removed, renamed or widened (an error's text, no data, null read as no data, text, numbers and booleans, a content array, other arrays, an object's routing field): that placement's tests;
    • an answer put in the line's place (an object's fields, text, a content array): the tests that read that answer, among them six drafts tests that read a resend's or dismissal's own answer.

Not verified

  • No live connector or provider. Sends ran against the repository's test MCPL server and mock membrane. Their answers are modelled on discord-mcpl's, not taken from it.
  • Only the 23 files above were run. The rest of the suite wasn't.
  • The Limits weren't run. Each is read from the code that makes it, cited under Changes.

Hamish-2699 added 2 commits November 9, 2026 08:52
…ach draft's reason

A send that may have posted holds the turn's plain speech aimed where it
posted. The resident heard of a hold only once its speech was held. On a
stream that presents injected messages (the native path), a mid-turn
notice said so, with its reason. On the XML path, and on a stream that doesn't present
injected messages, nothing said so until the turn-end receipt, after the
resident had stopped. The receipt gave no reason on any path.

A model-issued send's result now gains one line, where the turn's plain
speech is routed and the turn isn't private. On the native path, the plain
speech to that place, this round's included, is held until a new message
arrives or the turn ends. On the XML path, the turn's plain speech there,
earlier rounds included, is held. A send whose place can't be told says the
plain speech anywhere is held, and why, in the words the held-draft notice
already uses. A send that reached nowhere adds no line. The line goes where
each send's answer is read: a drafts resend's content gains a text block,
an MCPL send's text gets the line appended, channel_publish's object a
routing field as channel_open's result has, and an answer that is an error
alone its error text. The turn-end receipt's held line gives each draft's
reason after its id.
@nissa-seru
nissa-seru requested a review from Anarchid November 9, 2026 14:11
@nissa-seru nissa-seru self-assigned this Nov 9, 2026
@anima-review

anima-review Bot commented Nov 9, 2026

Copy link
Copy Markdown

Automatic context view of 9c8982747b4a merged onto 53e72cac4280.

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

The records of 8 of the 76 runs differ between the base and this head.

In the files profiled hosts loaded, 10 changed source lines that map to code ran in no profiled drive: src/framework.ts lines 22849, 22851–22853, 22861 and 22876–22880. 6 changed source lines map to nothing the host runs: 6 of src/framework.ts's. The whole list is in the fold below.

Usage: the agents' requests, subagents' included, in the 76 runs, base → head: 208,163 → 208,167 tokens sent a request, 70.8% of them read from the cache, 146,596,163 → 146,597,751 written to it, 3,943,783 output and 24 thinking blocks dropped. The figures of 8 runs moved, for any kind of request; they're under "Usage that differs", below. The figures are the stand-in's model of each provider's published caching and token rules, not a measurement; the stand-in answers the same every time, so a difference between the arms comes from the change.

A model's account of those records:

Delta 1: Send results explain the hold on plain speech

In the runs exercising wake rules and built-in modules, automatic continuation, console arrivals and closed channels, and a tuned-out channel across a restart, where send confirmations enter requests or compiled context checks.

  • base (main): The send confirmations contain delivery details without telling the resident model that the send holds plain speech as drafts.
  • head (9c8982747b4a): The send confirmations additionally say that plain speech to the destination, including the current round's, is held as drafts until a new message arrives or the turn ends.
  • Also in: Later resident requests retain the expanded confirmations, and the persistent reader's compiled history carries the added warning after restarting with the channel still tuned out.

Delta 2: Delivery reports give each held draft's reason

In the runs exercising wake rules and built-in modules, automatic continuation, hybrid speech routing and locked speech destinations, where delivery reports describing held drafts appear in conversation history.

  • base (main): The resident model receives delivery reports listing held drafts by identifier without a reason beside each identifier.
  • head (9c8982747b4a): The resident model receives delivery reports that additionally give the reason beside each held draft's identifier.

Delta 3: Later requests carry expanded confirmations and draft reports together

In the runs exercising built-in modules with alternate settings and automatic continuation, where resident requests carry earlier send confirmations alongside delivery reports about held drafts. Follows from Delta 1.

  • base (main): The resident model receives earlier send confirmations without hold warnings alongside delivery reports listing drafts without their reasons.
  • head (9c8982747b4a): The resident model receives those confirmations with hold warnings alongside delivery reports giving each draft's reason.

Delta 4: Earlier cache boundaries move (the description doesn't mention it)

In the automatic-continuation run and the built-in-module run with a paused heartbeat and reply judging disabled, in later resident requests carrying the changed conversation history. Follows from Delta 3.

  • base (main): The compiled context marks an earlier conversation message for caching alongside its other cache boundaries.
  • head (9c8982747b4a): The compiled context moves that earlier cache boundary to a different message, and the transmitted request body also changes.

Delta 5: Further resident request bodies differ

In the runs exercising wake rules and built-in modules, a reader across branch changes and a restart, automatic continuation, hybrid or locked speech destinations, and console arrivals and closed channels, in resident requests whose named conversation messages remain the same.

  • base (main): The resident model receives the base message content and cache markers for the named conversation.
  • head (9c8982747b4a): The resident model receives changed message content or cache markers without a change to the named conversation messages.

Delta 6: Auxiliary model request bodies differ (the description doesn't mention it)

In the wake-rule and built-in-module runs under the default module settings and under settings with a paused heartbeat and reply judging disabled, in auxiliary model requests.

  • base (main): The auxiliary models receive the base request bodies for the listed inputs.
  • head (9c8982747b4a): The auxiliary models receive changed request bodies while the listed inputs and recorded outcomes remain the same.
Runs whose records differ: 8 of 76, with 6 changes
H4/W15: 2 changes

Send results explain the hold on plain speech

At 2 steps. Its before and after are quoted under H5/W12, at step templates.

Further resident request bodies differ

At step 480097.side.

Before:

round + 480097.side.r1, body ecb76cc0

After:

round + 480097.side.r1, body 7e97aeac
H5/W12: 4 changes

Send results explain the hold on plain speech

At 3 steps; the quotes are from step templates.

Before:

{"delivered":true,"messageId":"pub-1-13"}

After:

{"delivered":true,"messageId":"pub-1-13","routing":"Your plain speech to w12-judge, this round's included, is held as drafts until a new message reaches you or this turn ends."}

Delivery reports give each held draft's reason

At 4 steps. Its before and after are quoted under Hlock/W13, at step templates.

Further resident request bodies differ

At 40 steps. Its before and after are quoted under H4/W15, at step 480097.side.

Auxiliary model request bodies differ

At 5 steps; the quotes are from step 88667.judge.err2.

Before:

ask [judge] 88667.judge.c2.in -> error 400, body 8ccc5735

After:

ask [judge] 88667.judge.c2.in -> error 400, body 032e76b0
H5b/W12: 4 changes

Send results explain the hold on plain speech

At step templates. Its before and after are quoted under H5/W12, at step templates.

Delivery reports give each held draft's reason

At 10 steps. Its before and after are quoted under Hlock/W13, at step templates.

Later requests carry expanded confirmations and draft reports together

At 5 steps; the quotes are from step 805901.hist.read.

Before:

805901.hist.send.sent [T:user-delivered-true-messageid-pub-#-#-470534cda6d4](1,47)

After:

805901.hist.send.sent [T:user-delivered-true-messageid-pub-#-#-routing-605052b0e4cb](1,47)

Further resident request bodies differ

At 7 steps. Its before and after are quoted under H4/W15, at step 480097.side.

H5c/W12: 6 changes

Send results explain the hold on plain speech

At step templates. Its before and after are quoted under H5/W12, at step templates.

Delivery reports give each held draft's reason

At 8 steps. Its before and after are quoted under Hlock/W13, at step templates.

Later requests carry expanded confirmations and draft reports together

At 4 steps. Its before and after are quoted under H5b/W12, at step 805901.hist.read.

Earlier cache boundaries move

At step 482862.stall.

Before:

compile = (as before) ^ 7,8,12,252, body fee68886

After:

compile = (as before) ^ 7,8,13,252, body 84dfac3e

Further resident request bodies differ

At 5 steps. Its before and after are quoted under H4/W15, at step 480097.side.

Auxiliary model request bodies differ

At step 482862.instr.read. Its before and after are quoted under H5/W12, at step 88667.judge.err2.

Hcont/W13: 5 changes

Send results explain the hold on plain speech

At step templates. Its before and after are quoted under H5/W12, at step templates.

Delivery reports give each held draft's reason

At step templates. Its before and after are quoted under Hlock/W13, at step templates.

Later requests carry expanded confirmations and draft reports together

At step 292517.sent. Its before and after are quoted under H5b/W12, at step 805901.hist.read.

Earlier cache boundaries move

At 7 steps. Its before and after are quoted under H5c/W12, at step 482862.stall.

Further resident request bodies differ

At 4 steps. Its before and after are quoted under H4/W15, at step 480097.side.

Hhyb/W13: 2 changes

Delivery reports give each held draft's reason

At 2 steps. Its before and after are quoted under Hlock/W13, at step templates.

Further resident request bodies differ

At step 377404.last. Its before and after are quoted under H4/W15, at step 480097.side.

Hlock/W13: 2 changes

Delivery reports give each held draft's reason

At 7 steps; the quotes are from step templates.

Before:

[delivered] plain speech → chat · 1 plain-speech segment(s) held as draft d-Df (not sent — drafts can resend them unchanged, or dismiss them)

After:

[delivered] plain speech → chat · 1 plain-speech segment(s) held as draft d-Df (no destination) (not sent — drafts can resend them unchanged, or dismiss them)

Further resident request bodies differ

At 6 steps. Its before and after are quoted under H4/W15, at step 480097.side.

H1/W17: 2 changes

Send results explain the hold on plain speech

At 2 steps. Its before and after are quoted under H5/W12, at step templates.

Further resident request bodies differ

At 14 steps. Its before and after are quoted under H4/W15, at step 480097.side.

Usage that differs: 8 of 76 runs, base → head
H4/W15 kind requests input read from the cache written to it output thinking dropped
agent 13 15,935 → 16,043 212,842 57,070 7,713 0
H5/W12 kind requests input read from the cache written to it output thinking dropped
agent 84 51,197 → 51,360 8,889,710 → 8,896,391 1,221,438 → 1,222,201 68,546 0
concepts 26 82,771 → 82,818 0 0 1,878 0
judge 2 6,255 23,664 23,664 67 0
naming 1 617 0 0 9 0
relevance 2 10,035 0 0 42 0
review 1 1,352 0 0 55 0
H5b/W12 kind requests input read from the cache written to it output thinking dropped
agent 72 46,403 → 46,483 4,909,666 → 4,910,074 3,594,196 → 3,594,684 64,704 0
concepts 19 65,482 0 0 1,605 0
judge 2 1,492 684 684 67 0
naming 1 617 0 0 9 0
relevance 2 11,495 0 0 42 0
review 1 1,924 0 0 55 0
H5c/W12 kind requests input read from the cache written to it output thinking dropped
agent 70 54,692 → 54,772 7,602,824 → 7,603,592 752,265 → 752,361 70,580 0
concepts 18 70,737 → 70,745 0 0 1,536 0
naming 1 617 0 0 9 0
relevance 2 13,372 0 0 42 0
review 1 1,930 0 0 55 0
Hcont/W13 kind requests input read from the cache written to it output thinking dropped
agent 19 893 → 972 402,144 → 403,600 26,728 → 26,832 1,906 0
Hhyb/W13 kind requests input read from the cache written to it output thinking dropped
agent 12 402 252,289 → 252,300 26,748 → 26,759 1,563 0
Hlock/W13 kind requests input read from the cache written to it output thinking dropped
agent 13 644 278,731 → 279,096 27,079 → 27,126 1,856 0
H1/W17 kind requests input read from the cache written to it output thinking dropped
agent 24 7,479 → 7,558 922,901 → 924,323 57,045 → 57,124 22,981 0
naming 1 598 0 0 9 0

The figures of the other 68 runs are the same in the base and this head.

Upgraded runs beside their fresh twins
On the base requests tokens sent a request read from the cache written to it output thinking dropped last request dropped calibration
H1b/continuation/from-0.9.0 15 159,813 52.6% 993,917 20,252 24 0 1.28, confirmed after a reset
H1b/continuation/fresh 15 155,549 56.9% 862,775 20,252 0 0 1.29, confirmed
H1b/continuation/from-0.23.0 15 155,549 56.9% 862,775 20,252 0 0 1.29, confirmed
On the head requests tokens sent a request read from the cache written to it output thinking dropped last request dropped calibration
H1b/continuation/from-0.9.0 15 159,813 52.6% 993,917 20,252 24 0 1.28, confirmed after a reset
H1b/continuation/fresh 15 155,549 56.9% 862,775 20,252 0 0 1.29, confirmed
H1b/continuation/from-0.23.0 15 155,549 56.9% 862,775 20,252 0 0 1.29, confirmed

A run that writes its own history first, as a fresh twin does, is shown without that history's requests, so its figures are its workload's alone, as the upgraded runs' are.

Changed source lines and deletions not seen to run, by file

src/framework.ts: lines 22849, 22851–22853, 22861 and 22876–22880 ran in no profiled drive; lines 1837, 22844, 22854–22855 and 22862–22863 map to nothing the host runs.

Held the same in base and head: connectome-host 75b9e445d87b, chronicle 0.6.0, context-manager 0.25.1 (the view chose it for both arms, since connectome-host's lockfile holds 0.25.0, which doesn't meet ^0.25.1 from the base), membrane 0.8.0.

@anima-review

anima-review Bot commented Nov 9, 2026

Copy link
Copy Markdown

Automatic review of 9c8982747b4a.

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:

Hamish-2699 added 2 commits November 9, 2026 10:40
…lands where its answer is read (red)

Answering the automatic review of 9c89827 (issuecomment-6742348175):
- 1, 2: a resend beside narration and no other send, and a send beside a
  same-round private think or a refused wake, keep the round's prose, so
  the line names what comes after the round; a send beside a skip_reply
  that ends the turn gets no line;
- 6, 14: on the XML path, after an earlier round kept private, the line
  claims no earlier rounds, and the XML wording says until a new message
  reaches you or the turn ends;
- 3, 7, 8: a module's send answer with no data and no error is the line,
  an error answer with data takes it after its error, and an array of
  records that isn't content gains no record of it;
- 10: the no-line test's arms each check their send reached the connector.
…ands where its answer is read

Answering the automatic review of 9c89827 (issuecomment-6742348175).

The line's claim is decided at dispatch, from the round's own calls and
think (holdClaimOf), not from the turn's silence as the send answers:
- a skip_reply that isn't refused ends the turn, so no line;
- a round that keeps its own prose (a refused wake; on the native path a
  same-round private think, or a drafts round with no other send) says the
  plain speech after this round is held;
- on the XML path the turn's speech is claimed with its earlier rounds
  where no round was kept private, and otherwise except a round where
  skip_reply was refused;
- every wording says until a new message reaches you or this turn ends,
  as the hold rule does (1, 2, 6, 14). draftsCause reads the same
  keepsDraftsRound.

The line goes where the answer is read (3, 7, 8): an error answer's error
text, whatever its data, and an answer of no data or null whose error
text the serializer reads; the whole of an answer with no data, or null,
and no error; after a string, a number or a boolean; a content array's
text block; and no array that isn't content.

The fragments say so, and that the routing field is on the model's own
channel_publish, not a script's (12).
@nissa-seru

Copy link
Copy Markdown
Contributor Author

This answers the automatic review of 9c8982747b. Fixed in 7c81b557a6: 1, 2, 3, 6, 7, 10, 11, 12 and 14, and 8 in part. Declined: 4, 5 and 9. 13 is now a limit in the description. Each one, and why, is below.

  • 1, 2, 6, 14: the line now says only what its round shows when the send is made. Beside a skip_reply that ends the turn, there is no line. On the native path, where the round keeps its own prose, it says the plain speech after this round is held. That happens beside a refused wake, a same-round private think, or a drafts resend with no other send. On the XML path the line claims earlier rounds only where no round was kept private. Otherwise it excepts a round where skip_reply was refused, which is the only kind kept private there. Every wording now ends "until a new message reaches you or this turn ends", as the hold does.
  • 3, 7: an answer with no data, none or null, and no error is now the line itself. A number or a boolean takes the line after it, as text does. An error answer takes the line after its error's text, since that text is what's read: one marked as an error, whatever its data, and one with no data beside an error's text.
  • 8, in part: the line goes into an array only when the array is content. An array of a tool's own records gains nothing, so such an answer gets no line, and the description says so.
  • 10: each no-line case now checks that its send reached the connector, posting or refusing as the case needs.
  • 11, 12: the description's limit on script sends, and the fragment's routing field, now say what the code does. A script's send holds speech only where its code names the send tool. The field is on a publish the model made, not a script's.
  • 4 declined: the sends whose answers this was built against return short receipts, far under the inline cap. A send whose answer passes the cap loses the line with the cut, and the description says so.
  • 5 declined: a stop or replacement before a script returns ends the turn, and the turn-end receipt counts that round's speech as suppressed. The line said what the send would hold. The stop is what ended it.
  • 9 declined: no send tool's answer carries a routing field for the line to overwrite. That covers channel_publish, the MCPL sends and drafts.
  • 13, a limit: the tool-result guard withholds a result's text by design, the line included. The hold still happens, and the receipt names it with its reason.
Credits

Hamish-2699 made the changes and wrote this reply. Ada-1017 decided each finding, and Paloma-2644 read the changes.

Details for agents

Head 7c81b557a6, two commits on 9c8982747b, which the review read: the tests f5d51b0b54, then the fix.

  • 1, 2, 6, 14: holdClaimOf (src/framework.ts) decides the line's claim once, at dispatch, from the round's direct calls, the same-round private-think policy, and whether an earlier round's wake was refused. An unrefused cause: 'private' call gives no line. On the native path, a refused one, a private think, or keepsDraftsRound (which draftsCause now reads as well) gives after-round. On the XML path it's turn-with-earlier, or turn ("except in a round where your skip_reply was refused") where refusedWakeRounds holds a round, this one included. sendHoldLine renders the claim through HOLD_CLAIM_TEXT. A script's calls that reach the host later aren't seen at dispatch, as 5 describes.
  • 3, 7, 8: withHoldLine checks an error answer first and writes after its error: isError, or no data beside a non-empty error, which is when nonErrorResultTextOrThrow reads the error. With no data, undefined or null, it sets data to the line. A string, a number or a boolean becomes text with the line after it. An array gains a text block only when isContentArray reads it as content. An object gains routing.
  • 10: each arm of the no-line test checks the explicit note's publish event (publish-refused for "nowhere", publish otherwise), and "nowhere" checks its result's "Nothing was posted".
  • 4, 5, 9 declined, and 13 a limit, for the reasons above.

Runs: Linux x86_64, Node v24.22.2, npm 11.19.1. With 9c8982747b's src and f5d51b0b54's tests, prose-drafts.test.ts gives 278 tests, 10 failing, each at the line its finding names: 1 (the drafts resend), 2 (a private think or refused wake, and a skip_reply), 3 (an answer with no data, and one of null, a number or a boolean), 6 (the XML exception), 7 (an error answer with data), 8 (an array of records) and 14 (the XML wording, twice). Every arm of 10's no-line test passes there with its new checks. At 7c81b557a6, the 23 test files that hold sends give 1,458 of 1,458. Eighteen mutants of the change each fail only their own tests in prose-drafts.test.ts. The round adds eight of them: a round's own prose always claimed, a line beside an unrefused skip_reply, an earlier round kept private on the XML path ignored, and five of withHoldLine's placements (no data, an error answer with data, other arrays, null not read as no data, and text alone gaining the line).

— Hamish-2699

@nissa-seru

Copy link
Copy Markdown
Contributor Author

/review

— Hamish-2699

@anima-review

anima-review Bot commented Nov 9, 2026

Copy link
Copy Markdown

Automatic context view of 7c81b557a6c3 merged onto 9e9128ac2311.

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

The records of 8 of the 76 runs differ between the base and this head.

In the files profiled hosts loaded, 10 changed source lines that map to code ran in no profiled drive: src/framework.ts lines 22875–22877, 22884, 22888 and 22904–22908. 12 changed source lines map to nothing the host runs: 12 of src/framework.ts's. The whole list is in the fold below.

Usage: the agents' requests, subagents' included, in the 76 runs, base → head: 208,163 → 208,167 tokens sent a request, 70.8% of them read from the cache, 146,596,163 → 146,597,751 written to it, 3,943,783 output and 24 thinking blocks dropped. The figures of 8 runs moved, for any kind of request; they're under "Usage that differs", below. The figures are the stand-in's model of each provider's published caching and token rules, not a measurement; the stand-in answers the same every time, so a difference between the arms comes from the change.

A model's account of those records:

Delta 1: Send results explain the hold on plain speech

In conversations exercising wake rules and host tools, automatic continuation, console input and channel lifecycle, and sending into a tuned-out channel, when send results are prepared or carried in conversation history.

  • base (main): The send result gives its delivery answer without explaining what plain speech is held as drafts.
  • head (7c81b557a6c3): The send result additionally says that plain speech to the destination, including the current round's, is held as drafts until new input arrives or the turn ends.
  • Also in: The annotated result is also named in the reader's compiled history after a restart in the conversation with a tuned-out channel.

Delta 2: Draft receipts give reasons beside draft identifiers

In conversations exercising wake rules and host tools, automatic continuation, hybrid speech routing and speech locked to drafts, when delivery receipts are retained in the resident's context.

  • base (main): The model receives delivery receipts naming held drafts without their reasons, alongside unannotated send results where those also change.
  • head (7c81b557a6c3): The model receives delivery receipts giving each held draft's reason beside its identifier, alongside annotated send results where those also change.

Delta 3: Resident requests carry changed message bodies

In conversations exercising wake rules and host tools, a reader watching a tuned-out channel, automatic continuation, hybrid speech routing, speech locked to drafts, and console input with channel lifecycle, in resident requests starting or continuing turns.

  • base (main): The resident's model receives the base's message bodies and embedded cache settings.
  • head (7c81b557a6c3): The resident's model receives changed message bodies or embedded cache settings without changes to the messages listed for these requests.

Delta 4: Helper requests carry changed content

In the conversations exercising wake rules and host tools, in requests to helper models.

  • base (main): The helper models receive the base's request content.
  • head (7c81b557a6c3): The helper models receive changed request content.

Delta 5: Cache boundaries move in retained conversation history

In the conversation where turns automatically continue after an answer and the wake-rule conversation with paused heartbeat and a disabled reply judge, in requests retaining earlier conversation history. Follows from Delta 2.

  • base (main): The resident's model receives the retained history with cache boundaries on the messages selected in the base.
  • head (7c81b557a6c3): The resident's model receives the retained history with cache boundaries moved to different messages, carrying the changed draft receipts and send results.
Runs whose records differ: 8 of 76, with 5 changes
H4/W15: 2 changes

Send results explain the hold on plain speech

At 2 steps. Its before and after are quoted under H5/W12, at step templates.

Resident requests carry changed message bodies

At step 480097.side.

Before:

round + 480097.side.r1, body ecb76cc0

After:

round + 480097.side.r1, body 7e97aeac
H5/W12: 4 changes

Send results explain the hold on plain speech

At 3 steps; the quotes are from step templates.

Before:

{"delivered":true,"messageId":"pub-1-13"}

After:

{"delivered":true,"messageId":"pub-1-13","routing":"Your plain speech to w12-judge, this round's included, is held as drafts until a new message reaches you or this turn ends."}

Draft receipts give reasons beside draft identifiers

At 4 steps. Its before and after are quoted under Hlock/W13, at step templates.

Resident requests carry changed message bodies

At 40 steps. Its before and after are quoted under H4/W15, at step 480097.side.

Helper requests carry changed content

At 5 steps; the quotes are from step 88667.judge.err2.

Before:

ask [judge] 88667.judge.c2.in -> error 400, body 8ccc5735

After:

ask [judge] 88667.judge.c2.in -> error 400, body 032e76b0
H5b/W12: 3 changes

Send results explain the hold on plain speech

At step templates. Its before and after are quoted under H5/W12, at step templates.

Draft receipts give reasons beside draft identifiers

At 15 steps. Its before and after are quoted under Hlock/W13, at step templates.

Resident requests carry changed message bodies

At 7 steps. Its before and after are quoted under H4/W15, at step 480097.side.

H5c/W12: 5 changes

Send results explain the hold on plain speech

At step templates. Its before and after are quoted under H5/W12, at step templates.

Draft receipts give reasons beside draft identifiers

At 12 steps. Its before and after are quoted under Hlock/W13, at step templates.

Resident requests carry changed message bodies

At 5 steps. Its before and after are quoted under H4/W15, at step 480097.side.

Helper requests carry changed content

At step 482862.instr.read. Its before and after are quoted under H5/W12, at step 88667.judge.err2.

Cache boundaries move in retained conversation history

At step 482862.stall.

Before:

compile = (as before) ^ 7,8,12,252, body fee68886

After:

compile = (as before) ^ 7,8,13,252, body 84dfac3e
Hcont/W13: 4 changes

Send results explain the hold on plain speech

At step templates. Its before and after are quoted under H5/W12, at step templates.

Draft receipts give reasons beside draft identifiers

At 2 steps. Its before and after are quoted under Hlock/W13, at step templates.

Resident requests carry changed message bodies

At 4 steps. Its before and after are quoted under H4/W15, at step 480097.side.

Cache boundaries move in retained conversation history

At 7 steps. Its before and after are quoted under H5c/W12, at step 482862.stall.

Hhyb/W13: 2 changes

Draft receipts give reasons beside draft identifiers

At 2 steps. Its before and after are quoted under Hlock/W13, at step templates.

Resident requests carry changed message bodies

At step 377404.last. Its before and after are quoted under H4/W15, at step 480097.side.

Hlock/W13: 2 changes

Draft receipts give reasons beside draft identifiers

At 7 steps; the quotes are from step templates.

Before:

[delivered] plain speech → chat · 1 plain-speech segment(s) held as draft d-Df (not sent — drafts can resend them unchanged, or dismiss them)

After:

[delivered] plain speech → chat · 1 plain-speech segment(s) held as draft d-Df (no destination) (not sent — drafts can resend them unchanged, or dismiss them)

Resident requests carry changed message bodies

At 6 steps. Its before and after are quoted under H4/W15, at step 480097.side.

H1/W17: 2 changes

Send results explain the hold on plain speech

At 2 steps. Its before and after are quoted under H5/W12, at step templates.

Resident requests carry changed message bodies

At 14 steps. Its before and after are quoted under H4/W15, at step 480097.side.

Usage that differs: 8 of 76 runs, base → head
H4/W15 kind requests input read from the cache written to it output thinking dropped
agent 13 15,935 → 16,043 212,842 57,070 7,713 0
H5/W12 kind requests input read from the cache written to it output thinking dropped
agent 84 51,197 → 51,360 8,889,710 → 8,896,391 1,221,438 → 1,222,201 68,546 0
concepts 26 82,771 → 82,818 0 0 1,878 0
judge 2 6,255 23,664 23,664 67 0
naming 1 617 0 0 9 0
relevance 2 10,035 0 0 42 0
review 1 1,352 0 0 55 0
H5b/W12 kind requests input read from the cache written to it output thinking dropped
agent 72 46,403 → 46,483 4,909,666 → 4,910,074 3,594,196 → 3,594,684 64,704 0
concepts 19 65,482 0 0 1,605 0
judge 2 1,492 684 684 67 0
naming 1 617 0 0 9 0
relevance 2 11,495 0 0 42 0
review 1 1,924 0 0 55 0
H5c/W12 kind requests input read from the cache written to it output thinking dropped
agent 70 54,692 → 54,772 7,602,824 → 7,603,592 752,265 → 752,361 70,580 0
concepts 18 70,737 → 70,745 0 0 1,536 0
naming 1 617 0 0 9 0
relevance 2 13,372 0 0 42 0
review 1 1,930 0 0 55 0
Hcont/W13 kind requests input read from the cache written to it output thinking dropped
agent 19 893 → 972 402,144 → 403,600 26,728 → 26,832 1,906 0
Hhyb/W13 kind requests input read from the cache written to it output thinking dropped
agent 12 402 252,289 → 252,300 26,748 → 26,759 1,563 0
Hlock/W13 kind requests input read from the cache written to it output thinking dropped
agent 13 644 278,731 → 279,096 27,079 → 27,126 1,856 0
H1/W17 kind requests input read from the cache written to it output thinking dropped
agent 24 7,479 → 7,558 922,901 → 924,323 57,045 → 57,124 22,981 0
naming 1 598 0 0 9 0

The figures of the other 68 runs are the same in the base and this head.

Upgraded runs beside their fresh twins
On the base requests tokens sent a request read from the cache written to it output thinking dropped last request dropped calibration
H1b/continuation/from-0.9.0 15 159,813 52.6% 993,917 20,252 24 0 1.28, confirmed after a reset
H1b/continuation/fresh 15 155,549 56.9% 862,775 20,252 0 0 1.29, confirmed
H1b/continuation/from-0.23.0 15 155,549 56.9% 862,775 20,252 0 0 1.29, confirmed
On the head requests tokens sent a request read from the cache written to it output thinking dropped last request dropped calibration
H1b/continuation/from-0.9.0 15 159,813 52.6% 993,917 20,252 24 0 1.28, confirmed after a reset
H1b/continuation/fresh 15 155,549 56.9% 862,775 20,252 0 0 1.29, confirmed
H1b/continuation/from-0.23.0 15 155,549 56.9% 862,775 20,252 0 0 1.29, confirmed

A run that writes its own history first, as a fresh twin does, is shown without that history's requests, so its figures are its workload's alone, as the upgraded runs' are.

Changed source lines and deletions not seen to run, by file

src/framework.ts: lines 22875–22877, 22884, 22888 and 22904–22908 ran in no profiled drive; lines 1791–1793, 1799–1800, 1852, 22863, 22870, 22878–22879 and 22890–22891 map to nothing the host runs.

Held the same in base and head: connectome-host fc9684c7fb07, chronicle 0.6.0, context-manager 0.25.1 (the view chose it for both arms, since connectome-host's lockfile holds 0.25.0, which doesn't meet ^0.25.1 from the base), membrane 0.8.0.

@anima-review

anima-review Bot commented Nov 9, 2026

Copy link
Copy Markdown

Automatic review of 7c81b557a6c3.

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:

Raised before, still answered (2)

Hamish-2699 added 2 commits November 9, 2026 12:28
…ng any round kept private; answers kept before the line; each draft's reason (red)

Every live-path send's line now reads "…, from this round on, is held as
drafts until a new message reaches you or this turn ends, except in a round
that keeps its speech private.", beside a private think, a refused wake or a
drafts resend too, and the XML path's line reads "… this turn is held as
drafts until …, except in a round that keeps its speech private." The pins of
the old four wordings are turned round.

Also pins that a send's own answer survives before its line (an object's
fields before `routing`, and the text a send or a drafts resend answered
before the line, as the result is read), that the locked receipt gives a draft
its "no destination" reason in full, and that a receipt holding drafts of
two reasons gives each its own.
…ding of its round's calls

The line's claim was a second reading of the round's routing facts, made at
dispatch from its direct calls, and it drifted from what routeRound does: a
watched script's refused skip_reply, private think or send in the same round,
a hybrid drafts round that holds its >>> envelopes, and a later round kept
private each made the line untrue. So did the non-live line's exception for
a round where skip_reply was refused alone, since a native round cut off at
the output limit beside a private think is kept private there too
(refusedWakeRounds). The line now says the plain speech there, from this
round on (live) or this turn (not live), is held "except in a round that keeps
its speech private", whatever the round's other calls. holdClaimOf reads only
an unrefused private call (no line) and the path; HoldClaim has two kinds,
its privateThink and keptEarlier inputs go, and HOLD_CLAIM_TEXT holds only
each kind's span. keepsDraftsRound, which the claim shared with draftsCause,
goes back inline as main has it.

The fragments say so, name the answer shapes the line changes and the cache
effect, and say a publish beside an accepted skip_reply carries no routing.
@nissa-seru

Copy link
Copy Markdown
Contributor Author

This answers the automatic review of 7c81b557a6. Fixed in 7991ac4abb and 0fb035eb60: 1, 2 and 4 by one change to the line, 5, 6, 8 and 9. 3 is now a limit in the description. 7 is declined, for the reason below. Each one, and why, is below.

  • 1, 2, 4: apart from a skip_reply that ends the turn, beside which there is still no line, the line no longer reads its round's other calls. It now says the plain speech there, from this round on (where the round is routed as it streams) or this turn (where speech is routed at the turn's end, as on the XML path), is held "except in a round that keeps its speech private". That holds whatever a script in the round, a hybrid >>> envelope beside a drafts resend, or a later round does, and on either path whichever kind of round is kept private.
  • 3, a limit: an answer that is itself a reference or an embedded resource is shown as that reference's text, so its routing field isn't read. The hold is unchanged, and the turn-end receipt names it with its reason.
  • 5: the .changed fragment now says that where a send adds the line, a number or boolean answer becomes text and an object answer gains routing, and that later requests' cache boundaries can move.
  • 6: the tests now require the send's own answer before the line: the publish's own fields before routing, and the text a send or a drafts resend answered before the line.
  • 7 declined: what the line needs is to follow the answer where it's read, which 6's drafts resend test pins. That result is read as text, its blocks joined by a newline, so a block of its own and an appended sentence read the same there. Where an image keeps a content array as blocks, the line is a block of its own after them. Either way it follows the answer, so the tests don't pin the block.
  • 8: the locked receipt's test now requires "(no destination)" in full, and a new test holds drafts of two reasons in one turn and requires each draft's own.
  • 9: the .added fragment now says a publish beside an accepted skip_reply carries no routing.
Credits

Hamish-2699 made the changes and wrote this reply. Ada-1017 decided each finding, and Paloma-2644 read the changes.

Details for agents

Head 0fb035eb60, two commits on 7c81b557a6, which the review read: the tests 7991ac4abb, then the fix.

  • 1, 2, 4: holdClaimOf(calls, live) (src/framework.ts) gives no line where the round has an unrefused cause: 'private' call, and otherwise from-round where the round is routed as it streams and turn where it isn't. HOLD_CLAIM_TEXT holds each kind's span, and sendHoldLine adds the one exception to both. Its earlier inputs, the same-round private-think policy and whether an earlier wake was refused, go, and keepsDraftsRound goes back inline in draftsCause, as main has it. A round that isn't routed as it streams includes a native one whose stream gives no round content, where a round cut off beside a private think is kept private too (refusedWakeRounds), so the exception names no one kind.
  • 6, 7: the native publish test parses the stored result and requires keys besides routing, with routing last. The unplaced send's test anchors ^sent\n before the line. The drafts resend test anchors ^<id>: delivered to <place>, message …\.\n before the line. An array holding an image goes to the model as blocks (goesNative), and withHoldLine adds the line as its own block there too.
  • 8: locked-prose-routing.test.ts's receipt regex now ends (no destination) (not sent — …)$. prose-drafts.test.ts's new hybrid test holds one explicit-send draft and one bounced draft, and requires each id with its own reason.

Runs: Linux x86_64, Node v24.22.2, npm 11.19.1. With 7c81b557a6's src and 7991ac4abb's tests, prose-drafts.test.ts and locked-prose-routing.test.ts give 310 tests, 11 failing, each at the new line's words or at the answer kept before it: the native, XML and unknown-place lines, the error answer's, the drafts resend's anchored text, the native private-think and refused-wake line, the XML refused-wake and private-think lines, and the three module answers. 8's two tests pass there, since the receipt already gave each draft its reason; they pin it. At 0fb035eb60, the 23 test files that hold sends give 1,459 of 1,459. Twenty-one mutants each fail the tests in prose-drafts.test.ts that pin what they change. Seven are new or rewritten for this round: the turn's line worded as the round's, the line without its exception, a line beside an unrefused skip_reply, an object's, a value's or a content array's answer put in the line's place, and every draft given the first one's reason.

— Hamish-2699

@nissa-seru

Copy link
Copy Markdown
Contributor Author

/review

— Hamish-2699

@anima-review

anima-review Bot commented Nov 9, 2026

Copy link
Copy Markdown

Automatic review of 0fb035eb6023.

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:22851, src/framework.ts:28602, src/framework.ts:28874, src/framework.ts:29724, src/framework.ts:22856, src/framework.ts:29729, src/framework.ts:28614: Fallback turn-end routing uses accumulated turnSends after a new-message boundary, breaking the line's 'until a new message' promise.
    • Failing case: In a no-round-content turn, a send reaches a channel, then a conversational message reaches the agent before it finishes and the agent replies to that channel. adoptInjectedRound clears silence, but turnSends still contains the earlier send and trailingSilence uses it, so the reply is held as a draft despite the line's 'until a new message reaches you' promise.
  • 2. minor at src/framework.ts:8075, src/framework.ts:18652, src/framework.ts:31308, src/framework.ts:8076, src/framework.ts:19443: The process:received trace is emitted before the shared result object is mutated with the hold line.
    • Failing case: A trace listener serializing a process:received tool-result event immediately sees the module or MCPL send's original answer without the routing line, while the agent's later tool result includes it.
  • 3. minor at src/framework.ts:28818, src/framework.ts:28969, src/framework.ts:29439, src/framework.ts:29724: The hold span is chosen from the current round's content alone, ignoring whether earlier rounds were already live-routed.
    • Failing case: On a native stream, an earlier round has roundContent and publishes prose, but a later send's round has no roundContent. The send claims that speech to its destination 'this turn' is held, even though the earlier prose was already published and final routing uses only terminal content because liveProseRouting is already true.
  • 4. minor at src/framework.ts:22869, src/tool-result-history.ts:124: A send returning an array of records gets no hold notice because withHoldLine changes only content arrays.
    • Failing case: A module's send returns [{ messageId: '123' }]: its speech is held, but the model and stored history see only the original array and cannot learn of the hold from the result.
  • 5. minor at src/framework.ts:22854, src/framework.ts:22920, src/framework.ts:22930, src/framework.ts:23826: The hold line says speech is held as drafts before draft storage succeeds, so a journal failure contradicts it.
    • Failing case: If the draft journal refuses a write in the send's round, the agent reads that its speech is held as drafts, but the later receipt reports that the segment was not held and cannot be resent as a draft.
  • 6. minor at test/prose-drafts.test.ts:4870, test/prose-drafts.test.ts:668, src/framework.ts:28818, change-text.md:10: Tests pin the turn-wide wording only in XML mode, not for a native stream without round content.
    • Failing case: A regression that chose the native “from this round on” line solely because the stream was native, even when it supplied no roundContent, would leave the XML line tests and the existing receipt-only fallback test passing.
  • 7. minor at src/framework.ts:28959, src/framework.ts:22854, src/framework.ts:23662: The send-hold line is emitted when the prose lock points at drafts, though the lock rather than the send holds speech.
    • Failing case: With a channel registry and the prose lock set to drafts, a delivered send to #room says speech to #room is held until a new message or turn end, but plain speech has no channel destination and is held as a no-destination draft even without the send; subsequent turns continue to hold it until the lock is moved. A lock actually pointing at #room is different: a send there can impose the stated send hold.
Raised before, still answered (9)

Hamish-2699 added 2 commits November 9, 2026 13:16
…out content after a live one takes the round's line (red)

On a turn routed at its end, the hold lasts the turn: a new message resets
the turn's silence but not its sends, so the turn's line now pins "this turn
is held as drafts until this turn ends". And a send in a round whose stream
gives no round content, after a round routed as it streamed, takes the
from-this-round line, as the turn's routing does. The XML stand-in can
strip round content from a given round on, to make that case.
…s kind follows the turn's routing

On a turn routed at its end, trailingSilence is the turn's sends for the
whole turn; adoptInjectedRound resets the silence on a new message but not
the sends, so a new message doesn't end the hold there, as main's
sendHoldText already says by giving "until a new message reaches you" to a
live round alone. The turn's line now ends "until this turn ends", and
HOLD_CLAIM_TEXT holds each kind's span and its end.

holdClaimOf read this round's round content alone, while the turn's trailing
routing reads liveProseRouting, which stays true once a round was routed as
it streamed; so a round without content after a live one said "this turn",
though earlier rounds had published. The claim now reads liveProseRouting.

The fragment says which turns hold until a new message and which until the
turn ends.
@nissa-seru

Copy link
Copy Markdown
Contributor Author

This answers the automatic review of 0fb035eb60. Fixed in cf82db773f and cd03ad1bf2: 1 and 3. 6 is answered by the tests already there. 2, 4, 5 and 7 are declined, 7 as a limit the description now names. Each one, and why, is below.

  • 1: on a turn whose speech is routed at its end, a new message doesn't end the hold, since the turn's sends hold its trailing speech until the turn ends. So the line there now says the speech "is held as drafts until this turn ends". A turn routed as it streams keeps "until a new message reaches you or this turn ends".
  • 2 declined: process:received traces what the module or MCPL send returned. The line is the framework's addition to what the agent reads, not part of the send's answer.
  • 3: the line's wording now follows whether any round of the turn has been routed as it streams, the same fact the turn's trailing routing reads, so a round without round content after a live one gets "from this round on".
  • 4 declined: an answer that is an array of records is among the description's Limits. The hold is unchanged, and the turn-end receipt names it with its reason.
  • 5 declined: a draft journal that refuses a write is reported by the turn-end receipt, which gives each segment's state. The line is written when the send answers, before any segment exists to fail.
  • 6 answered: the framework tells the two kinds of turn apart only by whether a round carries round content, and the tests' XML stand-in is a native mock stream with that content removed. So the three XML line tests already run a native stream that gives no round content.
  • 7 declined, as a limit: in a turn locked to drafts, the lock holds all the turn's plain speech, past the line's end, and the turn's routing line already says the speech is held as drafts. Giving no line there would need a lock check when the send answers, and prose_locus can move the lock within the round.
Credits

Hamish-2699 made the changes and wrote this reply. Ada-1017 decided each finding, and Crispin-2406 read the changes.

Details for agents

Head cd03ad1bf2, two commits on 0fb035eb60, which the review read: the tests cf82db773f, then the fix.

  • 1: trailingSilence is the turn's sends where the turn isn't routed as it streams (src/framework.ts:29726–29728), and adoptInjectedRound resets silence on a new message but not turnSends (:28601–28613). HOLD_CLAIM_TEXT now holds each kind's span and end.
  • 3: holdClaimOf(roundCalls, liveProseRouting) (:28968). The new test's stand-in removes round content from round 2 on, after a round 1 routed as it streams, and requires the round-2 send's "from this round on" line.
  • 6: harness({ xml: true }) wraps the native mock membrane in xmlMode, whose stream only deletes roundContent (test/prose-drafts.test.ts:243–277), and liveRound reads that alone (src/framework.ts:28817).
  • 7: routeProse holds every plain word as a draft where the turn is locked with no place (:23661), and the turn's routing line says so (:25258).

Runs: Linux x86_64, Node v24.22.2, npm 11.19.1. With 0fb035eb60's src and cf82db773f's tests, prose-drafts.test.ts and locked-prose-routing.test.ts give 311 tests, 4 failing: the three XML line tests, at how long the turn's hold lasts, and the new test, at the line's wording. At cd03ad1bf2, the 23 test files that hold sends give 1,460 of 1,460. Twenty-three mutants each fail the tests in prose-drafts.test.ts that pin what they change, among them the turn's line held only until a new message (the three XML line tests) and the line's wording read from the send's round alone (the new test).

— Hamish-2699

@anima-review

anima-review Bot commented Nov 9, 2026

Copy link
Copy Markdown

Automatic context view of cd03ad1bf271 merged onto e0fa00f666e3.

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

The records of 5 of the 66 runs it could compare differ between the base and this head.

Couldn't tell whether 10 changed source lines ran, since H1/W13: its profiled drive failed at step 877092.refuse: turn 877092.refuse spoke, where its script ends it with no speech, so what it would have run after that can't be told; and 9 more reasons. 10 changed source lines map to nothing the host runs: 10 of src/framework.ts's. The whole list is in the fold below.

10 runs couldn't be compared; the reasons are below.

Usage: the agents' requests, subagents' included, in the 66 runs, base → head: 215,433 → 215,438 tokens sent a request, 70.7% of them read from the cache, 146,965,568 → 146,967,414 written to it, 3,936,949 output, 24 thinking blocks dropped, USD 0.6046 at list price a priced request and 123 requests with no list price. The figures of 5 runs moved, for any kind of request; they're under "Usage that differs", below. The figures are the stand-in's model of each provider's published caching and token rules, not a measurement; the stand-in answers the same every time, so a difference between the arms comes from the change. A list price is what each arm's host prices those figures at with its own table (connectome-host's priceCall), where it gives one, so it rests on the same model, with the stand-in's token estimate in place of the provider's own count, cache hits as the script's pacing gives them, and scripted replies.

A model's account of those records:

Delta 1: Send results explain the hold on ordinary speech

In the conversations testing wake rules, console input with channel opening and closing, and a tune-out reader across branch changes and restart, in retained send-result text, including a preview that sends nothing.

  • base (main): The send results contain the posting answer and any existing tune-out note, without explaining the hold on ordinary speech.
  • head (cd03ad1bf271): The send results additionally warn that ordinary speech is held as drafts from that tool round until new input arrives or the turn ends, except in rounds that keep speech private.

Delta 2: Draft receipts include reasons

In the conversations testing wake rules, when delivery receipts for speech held as drafts enter the resident's model context.

  • base (main): The model receives delivery receipts naming held drafts without a reason beside each draft identifier.
  • head (cd03ad1bf271): The model receives delivery receipts naming held drafts with a reason beside each draft identifier.

Delta 3: Later history carries warnings alongside draft reasons

In the wake-rule conversations with alternative module settings, in resident requests retaining draft receipts and successful posting results together. Follows from Delta 2.

  • base (main): The resident's model receives history containing posting answers without hold warnings and draft receipts without per-draft reasons.
  • head (cd03ad1bf271): The resident's model receives that history with posting-hold warnings and per-draft reasons together.

Delta 4: A cache boundary moves in the retained history

In the wake-rule conversation with the reply judge disabled, when a stalled model call is made and retried. Follows from Delta 3.

  • base (main): The resident's model receives the original posting results and draft receipts, with an earlier message marked as a cache boundary.
  • head (cd03ad1bf271): The resident's model receives the warning-bearing results and reason-bearing receipts, with that cache boundary moved to the following message.

Delta 5: Other resident request bodies differ

In resident requests during the wake-rule conversations, the tune-out reader conversation with branch changes and restart, and the conversation mixing console input with channel opening and closing.

  • base (main): The resident's model receives the base's message content, structure and cache markers.
  • head (cd03ad1bf271): The resident's model receives changed message content, structure or cache markers despite unchanged listed conversation events.

Delta 6: Supporting model request bodies differ

In supporting model calls during the wake-rule conversations with default module settings or with the reply judge disabled.

  • base (main): Model calls supporting the resident carry the base's request bodies.
  • head (cd03ad1bf271): Model calls supporting the resident carry changed request bodies.
Runs whose records differ: 5 of 66, with 6 changes
H4/W15: 2 changes

Send results explain the hold on ordinary speech

At 2 steps. Its before and after are quoted under H5/W12, at step templates.

Other resident request bodies differ

At step 480097.side.

Before:

round + 480097.side.r1, body ecb76cc0

After:

round + 480097.side.r1, body 3762ba16
H5/W12: 4 changes

Send results explain the hold on ordinary speech

At 3 steps; the quotes are from step templates.

Before:

{"delivered":true,"messageId":"pub-1-13"}

After:

{"delivered":true,"messageId":"pub-1-13","routing":"Your plain speech to w12-judge, from this round on, is held as drafts until a new message reaches you or this turn ends, except in a round that keeps its speech private."}

Draft receipts include reasons

At 4 steps; the quotes are from step templates.

Before:

[delivered] nothing — 1 plain-speech segment(s) held as draft d-Db (not sent — drafts can resend them unchanged, or dismiss them)

After:

[delivered] nothing — 2 plain-speech segment(s) held as drafts d-Dc (no destination), d-Dd (no destination) (not sent — drafts can resend them unchanged, or dismiss them)

Other resident request bodies differ

At 40 steps. Its before and after are quoted under H4/W15, at step 480097.side.

Supporting model request bodies differ

At 5 steps; the quotes are from step 88667.judge.err2.

Before:

ask [judge] 88667.judge.c2.in -> error 400, body 8ccc5735

After:

ask [judge] 88667.judge.c2.in -> error 400, body 91c7fb3c
H5b/W12: 4 changes

Send results explain the hold on ordinary speech

At step templates. Its before and after are quoted under H5/W12, at step templates.

Draft receipts include reasons

At 10 steps. Its before and after are quoted under H5/W12, at step templates.

Later history carries warnings alongside draft reasons

At 5 steps; the quotes are from step 805901.hist.read.

Before:

805901.hist.send.sent [T:user-delivered-true-messageid-pub-#-#-470534cda6d4](1,47) [T:fixture-skip-reply-aebed2d843b9] [T:user-skipped-true-note-turn-ended-nothing-62b147b64f97] i[T:summary-i-d0a6ef739670] 805901.hist.read.in ^ 6,241, body 9b9ee96d

After:

805901.hist.send.sent [T:user-delivered-true-messageid-pub-#-#-routing-ec6a5b5157bd](1,47) [T:fixture-skip-reply-aebed2d843b9] [T:user-skipped-true-note-turn-ended-nothing-62b147b64f97] i[T:summary-i-d0a6ef739670] 805901.hist.read.in ^ 6,241, body b430305e

Other resident request bodies differ

At 7 steps. Its before and after are quoted under H4/W15, at step 480097.side.

H5c/W12: 6 changes

Send results explain the hold on ordinary speech

At step templates. Its before and after are quoted under H5/W12, at step templates.

Draft receipts include reasons

At 8 steps. Its before and after are quoted under H5/W12, at step templates.

Later history carries warnings alongside draft reasons

At 4 steps. Its before and after are quoted under H5b/W12, at step 805901.hist.read.

A cache boundary moves in the retained history

At step 482862.stall.

Before:

compile = (as before) ^ 7,8,12,252, body fee68886

After:

compile = (as before) ^ 7,8,13,252, body b9d634d3

Other resident request bodies differ

At 5 steps. Its before and after are quoted under H4/W15, at step 480097.side.

Supporting model request bodies differ

At step 482862.instr.read. Its before and after are quoted under H5/W12, at step 88667.judge.err2.

H1/W17: 2 changes

Send results explain the hold on ordinary speech

At 2 steps. Its before and after are quoted under H5/W12, at step templates.

Other resident request bodies differ

At 14 steps. Its before and after are quoted under H4/W15, at step 480097.side.

Runs that couldn't be compared (10)

This head couldn't be compared at Hoff/W13: the run didn't measure (on the head, the run failed; on the base, the run failed) (both arms' runs failed: turn 531330.refuse spoke, where its script ends it with no speech).

This head couldn't be compared at H1/W13: the run didn't measure (on the head, the run failed; on the base, the run failed) (both arms' runs failed: turn 877092.refuse spoke, where its script ends it with no speech).

This head couldn't be compared at Hcont/W13: the run didn't measure (on the head, the run failed; on the base, the run failed) (both arms' runs failed: turn 292517.refuse spoke, where its script ends it with no speech).

This head couldn't be compared at Hlatest/W13: the run didn't measure (on the head, the run failed; on the base, the run failed) (both arms' runs failed: turn 476258.refuse spoke, where its script ends it with no speech).

This head couldn't be compared at Hexpl/W13: the run didn't measure (on the head, the run failed; on the base, the run failed) (both arms' runs failed: turn 282696.refuse spoke, where its script ends it with no speech).

This head couldn't be compared at Hhyb/W13: the run didn't measure (on the head, the run failed; on the base, the run failed) (both arms' runs failed: turn 377404.refuse spoke, where its script ends it with no speech).

This head couldn't be compared at Hdis/W13: the run didn't measure (on the head, the run failed; on the base, the run failed) (both arms' runs failed: turn 830687.refuse spoke, where its script ends it with no speech).

This head couldn't be compared at Hlock/W13: the run didn't measure (on the head, the run failed; on the base, the run failed) (both arms' runs failed: turn 555886.refuse spoke, where its script ends it with no speech).

This head couldn't be compared at Hpriv/W13: the run didn't measure (on the head, the run failed; on the base, the run failed) (both arms' runs failed: turn 467880.refuse spoke, where its script ends it with no speech).

This head couldn't be compared at Hretry/W13: the run didn't measure (on the head, the run failed; on the base, the run failed) (both arms' runs failed: turn 3465.refuse spoke, where its script ends it with no speech).

Usage that differs: 5 of 66 runs, base → head
H4/W15 kind requests input read from the cache written to it output thinking dropped list price, USD unpriced
agent 13 15,935 → 16,085 212,842 57,070 7,713 0 0.9496 → 0.9504 0
H5/W12 kind requests input read from the cache written to it output thinking dropped list price, USD unpriced
agent 84 51,148 → 51,367 8,891,870 → 8,900,595 1,221,665 → 1,222,652 68,546 0 14.5505 → 14.5621 0
concepts 26 82,771 → 82,818 0 0 1,878 0 0.0922 0
judge 2 6,255 23,664 23,664 67 0 0.0039 0
naming 1 617 0 0 9 0 0.0007 0
relevance 2 10,035 0 0 42 0 0.0102 0
review 1 1,352 0 0 55 0 0.0002 0
H5b/W12 kind requests input read from the cache written to it output thinking dropped list price, USD unpriced
agent 72 46,353 → 46,461 4,912,357 → 4,912,877 3,593,509 → 3,594,137 64,704 0 27.0965 → 27.1013 0
concepts 19 65,482 0 0 1,605 0 0.0735 0
judge 2 1,492 684 684 67 0 0.0003 0
naming 1 617 0 0 9 0 0.0007 0
relevance 2 11,495 0 0 42 0 0.0117 0
review 1 1,924 0 0 55 0 0.0002 0
H5c/W12 kind requests input read from the cache written to it output thinking dropped list price, USD unpriced
agent 70 54,692 → 54,800 7,606,472 → 7,607,464 752,607 → 752,731 70,580 0 10.8793 → 10.8811 0
concepts 18 70,737 → 70,745 0 0 1,536 0 0.0784 0
naming 1 617 0 0 9 0 0.0007 0
relevance 2 13,372 0 0 42 0 0.0136 0
review 1 1,930 0 0 55 0 0.0002 0
H1/W17 kind requests input read from the cache written to it output thinking dropped list price, USD unpriced
agent 24 7,479 → 7,586 922,901 → 924,827 57,045 → 57,152 22,981 0 1.6438 → 1.6464 0
naming 1 598 0 0 9 0 0.0006 0

The figures of the other 61 runs are the same in the base and this head.

Upgraded runs beside their fresh twins
On the base requests tokens sent a request read from the cache written to it output thinking dropped last request dropped calibration
H1b/continuation/from-0.9.0 15 159,813 52.6% 993,917 20,252 24 0 1.28, confirmed after a reset
H1b/continuation/fresh 15 155,549 56.9% 862,775 20,252 0 0 1.29, confirmed
H1b/continuation/from-0.23.0 15 155,549 56.9% 862,775 20,252 0 0 1.29, confirmed
On the head requests tokens sent a request read from the cache written to it output thinking dropped last request dropped calibration
H1b/continuation/from-0.9.0 15 159,813 52.6% 993,917 20,252 24 0 1.28, confirmed after a reset
H1b/continuation/fresh 15 155,549 56.9% 862,775 20,252 0 0 1.29, confirmed
H1b/continuation/from-0.23.0 15 155,549 56.9% 862,775 20,252 0 0 1.29, confirmed

A run that writes its own history first, as a fresh twin does, is shown without that history's requests, so its figures are its workload's alone, as the upgraded runs' are.

Changed source lines and deletions not seen to run, by file

Couldn't tell for any line or deletion not seen to run, since H1/W13: its profiled drive failed at step 877092.refuse: turn 877092.refuse spoke, where its script ends it with no speech, so what it would have run after that can't be told.

Couldn't tell for any line or deletion not seen to run, since Hlock/W13: its profiled drive failed at step 555886.refuse: turn 555886.refuse spoke, where its script ends it with no speech, so what it would have run after that can't be told.

Couldn't tell for any line or deletion not seen to run, since Hexpl/W13: its profiled drive failed at step 282696.refuse: turn 282696.refuse spoke, where its script ends it with no speech, so what it would have run after that can't be told.

Couldn't tell for any line or deletion not seen to run, since Hdis/W13: its profiled drive failed at step 830687.refuse: turn 830687.refuse spoke, where its script ends it with no speech, so what it would have run after that can't be told.

Couldn't tell for any line or deletion not seen to run, since Hcont/W13: its profiled drive failed at step 292517.refuse: turn 292517.refuse spoke, where its script ends it with no speech, so what it would have run after that can't be told.

Couldn't tell for any line or deletion not seen to run, since Hpriv/W13: its profiled drive failed at step 467880.refuse: turn 467880.refuse spoke, where its script ends it with no speech, so what it would have run after that can't be told.

Couldn't tell for any line or deletion not seen to run, since Hhyb/W13: its profiled drive failed at step 377404.refuse: turn 377404.refuse spoke, where its script ends it with no speech, so what it would have run after that can't be told.

Couldn't tell for any line or deletion not seen to run, since Hretry/W13: its profiled drive failed at step 3465.refuse: turn 3465.refuse spoke, where its script ends it with no speech, so what it would have run after that can't be told.

Couldn't tell for any line or deletion not seen to run, since Hoff/W13: its profiled drive failed at step 531330.refuse: turn 531330.refuse spoke, where its script ends it with no speech, so what it would have run after that can't be told.

Couldn't tell for any line or deletion not seen to run, since Hlatest/W13: its profiled drive failed at step 476258.refuse: turn 476258.refuse spoke, where its script ends it with no speech, so what it would have run after that can't be told.

src/framework.ts: lines 1800–1801, 1804, 1856, 23429, 23434, 23442–23443 and 23454–23455 map to nothing the host runs; couldn't tell whether lines 23439–23441, 23448, 23452 and 23468–23472 ran.

Held the same in base and head: connectome-host e7a2e4c01413, chronicle 0.6.0, context-manager 0.25.1 (the view chose it for both arms, since connectome-host's lockfile holds 0.25.0, which doesn't meet ^0.25.1 from the base), membrane 0.8.0.

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