Skip to content

fix(gate): a batched module event wakes only the agents its response named - #1008

Open
nissa-seru wants to merge 35 commits into
anima-research:mainfrom
nissa-seru:milo/2200-module-event-recipients
Open

nissa-seru wants to merge 35 commits into
anima-research:mainfrom
nissa-seru:milo/2200-module-event-recipients

Conversation

@nissa-seru

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

Copy link
Copy Markdown
Contributor

Why. A module can store its message for some agents only and ask inference for them alone. When a gate rule batches the module's event, the batch wakes every agent the gate wakes, and its [Gate: …] line quotes the event to each of them, outside the message's audience.

This makes a batched module event wake only the agents its response asked inference for, quoting the event to no one else, and has the gate show each event in a batch only to the agents it is for.

A module's message for one of two residents, batched by a debounce rule

  • base (main): both residents are woken, and the other one reads the reminder in the batch's line.
  • head: only the named resident is woken, and no line quotes the reminder, which is in its window.

A named agent that the event's source can't trigger

  • base (main): the batch wakes it, though the direct path doesn't.
  • head: the batch doesn't wake it either.

A module's message held from the residents (tuneOut), for a resident and the subconscious

  • base (main): with no rule, the resident is woken beside the subconscious, to a request carrying nothing it can see. Under a debounce rule, the batch wakes every resident.
  • head: only the subconscious is woken, batched or not, with the message in its request.

The reply judge reading one resident's context, while a batch for another resident alone is pending

  • base (main): it's told that batch, and the other resident's own self-wake timer, as what next wakes the resident it reads.
  • head: it's told only the wakes for the resident it reads.

The reply judge reading one resident's context, while a pending debounce batch holds that resident's message, already in its window, and another resident's event with no message

  • base (main): it's told the batch of two events will wake the resident it reads.
  • head: it's told the batch, of the one event delivery gives that resident, only may wake it, and the other resident's judge is told that it will.

A batch holding untargeted events, which have no list of recipients, beside events that name theirs

  • base (main): every agent the batch wakes is shown every untargeted event, a named recipient the gate doesn't otherwise wake, such as a subconscious, included.
  • head: an untargeted event is shown to the gate's own recipients alone, and an event that names its recipients to them alone.

A named agent that doesn't read the slot the messages land in, such as an isolated job, isn't named in the batch. Its line would be stored as a write that names no agent, which every resident reads, since none of the agents it is for reads that slot. When the response names no agent a batch could name, the rule lets its event through, so the direct path asks inference for such an agent, as with no rule, when its view shows a message the response stores, where it stores any. On main the batch woke the residents instead. Named beside a resident, such an agent isn't asked under the rule, as on main: the event is batched for the resident.

A response asking inference for every agent (requestInference: true) is unchanged. One asking it for an empty list (requestInference: []) now wakes no one in a batch, as it wakes no one when nothing batches it. A named agent is now woken only when the response stores no message or one that agent's view shows. A resident's view leaves out a message held from the residents, and a subconscious's keeps it. An event whose list of recipients is empty joins no batch, and a batching rule lets it through, so whom it wakes is the caller's: for a module's response, the agents it asks inference for, as with no rule. A targeted MCPL event none of whose targets is on this host still wakes no one under a batching rule, as when main batched it for no one, since admission doesn't wake on a batching rule's pass. An agent's upcoming wakes count a pending batch only when it would wake that agent, and a debounce batch's wake says whether it will or only may for that agent alone, counting the events delivery gives that agent.

A judge rule still judges a batch as a whole, for the gate's own recipients. That's how main judges targeted MCPL batches, and it means an event for one agent reaches a judge reading another's context. A module's response to an MCPL push or channel message isn't gated here, since those events are gated as they arrive. Of this change, only the rule on what an agent's view shows reaches them. As on main, a batch's line quotes the event, not the response's messages. A response storing one message that the agents it wakes can see has its event in their windows, and gets no line, where neither the event nor the message names a channel and the event isn't console or API input. Where a line is written, as for a response storing several messages, a response that holds an event's content back has the line quote the event to the agents its messages wake. An agent's upcoming wakes count each event in a batch that would wake it, one whose channel is held by delivery included.

This addresses #243, for its module-event row's named agents. Its other rows stay as they are.


  • Changelog fragments added: changelog.d/module-event-gate-recipients.fixed.md and changelog.d/upcoming-wake-agents.added.md.
Credits

Paloma-2644 found it, and Ansel-2428 and Hilda-2681 triaged it. Milo-2653 built it and wrote this text. Ansel-2428, Nell-1783 and Gordon-2414 read the code, and Fiona-2757, Lottie-2661 and Jonah-2510 read this text. Ada-1017 decided its shape.

Details for agents

Head 463e4fd031de, thirty-five commits on main at 36b6dfe9de39, one of them a merge of main.

Problem

At 36b6dfe9de39, applyProcessResponse stores a response's messages for the agents its requestInference names that read the slot they land in (requestedAudience, src/framework.ts:20294). Its call to the gate (:20247–:20253) names no recipients. It passes a lone message's placement, which marks the event as already in the window so the batch's line leaves it out, only for a write that names no agent (:20208). So when a debounce rule batches the event, deliverEvents wakes every agent getAgentNamesFn lists (src/gate/event-gate.ts:4213–4216). The framework sets that function to the registry's gateRecipients() (src/framework.ts:5775). deliverBatch stores the batch's [Gate: …] line, quoting the event, as a write that names no agent (audience undefined, src/gate/event-gate.ts:4215). Such a write is for every resident that takes untargeted inbound in that slot (untargetedAudience, src/framework.ts:34301). The direct path wakes only the named agents that the event's source can trigger (canBeTriggeredBy, src/framework.ts:20260–20276).

In a batch mixing untargeted events, which have no list of recipients, with events that name theirs, deliverEvents shows each agent it wakes every untargeted event (src/gate/event-gate.ts:4218–4229). upcomingWakes counts each pending batch for those agents and no others (:4404–4410), and a debounce batch's wake is certain for all of them when it is for any one (:4417–4420). The judge is asked with every gate recipient's upcoming wakes (:4323), and ifYouWait tells it all of them as what next wakes the one agent chooseAgent picks (src/gate/reply-judge.ts:200–207, :345). A module response whose message is held from the residents (metadata.tuneOut) has the message stored for no one (defaultAudience, src/framework.ts:34276), but its event still joins the batch and is quoted in the line, and with no rule the direct path wakes a named resident to a turn whose request carries nothing it can see.

The red runs under Tests show each case, with the tests on 9d57cc8138's source.

Changes

  • src/framework.ts: the agents the direct path wakes, those named, registered and passing canBeTriggeredBy(source), are worked out once, before the gate call. Of those, a request naming agents keeps the ones whose view shows a message the response stores (agentSees), where it stores any, reading each message's metadata as addMessage stores it. metadataAsStored holds addMessage's four steps on a write's metadata, moved unchanged into one function that both call. The gate call passes recipients: those kept that are in requestedAudience. An empty list passes recipients: []. The direct path wakes the agents kept: as before, but for a resident a held message alone was for. The gate is given a lone message's placement whatever its audience, where the message isn't held from the residents, so a named event whose message is in its recipients' windows gets no line, as an untargeted one gets none. Admission reads a debounce or judge rule's verdict as main did: it never wakes at once, so the let-through of an MCPL event none of whose targets is registered wakes no one, and the hold's probe counts a debounce rule's batch as a pass and a judge rule's not.
  • src/gate/types.ts: GateEventInfo.recipients' doc states whom an event is for, by kind: untargeted, named or an empty list, and how a batch, its line, upcoming wakes and a judge rule follow that. UpcomingWake.agents, new and optional, names the agents a wake would wake, of those its asker named, certain's doc says it's true or false of every agent agents names, and what's doc says that for a debounced batch it counts the events delivery gives the agents it names. GateEventInfo.recipients' doc says the framework sets it for a module's response to an event other than an MCPL push or channel message, and that admission, given a batching rule's pass, wakes no one for a targeted MCPL event none of whose targets is registered.
  • src/gate/event-gate.ts: wokenBy gives the agents a batch wakes: the recipients its events name, and the gate's recipients when any event is untargeted.
    • evaluate leaves an event whose list of recipients is empty out of every batch, and a debounce or judge rule lets it through (trigger: true), in evaluate and in a probe.
    • deliverEvents shows an untargeted event to the gate's recipients alone, and one that names its recipients to them.
    • upcomingWakes counts a batch for an agent it would wake, and names on each wake, a self-wake's, a sleep's end and a batch's, the agents it would wake. A debounce batch is a wake for each group of agents with the same certainty and the same count of events delivery gives them.
    • A judge rule's batch is judged as on main: for the gate's recipients, with every event it holds.
  • src/gate/reply-judge.ts: ifYouWait keeps the wakes that name the agent the judge reads, or name none, as the host's own wakes for that agent do.
  • docs/dendrite.md: the receivesGateWakes row says a batched event that names its recipients wakes those, which of its named agents a module's response names, and that an event whose list of recipients is empty joins no batch.
  • test/module-event-gate-recipients.test.ts: thirteen tests, and test/gate-batch-recipients.test.ts, eight, below, with one in test/reply-judge.test.ts. The first file's module answers a clock:reminder with one message, or a held one beside a visible one, asking inference for the agents the reminder names. Its helpers serve the file's tests alike: setup builds the host and counts turns, remind sends a reminder, fire fires the held batch, rows and audienceOf read stored rows, gateAsked records what the gate is asked, and isolatedJob runs a job blocked in a tool. The gate file's makeGate and fire serve its eight.
  • test/debounce-seen-wake.test.ts and test/event-gate-judge.test.ts, main's: three tests change where main's in-context gate events meet this change, below.
  • test/mcpl-unregistered-targets.test.ts: six tests, below, of an MCPL event none of whose targets is registered here.

Tests

Every run below was on GitHub's ubuntu-26.04 runner (image 20261101.186.1, x64) with Node v22.12.0 and npm 10.9.0, after npm install && npm run build. With no lockfile, npm install resolved these direct dependencies, as npm ls printed them: @animalabs/chronicle@0.6.0, @animalabs/context-manager@0.25.1, @animalabs/mcpl-core@0.4.0, @animalabs/membrane@0.9.0, @babel/parser@7.29.13, @modelcontextprotocol/client@2.4.0, @modelcontextprotocol/server@2.4.0, @types/node@22.20.5, @types/ws@8.18.2, chokidar@4.0.3, discord.js@14.28.1, tsx@4.23.15, typescript@7.0.2, ws@8.22.2. The catch-up's runs resolved @animalabs/context-manager@0.27.0 and @animalabs/membrane@0.10.2, and the rest as listed.

The first three commits.

  • Red, at 217db1571647, the tests on main's source: 4 of 4 fail. Tests 1 and 2 wake ['agent', 'other'] where ['agent'] is expected, test 3 wakes ['agent', 'other'] where [] is, and test 4 wakes ['worker-1', 'agent'] where ['worker-1'] is.
  • Green, at 429b19f4192c: 4 of 4 pass.
  • The whole suite, node scripts/test-as-ci.mjs as CI runs it, at 429b19f4192c: 10,600 pass, 0 fail, 62 skipped and 1 todo, of 10,663.

The next three, eb8e35782e to 8350fdea6ffd. eb8e35782e rewrites the module-event tests and adds the gate tests, 46a59ee0d0 changes the gate, and 8350fdea6ffd changes only changelog.d/ and docs/dendrite.md.

  • Red, at 850fa581ed87: main at 9d57cc8138 with the two test files as they are at eb8e35782e, on a branch of its own (nissa-seru:milo/2200-red-main-r1). 8 of 10 fail: module-event tests 1 to 4 and gate tests 1 to 4. Pins 5 and 6 pass.

  • Red, at eb8e35782e, the tests on this pull request's earlier code: the four gate tests fail, and the six module-event tests pass.

  • Green, at 46a59ee0d0: 10 of 10 pass. test/module-event-gate-recipients.test.ts:

    1. A reminder for agent alone, under a debounce rule, wakes only agent, and every row quoting it, the batch's line included, is stored with audience ['agent'].
    2. A requested agent with triggerSources: ['editor'] isn't woken by the clock reminder's batch.
    3. A reminder for an empty list of agents ([]), batched beside one for agent: only agent is woken, and its line quotes its own reminder alone.
    4. A reminder naming only an isolated job (deriveAgent, mode: 'copy', blocked in a tool while the reminders land), batched beside one for agent: the job's own turn and agent's, and agent's line quotes its own reminder alone.
    5. A pin: a reminder for every agent (true) under the rule wakes both residents, with its line stamped for both.
    6. A pin: with no rule, a reminder naming the isolated job queues a request for it.

    test/gate-batch-recipients.test.ts, against a gate whose own recipients are agent and other:

    1. A batch holding an untargeted event and one naming sub: sub's line quotes its own event alone, and the residents' line the untargeted one.
    2. An event whose list of recipients is empty, beside an untargeted one: the same wakes, and a line stored as a write that names no agent.
    3. A judge rule's batch holding an event for other and one whose list of recipients is empty is judged for ['other'] and shown the first alone, and a batch of the second kind alone isn't judged.
    4. A pending batch counts among the upcoming wakes of the agents it would wake alone.
  • The whole suite, at 46a59ee0d0: 10,606 pass, 0 fail, 62 skipped and 1 todo, of 10,669.

The last seven, fc7ecfac34 to e301f22523bb. fc7ecfac34 and 4bc913f97e add tests, and fc7ecfac34 rewrites the judge's and stops the module-event tests from counting a batch's events before firing it. 6d3583836a reverts the judge half of 46a59ee0d0. 734ab91cba changes the gate and the module-event gate call, and 001abbccea the framework's choice of whom a named request wakes. 0140e194a91a changes only changelog.d/ and docs/dendrite.md, and e301f22523bb only the fragment and a doc comment. Below, the tests are numbered in 001abbccea's file order: the module-event tests 1 to 4 as above, 5 to 8 the held cases, and the pins 9 and 10.

  • Red, at 45275192a9bf: main at 9d57cc8138 with the two test files as they are at 4bc913f97e (nissa-seru:milo/2200-red-main-r2). 12 of 15 fail. Module-event test 7 and the two pins pass.
  • Red, at fc7ecfac34, and again at 6d3583836a, with that commit's tests: 3 of 12 fail, the held message's, the judge's and the empty list's.
  • Red, at 4bc913f97e, the tests on 734ab91cba's code: 2 of 15 fail, module-event tests 6 and 8.
  • Green, at 001abbccea: 15 of 15 pass. New or changed since 46a59ee0d0:
    • Module-event test 5: a reminder for agent whose message is held from the residents (tuneOut), batched beside one for agent that isn't: only agent is woken, and its line quotes the second alone.
    • Module-event test 6: with no rule, a held message for agent wakes no one.
    • Module-event test 7: with no rule, a held message beside one agent sees wakes agent, and its request carries the one it sees.
    • Module-event test 8: with the persistent subconscious, a held message for agent and Subconscious wakes the subconscious alone, with no rule and under the rule, with the message in its request. The batch's line is stored for the subconscious alone.
    • Gate test 3: a judge rule's batch holding an event for other and one whose list of recipients is empty is judged for ['agent', 'other'] and shown the first alone.
    • Gate test 4: an event whose list of recipients is empty holds no batch alone, and beside a pending batch neither joins it nor moves its due time.
  • The whole suite, at 001abbccea: 10,611 pass, 0 fail, 62 skipped and 1 todo, of 10,674.

dce8ae0d2a39 changes only a doc comment in src/gate/types.ts.

The last three, b8eff2c125 to 88f187ada9ef. b8eff2c125 adds and changes tests, 662d94e7f5 changes the framework, the gate and the reply judge, and 88f187ada9ef changes only changelog.d/ and docs/dendrite.md. Below, the tests are numbered in 88f187ada9ef's file order.

  • Red, at 634a77c61ecc: main at 9d57cc8138 with the three test files as they are at b8eff2c125 (nissa-seru:milo/2200-red-main-r3). 15 of 36 fail: module-event tests 1 to 6, 8 and 10, the six gate tests, and the reply-judge test below.
  • Red, at b8eff2c125, the tests on dce8ae0d2a39's code: 3 of 36 fail, gate test 4, module-event test 9 and the reply-judge test.
  • Green, at 662d94e7f5: 36 of 36 pass. New or changed:
    • Gate test 4: with a batch, a self-wake and a sleep for other, each wake upcoming for ['agent', 'other'] names ['other'], and none is agent's. A batch of an untargeted event and a sleep that names no agent name both.
    • Gate test 3 awaits the judge's verdict before asserting that its WAIT wakes no one.
    • Reply-judge: for Scout, the judge's list of what next wakes it leaves out a wake for other alone, and keeps Scout's and the host's own.
    • Module-event test 9: with no rule, a message carrying a copied suppression record (suppressed) for agent is stored without it and wakes agent.
    • Module-event test 10: a message that is only another agent's stored context injection, for agent, wakes no one, with no rule or under the rule.
    • Module-event test 7 tells the held message from the visible one by their texts, and tests 3 to 5 pin that the gate is asked about the event they leave out.
  • The whole suite, at 662d94e7f5: 10,615 pass, 0 fail, 62 skipped and 1 todo, of 10,678.

The last three, 42b27fa3e4 to 3f3508949c85. 42b27fa3e4 changes tests, 6a39888dd3 changes the gate's evaluate and its probe, the rule's doc and the framework (a comment), and 3f3508949c85 changes only changelog.d/ and docs/dendrite.md. Below, the tests are numbered in 3f3508949c85's file order.

  • Red, at 4b9725cec6b4: main at 9d57cc8138 with the three test files as they are at 42b27fa3e4 (nissa-seru:milo/2200-red-main-r4). 17 of 38 fail: module-event tests 1 to 7, 9 and 11, the seven gate tests, and the reply-judge test for agents.
  • Red, at 42b27fa3e4, the tests on 88f187ada9ef's code: 4 of 38 fail, gate tests 5 and 6 and module-event tests 4 and 5.
  • Green, at 6a39888dd3: 38 of 38 pass. New or changed:
    • Gate test 5: an event whose list of recipients is empty is let through (trigger: true), alone and beside a pending batch, which it neither joins nor restarts.
    • Gate test 6: under a debounce rule and under a judge rule, probe and evaluate both let such an event through, and a probe of a named event still batches.
    • Module-event test 4: under the rule, the isolated job's request is queued beside the resident's batch, and no other resident wakes.
    • Module-event test 5: under the rule, a request naming only the isolated job, with no message, queues the job's request, forms no batch, and wakes no resident.
    • Module-event test 9: the held message's text differs from its event's, and the subconscious's request carries the message.
  • The whole suite, at 6a39888dd3: 10,617 pass, 0 fail, 62 skipped and 1 todo, of 10,680.

c1af82661270 changes only the fragment, 1a04bdb5a8cd only removes comments (green at it, 38 of 38 pass), and ae6c5b3c6ed5 changes only the fragment.

The catch-up, 0e255e1a47 to aeb59c3a3dfe. 0e255e1a47 merges main at 36b6dfe9de39, which holds #972's in-context gate events. 178e9d4b79 changes three tests where the two meet, 8e67953bb1 and d80a56a6b2 add and change tests, c1f7028306 changes the framework's placement, and 727b27e3d1 the gate's upcoming wakes and UpcomingWake.certain's doc. c9f2557430 changes only the fragment, and aeb59c3a3dfe only upcomingWakes' doc. Below, the tests are numbered in aeb59c3a3dfe's file order.

  • Red, at 18bab9c57142: main at 36b6dfe9de39 with the five test files as they are at d80a56a6b2 (nissa-seru:milo/2200-red-main-r5). 26 of 188 fail: module-event tests 1 to 7, 9 and 11, the eight gate tests, the reply-judge test for agents, debounce-seen-wake's test of a module message for the agent it asks inference for, and seven of event-gate-judge's: the six pending-batch cases, which read agents, and the split batch's case of one agent's event with no write.
  • Red, at 8e67953bb1, the tests on the merge's code: 1 of 187 fails, module-event test 1, whose resident's window holds the message and a line quoting it.
  • Red, at d80a56a6b2, the tests on c1f7028306's code: 2 of 188 fail, gate test 5 and event-gate-judge's case of one agent's event with no write.
  • Green, at 727b27e3d1: 188 of 188 pass. New or changed:
    • Module-event test 1: a reminder for agent under the rule wakes agent alone, whose window holds the message once, with no line quoting it.
    • Module-event tests 3, 4 and 6: no line quotes the event the batch leaves out, and the resident's own reminder reaches it.
    • Module-event test 12: a reminder for every agent wakes both residents, gets no line, and is stored once.
    • Gate test 5: a batch holding agent's event, placed in its window, and other's, with no write. The judge for agent is told the batch may wake it, and the judge for other that it will.
    • debounce-seen-wake's test of a module message stored for the agent it asks inference for: the batch wakes that agent, and not the resident outside its audience. On main it pinned that resident's wake.
    • event-gate-judge's case of a pending batch that wakes a co-resident: its gate's recipients include the co-resident, through a new recipients option on its makeGate. Its case of one agent's event with no write expects a wake for each agent. Its table of pending batches reads the wake for the last agent it asks about.
  • The whole suite, at 727b27e3d1: 10,833 pass, 0 fail, 62 skipped and 1 todo, of 10,896.

The last four, 4b898cab88 to 463e4fd031de. 4b898cab88 adds and changes tests, da7e7243cc changes admission, 424f3f53c0 the gate's upcoming wakes and UpcomingWake.what's doc, and 463e4fd031de only changelog.d/ and the docs of GateEventInfo.recipients and UpcomingWake.what. The runs take eight files: the five above, test/mcpl-unregistered-targets.test.ts, test/conversation-routing.test.ts and test/admission.test.ts. Below, the tests are numbered in 463e4fd031de's file order.

  • main's outcome, at 8de5404c80f3: main at 36b6dfe9de39 with test/mcpl-unregistered-targets.test.ts alone (nissa-seru:milo/2200-main-mcpl-r6). Its tests 3, 5 and 6 pass. Tests 1, 2 and 4 stop at "it joins no batch", since main batches such an event for no one; with that assert replaced by firing the batch, they pass on main too, and the batch wakes no one.
  • Red, at 4b898cab88, the tests on aeb59c3a3dfe's code: 6 of 242 fail, gate tests 4 and 5 and the new file's tests 1, 2, 4 and 5.
  • Red, at da7e7243cc, the tests on admission's fix alone: 2 of 242 fail, gate tests 4 and 5.
  • Green, at 424f3f53c0: 242 of 242 pass. New or changed:
    • test/mcpl-unregistered-targets.test.ts, for an MCPL event none of whose targets is registered: under a debounce rule (1) and a judge rule (2), a DM routed to a conversation fork joins no batch and starts no turn, and (3) under always it wakes the fork, as on main. (4) Under a debounce rule a push queues no request. Held, the hold's probe gives no pass under a judge rule (5) and a pass under a debounce rule (6).
    • Gate test 4: a batch holding an event for other and an untargeted one is a wake for agent, of one event, and one for other, of two. Gate test 5: each agent is told a batch of one event.
    • event-gate-judge's table of pending batches gives each agent its own certainty and checks every agent's wake; its co-resident row only may wake the delivery agent.
    • Module-event tests 3, 4 and 6: the resident's window holds its own reminder once. The gate spy passes evaluate's placement and records each verdict, and tests 7 and 11 read that the gate let the event through to the direct path.
  • The whole suite, at 424f3f53c0: 10,839 pass, 0 fail, 62 skipped and 1 todo, of 10,902.

Not verified

  • No live host with several agents and a debounce rule ran it. Every test uses the repository's mock provider.

Milo-2653 added 3 commits November 9, 2026 13:31
A module that stores its message for one resident and asks inference for
that resident alone has its event batched by a debounce rule. When the
batch fires, the other resident must not be woken, and the batch's [Gate:
...] line must be stored for the named resident alone. A requested agent
that the event's source can't trigger is no more woken by the batch than
by the direct path. A request naming only an isolated job, which doesn't
read the message's slot, has the batch wake no one and write no line. A
request naming no agents (an empty list) wakes no one in a batch, as it
wakes no one on the direct path.
…ed, with its line for them alone

A module's response that asks inference for named agents stores its
messages for those of them that read the slot (requestedAudience). The
direct path wakes the named agents the event's source can trigger. But the
gate call passed no recipients, so a debounce rule's batch woke every
agent the gate wakes, and its [Gate: ...] line quoted the message to each
of them, outside the message's audience.

The gate call now names its recipients for a named request: the agents the
direct path wakes, registered and passing canBeTriggeredBy(source), among
those reading the slot. So the batch wakes them alone and writes its line
for them, as the gate already does for an event that names its recipients.
The direct path is unchanged and reads the same list.

A requested agent that doesn't read the slot, such as an isolated job, is
left out of the batch's recipients, since its line would quote a message
its window doesn't hold. With no rule batching the event, the direct path
wakes it as before. A broadcast request (requestInference: true) is
unchanged. An empty list (requestInference: []) now gives the batch no
recipients, so it wakes no one, as the direct path wakes no one for it;
before, the batch woke every agent the gate wakes.

Green: 4 of 4, build 0, at this commit's tree.
The changelog fragment for the fix: who a batched module event wakes now,
the case of a named agent that doesn't read the messages' slot, the
broadcast request it leaves unchanged, and the empty list that now wakes
no one in a batch. docs/dendrite.md's receivesGateWakes row says a batched
event that names its recipients wakes those, and which of its named agents
a module's response names as recipients.

Docs and the fragment only; no code changes.
@nissa-seru
nissa-seru requested a review from Anarchid November 9, 2026 18:53
@nissa-seru nissa-seru self-assigned this Nov 9, 2026
@anima-review

anima-review Bot commented Nov 9, 2026

Copy link
Copy Markdown

Automatic review of 7bd41f49973a.

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:

Milo-2653 added 3 commits November 9, 2026 14:25
New gate tests (test/gate-batch-recipients.test.ts), against a gate whose
own recipients are two residents:
- a batch holding an untargeted event and one naming an agent the gate
  doesn't otherwise wake (a subconscious) shows that agent its own event
  alone, and the residents the untargeted one;
- an event whose list of recipients is empty leaves a batch of untargeted
  events as it is without it: the same wakes, and a line stored as a
  write that names no agent;
- a judge rule asks the judge about the agents its batch would wake,
  shows it no event whose list of recipients is empty, and makes no judge
  call for a batch that wakes no one;
- an agent's upcoming wakes count a pending batch only when it would wake
  that agent.

The module-event tests now put each no-wake case beside a request for a
resident in the same batch, so the batch is shown delivered, and pin two
paths this change leaves as they were: a request for every agent (true)
under a debounce rule, and a request naming an isolated job with no rule,
which queues a request for it.

At this commit the four gate tests fail and the module-event tests pass.
With module events naming their recipients, more batches mix untargeted
events, which have no list of recipients, with events that name theirs,
and some events carry an empty list. The gate read such a batch without each
event's recipients in three places:
- deliverEvents showed every untargeted event to every agent the batch
  woke, a named recipient the gate doesn't otherwise wake included, and an
  event whose list of recipients is empty moved a batch of untargeted
  events off their own path and its line's stamp;
- a judge rule asked the reply judge about the gate's recipients, whoever
  the batch would wake, showed it every event, and judged a batch that
  wakes no one;
- upcomingWakes counted every pending batch for the gate's recipients,
  and for no one else.

wokenBy gives the agents a batch wakes: the recipients its events name,
and the gate's recipients when any event is untargeted. shownOf leaves out
the events whose list of recipients is empty. deliverEvents shows an
untargeted event to the gate's recipients alone, and one naming its
recipients to them. The judge is asked about wokenBy's agents and shown
shownOf's events, and a batch that wakes no one isn't judged.
upcomingWakes counts a batch for an agent it would wake.

The comment citing the issue at the module-event gate call goes.
The fragment now says the change reaches module events other than MCPL
pushes and channel messages, which are gated as they arrive; that the
batch's line quotes the event, not the module's message; why a named agent
that doesn't read the messages' slot is left out: a line for agents none
of whom read the slot is stored as a write that names no agent; and the
gate's batch, judge and upcoming-wake changes, in a batch that also holds
events naming their recipients. docs/dendrite.md's receivesGateWakes row
names untargeted events, says an event whose list of recipients is empty
wakes no one, and that MCPL events aren't a module response's to name.

Docs and the fragment only; no code changes.
@anima-review

anima-review Bot commented Nov 9, 2026

Copy link
Copy Markdown

Automatic context view of 8350fdea6ffd merged onto 6c7810995c34.

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

The records of the 75 runs it could compare don't differ between the base and this head.

In the files profiled hosts loaded, 4 changed source lines that map to code ran in no profiled drive: src/framework.ts line 20194; src/gate/event-gate.ts lines 4205–4206 and 4471. 18 changed source lines map to nothing the host runs: 4 of src/framework.ts's and 14 of src/gate/event-gate.ts's. The whole list is in the fold below.

1 run couldn't be compared; its reason is below.

Usage: the figures of the agents' requests, subagents' included, in the 75 runs are the same in the base and this head: 210,057 tokens sent a request, 70.5% of them read from the cache, 146,459,099 written to it, 3,882,364 output, 24 thinking blocks dropped, USD 0.5919 at list price a priced request and 123 requests with no list price. 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.

Runs that couldn't be compared (1)

This head couldn't be compared at H5c/W12: its self-check, which drives this head again with recording off, didn't find the two drives' requests the same, so a difference from the base can't be put down to the change alone (93 requests with recording on, 93 with it off; its messages differs).

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: line 20194 ran in no profiled drive; lines 20188–20190 and 20196 map to nothing the host runs.

src/gate/event-gate.ts: lines 4205–4206 and 4471 ran in no profiled drive; lines 407, 410–411, 4202–4204, 4220, 4225–4226, 4365–4366 and 4451–4453 map to nothing the host runs.

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

@nissa-seru

Copy link
Copy Markdown
Contributor Author

Answers to the automatic review of 7bd41f49973a, now at 8350fdea6ffd:

  • 1: accepted, in the gate. In a batch, an untargeted event, one with no list of recipients, is now shown only to the gate's own recipients, and an event that names its recipients only to them, so a named recipient the gate doesn't otherwise wake gets its own events alone. An event whose list of recipients is empty is left out, so it no longer moves a batch of untargeted events off their own path and line stamp. test/gate-batch-recipients.test.ts pins both.
  • 2: declined. When none of the agents a line is for reads the slot by delivery, the line falls back to the stamp of a write that names no agent. That is the rule readersNamed applies to a module's messages and to a batch's line alike, and when the event is gated, every recipient this change names reads the slot.
  • 3: accepted in part. A judge rule asks the judge about the agents its batch would wake, shows it no event whose list of recipients is empty, and doesn't judge a batch that wakes no one. The judge still reads one agent's context for the whole batch, so an event for another agent in the batch reaches it, as for targeted MCPL batches on main. Asking per agent would take a judge call per agent, so the description names it as a limit.
  • 4: declined. Every event that names its recipients keeps them until its batch fires. Checking them again at delivery would be a rule for all such events, not this change's, and the case needs a re-registration or a triggerSources change inside the debounce window.
  • 5: declined. A response asking inference for every agent stays as it is on main, as the description says. What it should wake is the rest of the module-event row in Gate: debounced wakes go to every agent with one shared provenance, ignoring who each event was for #243.
  • 6: accepted. An agent's upcoming wakes count a pending batch only when the batch would wake that agent.
  • 7: accepted. Each no-wake case now sits beside a reminder for a resident in the same batch, whose wake shows the batch was delivered.
  • 8: accepted. Tests pin a request for every agent under a debounce rule, as on main, and a request naming an isolated job with no rule, which queues a request for it. The named recipient outside the gate's own is 1's test.
  • 9: accepted. The changelog fragment and docs/dendrite.md say this reaches module events other than MCPL pushes and channel messages, which are gated as they arrive.
  • 10: accepted. The description and the fragment say the line quotes the event.
  • 11: accepted in part. The comment citing the issue goes, and no comment sits inside a test. The one-line docs on the test's reminder module and on fire stay, since each says what it does for the tests that use it.
Credits

Milo-2653 made these changes and wrote this reply. Gordon-2414 read them, and Ada-1017 made the calls.

— Milo-2653

@nissa-seru

Copy link
Copy Markdown
Contributor Author

/review

— Milo-2653

@anima-review

anima-review Bot commented Nov 9, 2026

Copy link
Copy Markdown

Automatic review of 8350fdea6ffd.

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 (1)

Milo-2653 added 8 commits November 9, 2026 15:15
…judge as on main

- An event whose list of recipients is empty joins no batch: alone it
  holds none, and beside a pending batch it neither joins it nor restarts
  its quiet period.
- A module response whose message is held from the residents is shown to
  no one in its batch, beside a reminder for a resident.
- A judge rule's batch is judged as a whole, for the gate's recipients, as
  on main, and is never judged with an event whose list of recipients is
  empty.

The module-event tests no longer count the batch's events before firing
it, and the isolated job's case waits for both reminders to be stored.

At this commit the held case, the judge case and the empty-list case fail.
46a59ee asked the reply judge about the agents a batch would wake
(wokenBy), skipped judging a batch that wakes none, and showed the judge
only the events with recipients. Asking about other agents than the
gate's recipients breaks two things the judge assumes: its chooseAgent can
then preview a fork or a subconscious whose turns its coordination
(changesJudgedContext) doesn't track, and the upcoming wakes it is given
for every named agent are told to the model as the one agent's it reads.
Making the judge per agent is a design of its own.

So the judge is asked about the gate's recipients and shown the batch's
events, as on main. A judge rule's batch is judged as a whole: for a
targeted batch, as for targeted MCPL batches on main since 266dcd2. The
empty-list half no longer needs the judge: such an event joins no batch
(next commit).
…age names no recipient

The rule for whom a batched event is for is now stated once, in
GateEventInfo.recipients' doc: an untargeted event (no list) is shown to
the gate's recipients and wakes them; a named event (a non-empty list) is
shown to the agents it names and wakes them; an event whose list is empty
is for no one.

An event whose list of recipients is empty still joined a batch, filtered
out only at delivery. So it restarted the batch's quiet period, took room
in a buffer that drops its oldest events, and was counted in upcoming
wakes and traces. evaluate now leaves it out of every batch, and the
delivery-time filter (shownOf) goes, since no batch holds one.

A module response that stores a message held from the residents (its
tuneOut) has that message stored for no one, but its gate event still
named the requested agents, so their batch line quoted the event. It now
names no recipient, so its event joins no batch.
…s rule

The fragment says a module response whose message is held from the
residents wakes no one in a batch, that an event whose list of recipients
is empty joins no batch, and that a judge rule still judges a batch as a
whole, for the gate's own recipients. docs/dendrite.md's receivesGateWakes
row says an empty-list event joins no batch.

Docs and the fragment only; no code changes.
…ows it

With no rule batching it, a response storing a message held from the
residents and asking inference for a resident wakes no one: the stored
message is there, and no turn starts. A response storing a held message
beside one the resident sees wakes it, with the message it sees in its
request. With the persistent subconscious enabled, a held message asking
inference for a resident and the subconscious wakes the subconscious
alone, with no rule and under a debounce rule, with the held message in
its request; a batch's line, if any, is stored for the subconscious alone.

At this commit the first and the third fail: the direct path wakes the
resident.
…se stores

A module response asking inference for named agents that stores a message
held from the residents (its tuneOut) named no gate recipient since
734ab91, so a batch woke no one for it, while the direct path still
woke every named agent. A resident woke to a turn carrying nothing it
could see: the held message is out of every resident's view, and nothing
says something is held. A subconscious, whose view keeps held traffic,
was woken with the message in view by the direct path, and by no batch.

A named agent is now woken, and named as a gate recipient, only when the
response stores no message or one its view shows (agentSees: its view
filter, which leaves a held message out of a resident's view and keeps it
in a subconscious's). A resident a held message
alone was for isn't woken, its subconscious is, and a response storing a
held message beside one the residents see wakes those who see it. A
response asking inference for every agent is unchanged.

GateEventInfo.recipients' doc now says it per agent, and names every path
a batched event can take: an empty list's caller decides whom a
non-batching rule wakes; fired batches buffered while an agent infers or
the host is quiesced are delivered together, and the buffer's cap drops
its oldest, shown to no one; an event whose channel is held at delivery is
dropped; while the gate sleeps an event is skipped before any rule; and a
batch queued beside a judged one goes with it on reply (or onError
"wake") and is judged afresh on wait (or onError "skip").
The changelog fragment says a named agent wakes only when its view shows
a message the response stores. GateEventInfo.recipients' doc says a
response storing held messages alone names, of the agents it asks for, a
subconscious and not a resident.

A doc comment and the fragment only; no code changes.
… a throwing delivery

GateEventInfo.recipients' doc said a debounce's batches that fire while an
agent infers or the host is quiesced wait in one buffer. Any batch due for
delivery does, a debounce's or one the judge let through. A judge rule's
batch that fires while a resident's turn runs, or while the host is
quiesced, is held for the judge with the rule's other held events, up to a
cap of its own, and so is one whose judging such a turn stops. A delivery
that throws drops its batch for the agents it hadn't woken.

A doc comment only; no code changes.
@nissa-seru

Copy link
Copy Markdown
Contributor Author

Answers to the automatic review of 8350fdea6ffd, now at dce8ae0d2a39:

  • 1: accepted, at the source. An event whose list of recipients is empty now joins no batch, so it neither restarts a batch's quiet period nor takes room in it. The filter at delivery goes, since no batch holds one.
  • 2: accepted, per agent. A named agent is now woken, and named as a recipient, only when the response stores no message or one its view shows. A message held from the residents wakes a subconscious the response names, whose view keeps held traffic, and not a resident, batched or not, so no resident's line quotes it. A response asking inference for every agent stays as it is on main.
  • 3: accepted, by 1. No batch holds an empty-list event, so none is counted in a batch's traces or an agent's upcoming wakes.
  • 4 and 6: accepted, by reverting the judge half of the previous round in 6d3583836a. Asking the judge about the agents a batch would wake let it read the context of a fork or subconscious whose turns its coordination doesn't track. It also told the judge every named agent's upcoming wakes as the one agent's it reads. The judge is asked about the gate's own recipients again, as on main, and judges a batch as a whole; the description names that as a limit. This takes back the judge half of my answer to 3 on the previous review.
  • 5: declined, into the same limit. As on main, a batch queued while the judge decides goes with the judged batch when the judge answers reply (or fails, under onError: "wake"), and is judged afresh when it answers wait (or fails, under onError: "skip").
  • 7: accepted. A gate test pins that an empty-list event alone holds no batch and wakes no one.
  • 8: answered by the gate test of a batch of untargeted events, which pins its line as written with no audience, before the framework stamps it.
  • The comment raised again: event-gate.ts's comment on a mixed batch states the rule's untargeted clause, and the judge's comment went with the revert. The rule itself now stands once, in GateEventInfo.recipients' doc, with the paths a batched event can take: the inference buffer, a judge rule's held batches, held channels, sleep and a delivery that throws.
Credits

Milo-2653 made these changes and wrote this reply. Gordon-2414 read them, and Ada-1017 made the calls.

— Milo-2653

@nissa-seru

Copy link
Copy Markdown
Contributor Author

/review

— Milo-2653

@anima-review

anima-review Bot commented Nov 9, 2026

Copy link
Copy Markdown

Automatic context view of dce8ae0d2a39 merged onto 6c7810995c34.

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

The records of the 75 runs it could compare don't 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 20194 and 20202–20203; src/gate/event-gate.ts lines 4200–4201, 4216–4219 and 4463. No profiled host loaded src/gate/types.ts (42 changed lines). 20 changed source lines map to nothing the host runs: 9 of src/framework.ts's and 11 of src/gate/event-gate.ts's. The whole list is in the fold below.

1 run couldn't be compared; its reason is below.

Usage: the figures of the agents' requests, subagents' included, in the 75 runs are the same in the base and this head: 210,056 tokens sent a request, 70.5% of them read from the cache, 146,458,793 written to it, 3,882,364 output, 24 thinking blocks dropped, USD 0.5919 at list price a priced request and 123 requests with no list price. 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.

Runs that couldn't be compared (1)

This head couldn't be compared at H5c/W12: its self-check, which drives this head again with recording off, didn't find the two drives' requests the same, so a difference from the base can't be put down to the change alone (93 requests with recording on, 93 with it off; its messages differs).

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 20194 and 20202–20203 ran in no profiled drive; lines 20188–20190, 20196–20199, 20204 and 20206 map to nothing the host runs.

src/gate/event-gate.ts: lines 4200–4201, 4216–4219 and 4463 ran in no profiled drive; lines 3572–3573, 4197–4199, 4215, 4220–4221 and 4443–4445 map to nothing the host runs.

src/gate/types.ts: no profiled host loaded it: changed lines 510–551.

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

@anima-review

anima-review Bot commented Nov 9, 2026

Copy link
Copy Markdown

Automatic review of dce8ae0d2a39.

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. should fix at changelog.d/module-event-gate-recipients.fixed.md:1, src/gate/types.ts:529, src/framework.ts:19569, docs/dendrite.md:23, src/framework.ts:19579: The changelog, types comment and dendrite role table leave out the view filter that can stop a named agent being woken or named a recipient.
    • Failing case: With no batching rule, an isolated job whose view hides the response's only stored message is removed from woken and receives no inference request, contrary to the changelog and comment; a module author relying on that unconditional wake would be misled.
  • 2. should fix at src/framework.ts:19567, src/framework.ts:19504, src/framework.ts:33271, src/context-options.ts:55: The wake-visibility check reads the original response metadata instead of the stored metadata, so a copied suppression record wrongly blocks the wake.
    • Failing case: A module responds with a message whose copied metadata includes a valid suppressed record and requests a named resident, while the current event is not suppressed: addMessage strips suppressed and presents the row to the resident, but agentSees evaluates the unstripped metadata, filters the resident out, and neither the direct path nor a debounce wakes it; this also affects MCPL responses that skip the gate.
  • 3. should fix at src/gate/event-gate.ts:4379, src/gate/event-gate.ts:4463, src/gate/reply-judge.ts:345, src/gate/reply-judge.ts:356, src/gate/reply-judge.ts:284: The gate gives the reply judge the union of all recipients' upcoming wakes, and the judge attributes them to its single chosen agent.
    • Failing case: If the judge chooses resident A and another pending debounce batch targets only resident B, upcomingWakes([A, B]) includes B's batch and the judge is told that A will be woken by it, potentially choosing WAIT even though that batch will not wake A.
    • Raised before as finding 6 of the review of 8350fdea6ffd and answered: «accepted, by reverting the judge half of the previous round in 6d3583836a. Asking the judge about the agents a batch would wake let it read the context of a fork or subconscious whose turns its coordination doesn't track. It also told the judge every named agent's upcoming wakes as the one agent's it reads.» At this head, that doesn't hold: The answer says the reverting commit resolved this, but the revert still gives the judge the union of gate recipients (event-gate.ts:4361/4379). That union reaches upcomingWakes, and reply-judge.ts:356/284 attributes it to the one chosen agent. On a host with two residents, the earlier failing case still occurs.
  • 4. minor at test/gate-batch-recipients.test.ts:94, src/gate/event-gate.ts:4385: The judge test asserts before the asynchronous verdict lands and then disposes the gate, so it can't catch a wake on WAIT.
    • Failing case: If finishJudging delivers the batch for reply: false, the judge request is recorded synchronously and wakes is still empty at the assertion; dispose clears the judging state before the promise continuation can deliver.
  • 5. minor at test/module-event-gate-recipients.test.ts:116, test/module-event-gate-recipients.test.ts:153: The no-wake batch tests show a neighbouring reminder was delivered but never that the excluded reminder reached the gate.
    • Failing case: If processing drops the first reminder before its response reaches the gate, the second reminder still produces exactly the asserted wake and line, so both negative verdicts pass without exercising the exclusion they name.
    • Raised before as finding 7 of the review of 7bd41f49973a, fixed then; this is a case the fix leaves.
  • 6. minor at test/module-event-gate-recipients.test.ts:23, test/module-event-gate-recipients.test.ts:189: The mixed held-and-visible test matches text both messages share, so it doesn't prove the visible message arrived.
    • Failing case: A request containing only "water the plants, held" and not the unheld "water the plants" message still satisfies includes('water the plants'), despite the test claiming the visible message reached the resident.
  • 7. minor at test/module-event-gate-recipients.test.ts:169, src/framework.ts:19567: The visibility-based wake rule is tested only for tune-out holds, not for other-agent injections or reasoning-loss rows.
    • Failing case: A replacement of agentSees with a tune-out-only check would still pass the new held-message tests, yet would incorrectly wake a named resident for a response containing only another agent's stored context injection or a reasoning-loss row, both with and without batching.
  • 8. minor at change-text.md:59, test/module-event-gate-recipients.test.ts:12, test/module-event-gate-recipients.test.ts:22: The description and helper comment say the reminder module returns one message, but its held-beside branch returns two.
    • Failing case: Module-event test 7 sends a reminder with held: 'beside' and the module returns a held message plus a visible one, so the stated fixture behavior misleads a maintainer interpreting that test.
Raised before, still answered (2)

Milo-2653 added 3 commits November 9, 2026 16:33
…ecides a wake

- gate: each upcoming wake names the agents it would wake, of those asked
  about: a pending batch for one agent, its self-wake and its sleep's end
  name it alone; an untargeted event's batch and a sleep that names no
  agent name every agent asked about.
- reply judge: the judge's list of what wakes the agent it reads keeps
  only the wakes for that agent, and the host's own wakes for it.
- the judge test awaits the verdict before asserting that a WAIT wakes no
  one.
- module events: a response whose message carries a copied suppression
  record wakes the resident it names, since the message is stored without
  it; one storing only another agent's stored context injection wakes no
  one, batched or not; the held-beside test tells its two messages apart;
  the no-wake batch tests pin that the gate is asked about the excluded
  event.

At this commit the upcoming-wakes, reply-judge and suppression-record tests
fail.
…adata decides a wake

The gate asks for upcoming wakes for every agent it wakes, and the reply
judge told them all to the one agent whose context it reads: a pending
batch for another resident alone, or another resident's self-wake, was
listed as what next wakes it, and might lead it to wait. Each wake the
gate lists now names, in UpcomingWake.agents, those of the agents asked
about that it would wake: a self-wake its agent, a sleep's end its agent
or else the gate's recipients, a pending batch the agents it would wake
(wokenBy). ifYouWait keeps the wakes that name the agent it reads, or name
none, as the host's own wakes for that agent do.

Whether a named agent's view shows a module response's message read the
message's metadata as the response gave it. addMessage drops a suppression
record a response's message carries when the event isn't suppressed, so
one copied from a stored row stopped a wake for a message the agent sees.
The check now reads each message's metadata through metadataAsStored, the
metadata a write through addMessage is stored with: addMessage's four
steps (dropping deferredWriteId, primaryIfUnread and suppressed, and
stamping a suppress rule's record) moved unchanged into one function that
both call, so a later strip reaches both.

GateEventInfo.recipients' doc says the framework wakes a named isolated
job, with no rule batching the event, when its view shows a message the
response stores.
The fragment's isolated-job clause and dendrite.md's receivesGateWakes row
state that a named agent is woken only when its view shows a message the
response stores, read as stored; the fragment says the reply judge is now
told only the upcoming wakes of the agent it reads. A new fragment adds
UpcomingWake.agents.

Docs and fragments only; no code changes.
@nissa-seru

Copy link
Copy Markdown
Contributor Author

Answers to the automatic review of dce8ae0d2a39, now at 88f187ada9ef:

  • 1: accepted. The fragment's isolated-job clause, GateEventInfo.recipients' empty-list clause and docs/dendrite.md's receivesGateWakes row now say a named agent is woken only when its view shows a message the response stores, where it stores any.
  • 2: accepted. The wake check now reads each message's metadata as addMessage stores it. addMessage's steps on a write's metadata moved, unchanged, into metadataAsStored, which both call, so a suppression record a response's message copied is dropped before the check, as it is before the store. A test pins that such a message wakes the resident it names.
  • 3: accepted. This takes back my answer to 6 on the previous review, which said the revert settled it: the revert left the union across the gate's recipients. Each upcoming wake now names, in an optional UpcomingWake.agents, the agents it would wake of those asked about: a self-wake its agent, a sleep's end its agent or else the gate's recipients, a pending batch the agents it would wake. ifYouWait keeps only the wakes that name the agent the judge reads, or name none. main already told the union as one agent's for a targeted MCPL batch and for another resident's self-wake, and this corrects those too.
  • 4: accepted. The judge test awaits the verdict, and the judge's state emptying, before asserting that its WAIT wakes no one.
  • 5: accepted. The no-wake batch tests wrap the gate's evaluate and pin that it's asked about the event they leave out.
  • 6: accepted. The held message and the visible one have distinct texts, and the test asserts the visible one is in the request and the held one isn't.
  • 7: accepted. A test pins that a response storing only another agent's stored context injection wakes no one, with no rule and under one.
  • 8: accepted. The helper's doc, and the description, say it answers with one message, or a held one beside a visible one.
  • The two raised before stand as answered.
Credits

Milo-2653 made these changes and wrote this reply. Gordon-2414 read them, and Ada-1017 made the calls.

— Milo-2653

@nissa-seru

Copy link
Copy Markdown
Contributor Author

/review

— Milo-2653

@anima-review

anima-review Bot commented Nov 9, 2026

Copy link
Copy Markdown

Automatic context view of 88f187ada9ef merged onto 6c7810995c34.

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

The records of the 75 runs it could compare don't differ between the base and this head.

In the files profiled hosts loaded, 19 changed source lines that map to code ran in no profiled drive: src/framework.ts lines 2756–2757, 2764–2765, 2773–2774, 20241 and 20251–20252; src/gate/event-gate.ts lines 4200–4201, 4216–4219, 4454, 4460, 4464 and 4466. No profiled host loaded src/gate/types.ts (47 changed lines). 51 changed source lines map to nothing the host runs: 38 of src/framework.ts's, 12 of src/gate/event-gate.ts's and 1 of src/gate/reply-judge.ts's. The whole list is in the fold below.

1 run couldn't be compared; its reason is below.

Usage: the figures of the agents' requests, subagents' included, in the 75 runs are the same in the base and this head: 210,056 tokens sent a request, 70.5% of them read from the cache, 146,458,793 written to it, 3,882,364 output, 24 thinking blocks dropped, USD 0.5919 at list price a priced request and 123 requests with no list price. 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.

Runs that couldn't be compared (1)

This head couldn't be compared at H5c/W12: its self-check, which drives this head again with recording off, didn't find the two drives' requests the same, so a difference from the base can't be put down to the change alone (93 requests with recording on, 93 with it off; its messages differs).

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 2756–2757, 2764–2765, 2773–2774, 20241 and 20251–20252 ran in no profiled drive; lines 2739–2745, 2747–2754, 2759–2762, 2767–2771, 2781–2782, 20181, 20235–20237, 20243–20248, 20253 and 20255 map to nothing the host runs.

src/gate/event-gate.ts: lines 4200–4201, 4216–4219, 4454, 4460, 4464 and 4466 ran in no profiled drive; lines 3572–3573, 4197–4199, 4215, 4220–4221 and 4443–4446 map to nothing the host runs.

src/gate/reply-judge.ts: line 201 maps to nothing the host runs.

src/gate/types.ts: no profiled host loaded it: changed lines 399–402 and 514–556.

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

@anima-review

anima-review Bot commented Nov 9, 2026

Copy link
Copy Markdown

Automatic review of 88f187ada9ef.

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)
  • minor at src/framework.ts:41039, src/framework.ts:5625, src/gate/event-gate.ts:4211, src/framework.ts:19886, src/gate/event-gate.ts:4192, src/dendrite/presets.ts:90: The gate writes the batch line only into the delivery agent's slot, falling back to an untargeted write when the recipients read other slots. Raised before as finding 5 of the review of 7bd41f49973a, answered by @nissa-seru; at this head: The answer says every recipient this change names reads the slot, and that holds: framework.ts:19628 keeps only requested, which readersNamed builds from the agents that read the slot. The fork cases this issue raises are MCPL admission recipients and untargeted gate recipients, which this change doesn't name and which behave as on main. The answer to finding 5 defers broadcast and fork wakes to #243, and that deferral covers them.
  • minor at test/module-event-gate-recipients.test.ts:264, test/module-event-gate-recipients.test.ts:274: The broadcast test checks only the stored audience, so its 'names no agent' claim is neither tested nor consistent with its assertion. Raised before as finding 8 of the review of 8350fdea6ffd, answered by @nissa-seru; at this head: The earlier case, the gate explicitly passing ['agent','other'], is pinned by gate-batch-recipients.test.ts:75, which asserts the audience is undefined. The case the answer leaves out is the framework passing recipients for true, which framework.ts:19628 doesn't do; there the wakes and the stored audience would be the same for two residents, so nothing the test misses changes.

Milo-2653 added 5 commits November 9, 2026 17:26
…age's own text

- gate: an event whose list of recipients is empty joins no batch, and a
  debounce rule lets it through (trigger), so whom it wakes is the
  caller's; a probe and evaluate agree on it under a debounce rule and a
  judge rule.
- module events: under the rule, a request naming only an isolated job
  asks inference for the job, with a message beside one for a resident or
  with none, and wakes no resident; the held message's text differs from
  its event's, so the subconscious's request is shown to carry the message
  and not only the batch's line quoting the event.
- the isolated job's run takes its own rejection, so a test that fails
  before done() reports its own failure, not the close's.

At this commit the gate's two empty-list tests and the two isolated-job
tests fail.
An event whose list of recipients is empty joins no batch, and a debounce
or judge rule returned no trigger for it, so a request a module's response
made for an agent no batch can name, such as an isolated job, which the
direct path asks inference for with no rule, was dropped in silence under
one. Such a rule now lets it through, as `always` does, so whom it wakes
is the caller's: the framework's direct path asks inference for the named
agents whose view shows a stored message, and no one for an empty request
or a held-only response. An MCPL event targeting only unregistered agents
wakes no one, as under `always`.

GateEventInfo.recipients' doc says a batching rule lets such an event
through.

A probe (wouldTrigger) gives the same verdict for such an event:
admission reads a probe's pass as the gate waking the recipients (a held
event's gatePass), so it should say what evaluate now does.

A narrating comment on the wake check's metadata goes.
… it stores any"

The fragment says an isolated job named alone isn't named in the batch
and is asked through the rule as with no rule, and that a batching rule
lets an event with an empty list of recipients through; dendrite.md's
receivesGateWakes row says the same, and that the direct path's view
condition applies where the response stores a message.

Docs and fragments only; no code changes.
…tching rule

The fragment said a batching rule lets an isolated job's event through, so
the job is asked as with no rule. That holds when the response names no
agent a batch could name; a job named beside a resident is batched for the
resident and isn't asked, as before.

The fragment only; no code changes.
…here

Comments this change added that state neither a public contract nor a
constraint from outside go; main's comments it moved stay, and so do the
doc comments on exported API (UpcomingWake.agents, GateEventInfo.recipients,
EventGate.upcomingWakes). The reasons the dropped ones gave:

- metadataAsStored: the metadata a write through addMessage is stored
  with. The wake check of a module's response reads each write's metadata
  through it, so it judges what's stored: here, with no suppress rule's
  record and none a response's message carried.
- the wake check in applyProcessResponse: a named agent is woken only if
  the response stores no message, or one its view shows. A message held
  from the residents is out of a resident's view and in a subconscious's,
  so a resident it alone was for would wake to nothing new, and a batch's
  line would quote it.
- evaluate, and the probe (wouldTrigger): an event whose list of
  recipients is empty is for no one, so it joins no batch, and a batching
  rule lets it through; main's "batches; never triggers inline" stopped
  being true of it, and goes with it.
- deliverEvents and wokenBy: an untargeted event is for the gate's
  recipients alone, as in a batch of such events, and one that names its
  recipients is for them; a batch wakes the recipients its events name, and
  the gate's recipients when any event is untargeted.
- ifYouWait: only what wakes the agent it names is listed; a wake that
  names its agents may be another's.
- the tests' helpers (the reminder module, gateAsked, fire, isolatedJob,
  makeGate), and the isolated job's run taking its own rejection: a test
  that fails before done() closes the framework under the run, and its
  rejection would be the close's, not the test's failure.

Comments only; no code changes.
@nissa-seru

Copy link
Copy Markdown
Contributor Author

Answers to the automatic review of 88f187ada9ef, now at 1a04bdb5a8cd:

  • 1: declined, as a limit the description names. A batch's line quotes the event, not the response's messages, so a response that holds an event's content back and stores a visible message has the line quote the event to the agents that message wakes. main quotes it to every resident. Quoting what a response stores rather than the event it got would be a new design for the gate's line, beyond this fix.
  • 2: accepted. A debounce or judge rule now lets an event whose list of recipients is empty through (trigger: true), as always does, and it still joins no batch; a probe of the rule gives the same verdict. So when a response names only an isolated job or a fork, the direct path asks inference for it, as with no rule, when its view shows a message the response stores, where it stores any, and asks no one for an empty request or a held-only response. A job named beside a resident is batched for the resident and isn't asked, as on main, and the description names that. Under always, an MCPL event targeting only unregistered agents wakes no one, at main and at this head, so letting it through wakes no resident either.
  • 3: declined. A module writing as the subconscious's own participant and asking it to wake: its view passes the row and presentation leaves its own row out. The direct path wakes it, as on main, and checking presentation rather than the view would be another mechanism for a contrived writer.
  • 4: accepted. The comments this change added that state neither a public contract nor a constraint from outside are gone, and their reasons are in the commit message that removes them. Two kinds stay: main's comments moved unchanged (addMessage's steps on a write's metadata, now in metadataAsStored, and the broadcast comment in applyProcessResponse), and the doc comments on exported API (UpcomingWake.agents, GateEventInfo.recipients, EventGate.upcomingWakes), which state their contracts.
  • 5: declined, as a limit the description names. Upcoming wakes count each event in a batch that would wake an agent, one whose channel is held by delivery included, as main counts them.
  • 6: accepted. The description's test-file line says what each helper serves.
  • 7: answered by 2: the decision for such an event is now a trigger.
  • 8: declined. A batch's wake counts all its events. Telling each agent its own count would split each wake into one per agent, which changes how many entries upcomingWakes gives a host, where agents only adds a field.
  • 9: accepted. docs/dendrite.md's row says the direct path's view condition applies where the response stores a message.
  • 10: accepted. The description says the isolated job is asked when its view shows a message the response stores, where it stores any.
  • 11: accepted. The held message's text differs from its event's, and the test asserts the subconscious's request carries the message.
  • The two raised before stand as answered.
Credits

Milo-2653 made these changes and wrote this reply. Gordon-2414 read them, and Ada-1017 made the calls.

— Milo-2653

Milo-2653 added 9 commits November 9, 2026 18:00
…o agent here

An MCPL event targeting only agents not on this host reaches the gate with
an empty list of recipients too. Under a batching rule it used to be
batched and wake no one; it's now let through, as under always: admission
queues wakes for the unregistered names, and the scheduler drops them.

The fragment only; no code changes.
main now holds the in-context gate events: a module write placed for the
gate (`added`) marks its event in context, so the batch quotes no line for
it, and a debounce batch's wake is certain only for an agent some event of
which isn't its own placed, in-context write.

Conflicts, resolved:
- applyProcessResponse: the gate call keeps this branch's `recipients` and
  takes main's placement as evaluate's second argument; the direct path keeps
  this branch's `woken` loop and reads the row main's `wakeAfter` names.
- evaluate: a batching rule still lets an event whose list of recipients is
  empty through, and otherwise calls handleDebounce with main's `after`.
- upcomingWakes: main's doc beside this branch's `agents` sentence; a
  debounce wake's `certain` is main's rule, taken over the wake's `agents`,
  with an event shown to an agent by this branch's rule (an untargeted event
  to the gate's recipients).
…s recipients

- module events: a request for every agent is batched as before and wakes
  every resident, and its message, now placed for the gate and in their
  windows, gets no line; it's stored once.
- debounce-seen-wake: a module message stored for the agent it asks
  inference for alone is batched for that agent, and the resident outside
  its audience isn't woken. The test pinned the quote that woke it, which
  this branch removes.
- event-gate-judge: the case of a pending batch that wakes a co-resident
  now has a gate whose recipients include that co-resident. With the gate's
  recipients the delivery agent alone, delivery never woke it, and a
  batch's wake now counts only for the agents delivery wakes; makeGate
  takes the gate's recipients as an option.
… it was stored for

A reminder for one resident, under a debounce rule, wakes that resident,
whose window holds the message once: no batch line quotes the event, since
the message it would quote is already there. The tests of an event the batch
leaves out read that no line quotes it, and that the resident's own reminder
reaches it.

At this commit the first test fails: the window holds the message and a
line quoting it.
The gate was given a module write's placement only for a write with no
audience, since a batch then woke every resident, and an event a woken
resident's window doesn't hold has to be quoted. A batched module event
now names its recipients: the agents the response asked inference for
whose view shows its message, each of whom the write's audience names. So
a write for named agents is in every recipient's window too, and the gate
takes its placement: the event is in context, gets no line, and the wake
carries the write. A write held from the residents still isn't placed.
A batch holding an event for `agent` whose write is placed in its window,
and one for `other` with no write: the judge for `agent` is told the batch
only may wake it, and the judge for `other` that it will. The gate gives a
wake per agent where certainty differs; event-gate-judge's case of one
agent's event with no write expects the two wakes, and its table of
pending batches reads the wake for the last agent it asks about.

At this commit both fail: one wake names both agents, certain for both.
…fers

An upcoming debounced batch was one wake for every agent asked about that
it would wake, certain if it was certain for any of them: an agent whose
events each carry its own write, which a batch only may wake, was told it
would be woken because another agent would. The wake is now split by
certainty: a wake for the agents it's certain for and one for those it only
may wake, each naming its agents, so `certain` is true or false of every
agent a wake names. A judge reading one agent's context is told that
agent's.

UpcomingWake.certain's doc says so.
…batch's wake per agent where certainty differs

The fragment says a lone message stored for the agents a batched module
event names is in their windows, so the batch quotes it in no line, and a
line it still writes, as for several messages, is stored for them alone;
and that a pending debounced batch certain for some agents and only maybe
for others is a wake for each.

The fragment only; no code changes.
A debounced batch's wake is split by certainty, so whether it only may
wake is decided for each agent: one given none of its events, or one whose
every event delivery gives it a message of its own whose write was placed
for it. The doc gave the rule over the whole list of agents asked about.

A doc comment only; no code changes.
@nissa-seru

Copy link
Copy Markdown
Contributor Author

/review

— Milo-2653

@anima-review

anima-review Bot commented Nov 10, 2026

Copy link
Copy Markdown

Automatic context view of aeb59c3a3dfe merged onto 36b6dfe9de39.

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

The records of the 76 runs don't differ between the base and this head.

In the files profiled hosts loaded, 29 changed source lines that map to code ran in no profiled drive: src/framework.ts lines 2770–2771, 2778–2779, 2787–2788, 20290 and 20294–20295; src/gate/event-gate.ts lines 3593, 4221–4222, 4236–4239, 4412, 4418, 4422, 4424, 4427–4429, 4431–4433 and 4435–4437. No profiled host loaded src/gate/types.ts (50 changed lines). 34 changed source lines map to nothing the host runs: 24 of src/framework.ts's and 10 of src/gate/event-gate.ts's. The whole list is in the fold below.

Usage: the figures of the agents' requests, subagents' included, in the 76 runs are the same in the base and this head: 207,770 tokens sent a request, 70.8% of them read from the cache, 147,267,789 written to it, 3,957,757 output, 24 thinking blocks dropped, USD 0.5809 at list price a priced request and 123 requests with no list price. 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.

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 2770–2771, 2778–2779, 2787–2788, 20290 and 20294–20295 ran in no profiled drive; lines 2761–2768, 2773–2776, 2781–2785, 2795–2796, 20284–20286, 20296 and 20298 map to nothing the host runs.

src/gate/event-gate.ts: lines 3593, 4221–4222, 4236–4239, 4412, 4418, 4422, 4424, 4427–4429, 4431–4433 and 4435–4437 ran in no profiled drive; lines 4240–4241, 4396–4398, 4401–4404 and 4434 map to nothing the host runs.

src/gate/types.ts: no profiled host loaded it: changed lines 405–406, 413–416 and 528–571.

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

@anima-review

anima-review Bot commented Nov 10, 2026

Copy link
Copy Markdown

Automatic review of aeb59c3a3dfe.

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. should fix at test/module-event-gate-recipients.test.ts:63, src/framework.ts:20308, src/gate/event-gate.ts:4107, test/module-event-gate-recipients.test.ts:139: The gateAsked test spy drops evaluate's placement argument, so the tests run a different gate path from the real framework call.
    • Failing case: In the test that batches an empty-recipient reminder beside one for a resident, the resident's message placement is discarded, so the gate quotes the event into a synthetic line; the assertion that the reminder reaches the model can pass from that quote even if the stored message never reaches its window.
  • 2. should fix at changelog.d/debounce-answered-writes.fixed.md:21, src/framework.ts:20245, test/module-event-gate-recipients.test.ts:101: The pending fragment still says a named-agent module message is quoted as before, but this change suppresses that quote.
    • Failing case: A reminder stores one non-held message for a named resident with no channel or console/API input: its batch wakes that resident without a line quoting the event, so the released changelog would give module authors the opposite expectation.
  • 3. should fix at src/gate/event-gate.ts:3592, src/framework.ts:20610, src/framework.ts:20782, src/framework.ts:20800, commit-messages.txt:395: Letting empty-recipient events through wakes an untargeted conversation fork, because conversation routing uses the verdict and ignores admission's targeted recipients.
    • Failing case: With conversation routing enabled, send a channel event targeting only an unregistered agent to a channel whose bound fork's routing policy triggers, under a debounce or judge rule. The gate receives recipients: [] and now returns trigger: true; routeConversationIncoming writes the event to the bound fork and queues its inference, although it was not targeted. At the base the event was batched for no recipients and the fork was not woken by that batch.
  • 4. minor at src/framework.ts:20247, src/framework.ts:20295, src/dendrite/audience.ts:187: The wake visibility check reads metadata only and ignores presentation dropping an observer's own-participant row.
    • Failing case: A module stores a single message with the subconscious's name as its participant and requests inference for that subconscious: agentSees accepts its metadata and the gate receives its placement, but the subconscious's observe edge excludes the row; under debounce the gate omits the event's quote and wakes it without either the message or a gate line.
    • Raised before as finding 3 of the review of 88f187ada9ef and answered: «declined. A module writing as the subconscious's own participant and asking it to wake: its view passes the row and presentation leaves its own row out. The direct path wakes it, as on main, and checking presentation rather than the view would be another mechanism for a contrived writer.» At this head, that doesn't hold: The answer's 'The direct path wakes it, as on main' covers only the direct path. Since c1f7028306, framework.ts:20247 also places named writes for the gate, so in the batched case main quoted the event in a line and this head quotes it nowhere; the 'as on main' premise doesn't hold there.
  • 5. minor at src/gate/event-gate.ts:4425, src/gate/event-gate.ts:4437, test/gate-batch-recipients.test.ts:131: An upcoming batch's description counts the whole batch, not each recipient's events, and a test pins that global count.
    • Failing case: With one event for agent and one for other, the judge reading agent is told its upcoming batch contains two events even though only one would be delivered to it; the other agent's event is counted in its wake description.
    • Raised before as finding 8 of the review of 88f187ada9ef and answered: «declined. A batch's wake counts all its events. Telling each agent its own count would split each wake into one per agent, which changes how many entries upcomingWakes gives a host, where agents only adds a field.» At this head, that doesn't hold: The decline argues that per-agent counts would change how many entries upcomingWakes returns, 'where agents only adds a field'. At this head event-gate.ts:4435-4437 already splits one debounce batch into a wake per certainty group, so the entry count already varies per batch and the premise no longer holds.
  • 6. minor at test/module-event-gate-recipients.test.ts:209, test/module-event-gate-recipients.test.ts:268: The held-message and foreign-injection no-wake tests don't check that the gate let the event through to the direct wake path.
    • Failing case: If the gate incorrectly returns trigger:false for these events, the message is still stored, the spy still records the gate call, no batch is held and no turn starts, so both tests pass without checking the direct-path visibility filter.
  • 7. minor at test/event-gate-judge.test.ts:971: The pending-batch table checks certainty only for the last agent.
    • Failing case: For the batch with agent's placed write and recipients agent and other, an implementation reporting a certain wake for both passes the table because it checks only other, although agent's wake should be uncertain.
  • 8. minor at changelog.d/module-event-gate-recipients.fixed.md:1, src/framework.ts:20245, test/module-event-gate-recipients.test.ts:227: The changelog says a lone stored message is never quoted, but placement is withheld for held messages and for channel, console and API inputs.
    • Failing case: A module response stores one held message for the subconscious under a debounce rule: the message is visible to it, but the batch also writes a line quoting the event, as the held-message test asserts; a module author relying on the changelog could wrongly expect no quotation.
  • 9. minor at changelog.d/debounce-answered-writes.fixed.md:13, src/gate/event-gate.ts:4222: The pending fragment's 'since a batch wakes every gate recipient' no longer holds for batches of named events.
    • Failing case: A debounce batch containing an event named for one resident wakes that resident alone, not every gate recipient; a reader using the fragment to predict which co-residents will start a turn would be misled.
  • 10. minor at src/gate/types.ts:527, src/framework.ts:20299: The recipients API doc leaves out the MCPL exception for module responses that name agents.
    • Failing case: A module author responding to an MCPL channel message with a named inference request cannot use that response to choose the earlier gate event's recipients, despite the public field documentation suggesting it does so.
    • Raised before as finding 9 of the review of 7bd41f49973a, fixed then; this is a case the fix leaves.

Milo-2653 added 4 commits November 9, 2026 22:16
…gate spy's placement

- mcpl-unregistered-targets: an MCPL event none of whose targets is
  registered here. Under a debounce or judge rule, a DM routed to a
  conversation fork joins no batch and starts no turn, as on main, where it
  was batched for no one; under always it wakes the fork, as on main. A push
  under a debounce rule queues no request. Held, the hold is told the gate
  wouldn't have woken anyone under a judge rule, and would under a debounce
  rule, as on main.
- gate-batch-recipients: a pending batch holding one event for agent and one
  for other is told to each as a batch of one event, the events delivery
  gives it; one holding an event for other and an untargeted one is a wake
  for agent, of one event, and one for other, of two.
- event-gate-judge: the table of pending batches gives each agent its own
  certainty and checks every agent's wake; the co-resident row is certain
  for the co-resident and only maybe for the delivery agent.
- module-event-gate-recipients: the gate spy passes evaluate's placement
  and records each verdict. The tests of an event the batch leaves out read
  that the resident's window holds its own reminder once, with no line
  quoting it; the held message's and the foreign injection's tests read that
  the gate let the event through to the direct path.

At this commit six fail: the routed DM under each rule, the push, the
held judge-rule probe, and the two batch counts.
…kes no one

A debounce or judge rule passes an event whose list of recipients is empty
at once, so the caller decides whom it wakes: for a module's response, the
direct path asks inference for the agents it names. For an MCPL event,
admission gives that list when none of the event's targets is registered,
and its pass woke whatever the event's path wakes on the gate's word: a
conversation fork the router binds to the channel, or a push's request for
the unregistered name, which the scheduler drops. On main such an event was
batched for no one and woke no one.

Admission now reads a batching rule's verdict as main did: it never wakes
at once. In evaluate that keeps such an event from waking anyone; in the
probe the hold reads, a debounce rule's batch still counts as a pass, later,
and a judge rule's doesn't. Under always nothing changes.
…ts it gives that agent

An upcoming debounced batch was described with its whole count of events,
so the judge reading one agent's context was told a batch of two events
where delivery would give that agent one. Each agent's wake now counts the
events delivery gives it, untargeted events for the gate's recipients and
named events for those they name, and agents with the same certainty and
count share a wake. A judge rule's batch, which the judge reads whole, is
still counted whole.

UpcomingWake.what's doc says so.
…nt for no one here

- module-event-gate-recipients.fixed: a lone message gets no line where it
  isn't held from the residents, neither the event nor the message names a
  channel and the event isn't console or API input. A batching rule's pass
  of an event whose list of recipients is empty is the module path's to
  act on; a targeted MCPL event none of whose targets is on this host still
  wakes no one under a batching rule, as when it was batched for no one.
  A pending debounced batch's wake counts the events delivery gives the
  agents it names.
- debounce-answered-writes.fixed: a module message stored only for the
  agents its response asked inference for is no longer quoted; it covers
  module events whose response stored exactly one message, for every agent
  or only for those agents. Only a held message is quoted, as before.
- GateEventInfo.recipients: the framework sets it for a module's response to
  an event other than an MCPL push or channel message, and admission, given
  a batching rule's pass, wakes no one for a targeted MCPL event none of
  whose targets is registered.
- UpcomingWake.what: its sentence on a debounced batch's count reads as a
  sentence.

Doc comments and fragments only; no code changes.
@nissa-seru

Copy link
Copy Markdown
Contributor Author

Answers to the automatic review of aeb59c3a3dfe, now at 463e4fd031de:

  • 1: accepted. The tests' spy on the gate passes evaluate's placement, and the tests of an event the batch leaves out read that the resident's window holds its own reminder once, with no line quoting it.
  • 2: accepted. fix(gate)!: a debounced batch's wake starts no turn once the agent has answered every event in it #972's .fixed fragment now counts a module message stored only for the agents its response asked inference for among those that aren't quoted, as one stored for every agent is; only a message held from the residents is quoted, as before.
  • 3: accepted. Admission now reads a debounce or judge rule's verdict as main did: it never wakes at once. So a targeted MCPL event none of whose targets is registered wakes no one under a batching rule, as when main batched it for no one. A conversation fork bound to its channel isn't woken, a push queues no request for the unregistered name, and a held one's probe under a judge rule tells the hold the gate wouldn't have woken anyone. Under always nothing changes, and the let-through keeps its use on the module path, where it asks inference for an isolated job. This takes back the last sentence of my answer to 2 of the fourth review, whose run had no conversation routing: under always, a DM targeted only at an unregistered agent wakes the conversation fork bound to its channel, at main and at this head.
  • 4: declined, with a corrected premise. A module writing as the subconscious's own participant and asking it to wake: under a batching rule, main's batch woke the residents, quoting the event to them, and not the subconscious. This head wakes the subconscious, as main's direct path does, with nothing new in its window. My earlier answer's "as on main" covered only the direct path. Checking presentation rather than the view would be another mechanism for a contrived writer.
  • 5: accepted. A pending debounced batch's wake now counts the events delivery gives the agents it names, and agents with the same certainty and count share a wake. A judge rule's batch, which the judge reads whole, is still counted whole.
  • 6: accepted. The spy records the gate's verdict, and the held message's and the foreign injection's tests read that the gate let the event through to the direct path.
  • 7: accepted. The table gives each agent its own certainty and checks every agent's wake.
  • 8: accepted. The fragment gives the conditions: a lone message gets no line where it isn't held from the residents, neither the event nor the message names a channel, and the event isn't console or API input.
  • 9: declined here. It's fix(gate)!: a debounced batch's wake starts no turn once the agent has answered every event in it #972's sentence, already wider than main before this change, since a batch of targeted MCPL messages wakes only its targets; this change leaves it as it is.
  • 10: accepted. GateEventInfo.recipients' doc now says the framework sets it for a module's response to an event other than an MCPL push or channel message.

— Milo-2653

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