Repository navigation
fix(framework): awaited tool calls and the turn's dispatch share one routing function - #913
Merged
Merged
Conversation
added 5 commits
October 29, 2026 17:04
…call does The admin puppet (puppetToolCall), a host's executeToolCall and a module's ModuleContext.callTool all run through executeToolCallFrom, which routed only some of the framework's own tools. The rest fell to the module registry and failed with "Invalid tool name format", and the puppet stored that failure in the agent's window as a call the agent had made. A household's operator met it as `/puppet agent_settings`. The file pins what an awaited call must do: - the puppet's agent_settings, workspace tools (save_recent_image, read_image, fetch_reference), gate tools (gate_status, event_tags, wake_add_rule, wake_remove_rule, sleep, wake), think, journal and prose_help each answer as the agent's own call of the same input does, and prose_help, on an explicit-prose agent's surface for the puppet, answers with the routing reference; - skip_reply, which ends the turn it's called in, is refused off a turn: the puppet refuses it, by name or through a toolsets--act that reaches it, with the agent's window unchanged, and a host's call gets the refusal as its result; - a module's call of the agent's own tools (think, journal, skip_reply, drafts) is refused with a reason naming the tool as the agent's own, even when it names the agent as its caller; - a call with no agent behind it (no callerAgentName): think and journal answer as they answer the agent's own call, skip_reply is refused; - a conversation fork's awaited sleep gets the dispatcher's refusal without `refused`, which ToolResult reserves for the dispatcher's own calls; - a host's fetch_reference whose ref_id has no text form answers with the error, as the agent's own call does; - a module that saves an image from inside its own tool call is answered at once, and the image is written, rather than held by the round its own call is in. Against the base's src all ten fail, each on the defect: "Invalid tool name format", the puppet's "not on agent's surface" refusal, or no refusal where one is due.
…tool toolRoute decides where a call goes, from its name, in the order the turn's dispatch decided it, and both dispatchToolCall and executeToolCallFrom switch on its answer. Each switch covers every ToolRoute kind (dispatch's ends in `route satisfies never`, and the awaited path must return), so a route added for one caller is a compile error until the other handles it. Before, executeToolCallFrom kept its own list, which lacked 14 of the names the turn routes: agent_settings, save_recent_image, read_image, fetch_reference, gate_status, event_tags, wake_add_rule, wake_remove_rule, sleep, wake, think, journal, skip_reply and prose_help. Each fell to the module registry and failed with "Invalid tool name format". - The eight dispatch handlers those routes need (agent settings, save and read image, fetch reference, gate status, sleep and wake, wake rules, event tags) take a `deliver`, defaulting to the tool-result push they made before, so the awaited path runs the handler the turn runs and gets its answer through awaitedToolAnswer. That drops `refused` (ToolResult.refused: a programmatic call is never refused) and answers a synchronous throw with its text. - think and journal on an awaited call with agent origin go to the channel registry as the awaited channel tools already did. skip_reply is refused there: it ends the turn it's called in, and an awaited call isn't made in one. - The puppet refuses skip_reply, by name or through a toolsets--act that reaches it, before anything runs or is stored, as it refuses a tool off the agent's surface: a stored pair must be an act the agent could have made, and the agent's own skip_reply never answers "not in a turn". - A module's call of think, journal, skip_reply or drafts is refused as the agent's own (agentsOwnVerbRefusal). This keeps the rule from e86ae33's review round, that these verbs are the agent's, for every caller but the agent itself; the old comment saying they were "deliberately not routed here" is removed with the list it described. - The awaited path now takes the turn's order: code_execution after toolsets--act, MCPL, the channel tools and the framework's own tools, and drafts and the heartbeat after tune_out, focus and the subconscious's tools. None of these names can reach another of those routes (an MCPL name always carries `prefix--`, a heartbeat tool `heartbeat--`), so nothing a caller meets changes from the order alone. - The puppet's surface check reads availableToolsForPresentation (the tools the agent may call, prose_help included for an explicit-prose agent) instead of getToolsForAgent filtered by canUseTool. - fetch_reference's detached body had no catch, so a throw before it answered (a ref_id with no text form) left the agent's own call unanswered, as it would have an awaited one. It now answers once, with "fetch_reference couldn't answer: …". - save_recent_image's sibling barrier waits only for a call that is one of its round's pending calls. A call outside the round, a script's or an awaited call's, counts the images the agent has now. Only a script's was exempt before, so a module's save from inside its own pending tool call, which this change lets reach the handler, would have waited on its own call until the barrier's bound.
…s own tools Its doc named only "(module registry or MCPL)" as the handlers it routes to, which said less than the method does once it takes the turn's routing: it also reaches the framework's own tools and the channel tools, and refuses skip_reply, which ends the turn it's called in. A host's reach is an addition under the rule for published libraries (a host that doesn't call these names meets no change), so it gets its own `.added` entry beside the `.fixed` one.
…ches The gate tools route only with an event gate, and think, journal and skip_reply only with a channel registry, which an MCPL server brings, on the turn's dispatch and the awaited path alike. executeToolCall's doc and the Fixed fragment said the calls run unconditionally; they now say they reach the tools the agent has. - The Fixed fragment's "(__ephemeral__ where it names none)" for a module's call is dropped: tune_out and focus take the primary agent when no callerAgentName is given, as on main. - The Added fragment is trimmed to one line that points at the Fixed entry, which already lists the tools and the skip_reply refusal. It no longer says a host that doesn't call these names meets no change, since a module's call of the agent's verbs now gets a named refusal.
nissa-seru
force-pushed
the
alfred/puppet-routes
branch
from
October 30, 2026 01:09
28a55ac to
42567ac
Compare
tel-0s
added a commit
that referenced
this pull request
Oct 30, 2026
…p durable before its end, gate texts as written, awaited calls routed as the turn's, a stop that waits for a start main at 48f26ce is four merges past d700dce, with no release among them, so package.json stays 0.30.0 and this branch's floors stand (chronicle ^0.6.0, context-manager ^0.25.0, membrane ^0.8.0), which test/dependency-ranges pins. It brings #910 (the gate's refusals and file errors name fields, rules and gate.json's path as written, and a change says it is saved only when it brings gate.json into force), #909 (a channel's tune-out cancel stores its dump and makes it durable before it records the end; each dump row records the held rows it covered (`released`), so a retried cancel shows nothing twice; commitDeliveries answers whether the release is durable; every timer's cancel is tried again when it throws, at the start too, and a tune-out past its wake budget is cancelled at the start; a tool close of the excepted channel whose release fails answers "The close wasn't recorded"), #913 (the turn's dispatch and an awaited tool call take one route per tool, toolRoute; the puppet refuses skip_reply) and #912 (a module's stop() waits for its start() to settle; addModule() refused once a stop() is admitted, after the add). Three paths conflicted (17 regions): - src/tune-out/coordinator.ts (12), the #909-into-#915 port. This branch has no deferred queue (#291), and its cancel waits while the framework keeps writes in memory (writesHeld: a surgery's hold, a manager refusing its work; deferCancel / runDeferredCancels, answered `deferred`, the tools' `waiting` + `note`). #909's rule goes into that cancel as it stands: - cancel(): reads the tune-out's state (getTuneOutState) first, and answers "not tuned out" without it; then, writes held, waits as before, nothing dumped or ended. Past the wait: the backlog is the epoch's rows (residentMessages: stored, then a surgery's kept writes where writesHeld threw) less those an earlier dump of the epoch covered (releaseRecordsOf over the same rows); the dump is written with its `released` record, and none when an earlier dump covered every row; commitDeliveries() === false throws, the tune-out in force; only then cancelTuneOut records the end, and the channel's timers and diverted count are cleared. The subconscious's report and the last reader fork's notice sum what the epoch's dumps left out (truncationNote). Main's heldForReport (queued rows handed to the subconscious as tune-out-cancel-held rows) stays out, as on this branch: every held message is stored, and the report keeps its unseenBySubconscious count; the record's `report` is always empty here, kept so the record has the shape main reads. - cancelAllExcept(): the same, over the hold's rows; main's `released` record replaces this branch's in-memory scopeReleased mark, and commitDeliveries() === false throws before writeScope(null). - Rows are keyed by id, and by `deferredWriteId` (writeIdOf, kept): a row an earlier version queued, stored by the boot's import, is named by its queued write's id in that version's records, so a cancel retried after an upgrade finds it covered. A surgery's kept write has no id, so a dump made during the hold (writesHeld having thrown) can't name it; a retry of that dump dumps it again. - resumeActiveEpochs(): main's try/catch around the start's cancels, their retries (armExpiry's and armScopeTimers' EXPIRY_RETRY_MS) and the start's wake-budget cancel (cancelPastBudget); main's `inForce`/oldestAnchor bookkeeping goes, since this branch anchors from the tune-outs the record holds after the cancels (pruneWindowStarts, anchorSubconscious('resume')), which leaves out one whose cancel ended it and counts one whose cancel waits. - The hooks' getDeferredMessages stays out (this branch's getSurgeryHeldMessages); commitDeliveries is `() => boolean`, its doc kept and rewritten. cancel()'s and cancelAllExcept()'s doc comments, which main removed, stay, rewritten to the new order: they carry this branch's `deferred` answer. - src/framework.ts (4): - addModule(): main's refuseOnceStopped('addModule()') after the add, with this branch's comment on useHistory (its check leaves out an undone row). - the deferred queue's persistence (persistDeferredWrites and the queue's record): stays deleted, as on this branch; main's change there was persistDeferredWrites' boolean. - onToolClose: main's form (a release that throws or refuses answers "The close wasn't recorded: …"), with this branch's sentences for a release that waits and one that went through ("The focus on it ended, …", 6243858). - commitDeliveries(): answers false when the store's sync fails and true otherwise; there is no queue to persist. - src/gate/event-gate.ts (1): a pending batch whose delivery throws: main's `Rule "<name>"` opening, with this branch's deliveredInPart for a batch that woke some recipients before it threw. Read onto this branch past the conflicts: - docs/dendrite.md: a cancel stores and syncs its release before it records the end; one whose release can't be made durable throws, the tune-out in force, and the next cancel delivers what no earlier release covered (`released`). Main's sentence on the all-except hold's start came in clean. - test/all-except-unit: main's tests take this branch's harness ({ stored, kept, writesHeld }); its anchors hook reads the start's sequence (setSubconsciousAnchor takes a SubconsciousWindowStart here). - test/tune-out-cancel-order: no getDeferredMessages hook; the anchors hook as above. Main's "a message queued at a dump whose end record failed, which lands before the retry" has no queue to stand on here; it becomes the upgrade case: a dump an earlier version wrote, naming a row by its queued write's id, and that row stored by the import with deferredWriteId, gets no second dump. storeSees: no new site; main opens and derives no context manager. agentSees: no new view decision. #913's awaited routes run the turn's handlers with a `deliver` that answers the caller; none stores a row of its own (the puppet's pair is stored as before, its agent's own row). Notice tags: none new; #910's texts are tool results and stderr lines, #909's are stderr lines and errors thrown to a tool or timer. Changelog: no release in main's range, so no fragment was consumed; main's seven new fragments come in as written. 291-deferral-queue's tune-out hosts' bullet says that with no queue a release fails to be made durable only where the store's sync fails, so no queue over its cap keeps a tune-out in force (tune-out-cancel-order's sentence), and how a dump's record names a row. Tests: built with zero errors; no stale compiled test; fixtures copied. main's 16 changed test files: 419 of 419. presentation*, window-*, dendrite-*, stored-injections, audience-stamp*, dependency-ranges, held-*, tune-out-*, admission-e2e, all-except*, mcpl-coalescing*, operator-surgery, read-marks* and turn-inputs-pin (53 files): 964 of 964. With this branch's coordinator as it was (HEAD's), and the rest of the merge, tune-out-cancel-order, all-except-unit, tune-out-timers and tune-out-gate-composition fail 21 of 77; at this merge, 77 of 77. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 task done
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
With this change an awaited tool call takes its route from the function the turn's dispatch uses, so where such a call made as an agent failed with "Invalid tool name format" on 13 framework tools the agent has, it now gets each tool's own answer, and on
skip_reply, which ends the turn it's called in, a refusal.An awaited call is one whose caller waits for the result in process. The admin puppet's
puppetToolCalland a host'sexecuteToolCallmake it as the agent, and a module'sModuleContext.callToolmakes it as the module. In a probe built frommain's source, a host'sexecuteToolCallof each of 14 names that the turn's dispatch routes,agent_settingsamong them, answered "Invalid tool name format: NAME, so nothing was done". The published 0.30.0 gave the puppet'sagent_settingsthe same answer, and connectome-host's/puppetcommand callspuppetToolCall. Built from this change atd1be0036a3, 13 of the 14 answered as their handlers do, andskip_replygot a refusal that says why. This change's ten new tests fail againstmain'ssrc/framework.tsand pass atd1be0036a3.Problem
puppetToolCall,executeToolCallandcallToolall go throughexecuteToolCallFrom(src/framework.ts:27124atmain8145085d06). It checked its own list of routes and passed any other name to the module registry (:27233), which answers a name without--with "Invalid tool name format: NAME, so nothing was done" (src/module-registry.ts:371). Fourteen names the turn's dispatch routes weren't on that list:agent_settings;save_recent_image,read_imageandfetch_reference;gate_status,event_tags,wake_add_rule,wake_remove_rule,sleepandwake;think,journalandskip_reply;prose_help.In the probe, each of the 14 got that answer when a host's
executeToolCallnamed the agent incallerAgentName. The probe's framework had an MCPL server and an event gate, which give the agentthink,journalandskip_reply, and the gate tools (see Changes).The puppet is where an operator meets it. connectome-host's
/puppet <tool> [json]command callspuppetToolCall(src/commands.ts:980in connectome-host 0.19.0).puppetToolCall's doc says the call "executes FOR REAL through the shared dispatch (MCPL, channel tools, utils, modules)", is refused "for tools outside the agent's own surface", and is stored in the agent's window "as a model-initiated call is stored" (src/framework.ts:26925–26948at8145085d06). Two runs show what it does withagent_settings:@animalabs/agent-framework0.30.0 from npm, makes an agent withagent_settingson its surface and callspuppetToolCall('agent', 'agent_settings', {})gets{"success":false,"error":"Invalid tool name format: agent_settings, so nothing was done","isError":true}.main's source. A puppetedagent_settingsadds two messages to the agent's window: atool_useofagent_settingsunder the agent's name, and itstool_result, marked as an error, reading "Invalid tool name format: agent_settings, so nothing was done".The puppet also refused
prose_helpfor an agent with explicit prose routing. Its surface check readgetToolsForAgentfiltered bycanUseTool(src/framework.ts:26998–26999at8145085d06). Withmain'ssrc/framework.ts, this change'sprose_helptest fails with "puppet refused: tool prose_help is not on agent's surface".Changes
toolRoute(src/framework.ts:29152) returns a call's route as aToolRoute(:819), checking names in the order the turn's dispatch checked them.dispatchToolCall(:29249) andexecuteToolCallFrom(:27190) each switch on what it returns.dispatchToolCall's switch ends inroute satisfies never(:29449).agent_settings,save_recent_image,read_image,fetch_reference,gate_status,sleepandwake,wake_add_ruleandwake_remove_rule, andevent_tagstake adelivercallback. It defaults topushToolResult(:29181), which pushes thetool-resultevent they pushed before. The awaited path passes one throughawaitedToolAnswer(:29185), which resolves with the handler's result and answers a handler's synchronous throw with its text.thinkandjournalgo to the channel registry, as the awaited path's channel tools already did, andprose_helpanswers with the routing reference the turn's call gets.skip_replyis refused off a turn. It ends the turn it's called in, and an awaited call isn't made in one. A host's call gets "skip_reply ends the turn it's called in, and this call wasn't made in a turn, so nothing was done." The puppet refuses it by throwing, and stores nothing, whetherskip_replyis called by name or through atoolsets--actthat resolves to it.callToolofthink,journal,skip_replyordraftsgets "NAME is the agent's own, which a module's call can't make for it, so nothing was done." Onmaina comment on this path says the agent-only synthesized verbsthinkandskip_replyare deliberately not routed there (src/framework.ts:27188–27190at8145085d06, from @slimepriestess'sedbb62271c). With this changethinkandjournalrun for a callexecuteToolCallmakes as the agent, the puppet's or a host's, and a module's call of any of the four is refused.toolRoutegives them their routes only when the framework has a channel registry (:29174,:29176), as the turn's dispatch did, and the framework builds the registry when it starts with an MCPL server or connects its first one (:5045,:34055–34057).toolRoutegivesgate_status,sleep,wake,wake_add_rule,wake_remove_ruleandevent_tagstheir routes only when the framework has one (:29167,:29170,:29172,:29173), as the turn's dispatch did.availableToolsForPresentation, the tools the agent may call (:27036), so an explicit-prose agent'sprose_helpis on it.refusedon an awaited answer. Where the dispatcher answers one of the calls above with a refusal, such as a conversation fork'ssleep,awaitedToolAnswerpasses on the refusal's text withoutrefused.ToolResult.refused's doc says a programmatic call "opens no RFC-007 call, so it's never refused" (src/types/events.ts:227–228).fetch_referencenow answers "fetch_reference couldn't answer: …" when its work throws before it answers, such as for aref_idthat can't be turned into text, on the turn's path and the awaited one (:37182).save_recent_imagewaits for its round's other calls only when it is one of that round's pending calls (:37309), so a module's save made from inside its own pending tool call isn't held by the round that call is in. The check this replaces exempted a script's calls (pytc-…) alone.executeToolCall's doc said it routes "(module registry or MCPL)". It now says the call goes where the agent's own call of the name goes, which includes the framework's own tools the agent has, and thatskip_replyis refused.puppetToolCall's doc names the surface it checks and itsskip_replyrefusal.test/awaited-tool-routes.test.ts, new, with 10 tests.changelog.d/awaited-call-routes.fixed.mdandchangelog.d/awaited-call-routes.added.md, new. The second covers a host'sexecuteToolCallreaching the framework's own tools that the agent has.What each part is for
toolRouteand its 21-kindToolRouteput the route decision in one place for both paths. The 14 names the awaited path missed were names the turn's dispatch routed and the awaited path's own list didn't, so one function for both keeps the two from drifting apart again.dispatchToolCall'sroute satisfies neverstops a new kind from compiling until that switch handles it. Routingagent_settingsalone on the awaited path would have fixed the reported call and left the two lists to drift.delivercallback on eight handlers, defaulting to the push each made, lets the awaited path run the handler the turn runs, rather than a copy of it. The default leaves the turn's path pushing each result where it did before. Handlers that returned a promise instead would have moved those pushes to a later microtask, and copies for the awaited path would have been two bodies to keep alike.awaitedToolAnsweris the one place the awaited path turns a handler's answer into its result. It dropsrefused, whichToolResult.refused's doc says a programmatic call never gets, and answers a handler's synchronous throw with the throw's text, so the caller gets an answer either way.agentsOwnVerbRefusalgives the four tools a module can't call for the agent one refusal text.skip_replyrefusal off a turn, and the puppet's check for it, keep an awaitedskip_reply, made outside any turn, from reaching the handler that ends the turn it's called in. The puppet's check reads the call's route, so atoolsets--actthat resolves toskip_replyis refused the same way, before anything is stored.think,journalandprose_helpgo where the awaited path's channel tools and the turn'sprose_helpalready went, so they need no new handler.prose_helpfor an explicit-prose agent among them.fetch_reference's catch gives a call an answer when the work behind it throws, on the turn's path and on the awaited path, which this change opens to it.save_recent_image's round check replaces an exemption for a script's calls (pytc-…) with the rule the exemption stood for: wait for a round's other calls only when the save is one of that round's pending calls. This change needs the rule once a module's save reaches the handler.test/helpers/coalescing-fixture.ts), and builds two frameworks of its own for a conversation fork and a module's image save.prose_help's test also checks that the call succeeds, so two equal errors can't pass it..fixedsays what the puppet, a host and a module meet now..addedis one sentence, that a host'sexecuteToolCallreaches the framework's own tools the agent has, pointing to the Fixed entry for the rest.With other open pull requests
At 01:11 UTC on October 30, of the 36 other open pull requests that change
src/framework.ts, twelve merge withmainatd700dce18bwithout conflicts, bygit merge-tree:27f893b9ab, merges with this change without conflicts.never, don't fetch or save its payload, and forget an earlier copy's path #851, fix(gate)!: answer the reply judge's first review: refused calls, a failed last turn, pending batches, quoted participant text and too-long requests #850, fix(framework)!: decline a maxStreamTokens restart that won't fit #842, feat(agent): declare later tool and system-text changes in the context #690 and fix(mcpl,api): read a JSON-RPC envelope once: answer id null, refuse an invalid request with -32600 #534, at5d9af7beec,a2ab0fd8d5,f658e4ab41,e6908ed473,119cb8662d,2f15c4293d,0342d90c75,60540f5d07,598f8cc58candf9337c4794, merge with this change without conflicts.191fc24821, conflicts with this change insrc/framework.ts. It passes aworkTurnto the tune-out and focus handlers and toarmSelfWake, in the code this change moves into the switchesdispatchToolCallandexecuteToolCallFrommake ontoolRoute's result.What this leaves
#911 describes a gap this change leaves as it was: the awaited path's
channel_*route doesn't make the checks the turn's dispatch makes for a conversation fork. In a probe, a host'sexecuteToolCallnaming a fork opened a channel other than the fork's home, and a module'scallToolnaming the fork published to that channel, where the fork's own calls of both were refused.Tests
This branch's commit messages were reworded after the runs below, with every commit's tree unchanged, so each run is named by the commit that now holds the tree it ran:
d1be0036a3was8d7e7c1977,862c981e90was28f47f2395, and42567acbafwas28a55ac252. Each run below was on Linux, in a clean checkout. The runs atd1be0036a3and862c981e90printed Node 24.22.1, npm 11.19.1 and TypeScript 7.0.2.d1be0036a3, in each of two runs: the build (npm run build) succeeds, andtest/awaited-tool-routes.test.tspasses 10 of 10. These are the runs this description's probes ran in, each with its own probe file added totest/, and both printed the dependenciesnpm installgave: chronicle 0.5.0, context-manager 0.24.1, membrane 0.8.0, mcpl-core 0.4.0,@modelcontextprotocol/clientand/server2.4.0, discord.js 14.27.0, ws 8.22.0 and chokidar 4.0.3.862c981e90, which mergesmainat8145085d06into this change.d1be0036a3changes onlyexecuteToolCall's doc comment over it and addschangelog.d/awaited-call-routes.added.md. This run, about 20 minutes before the two runs above, didn't print its dependencies' versions.test/awaited-tool-routes.test.ts: 10 of 10 pass.main'ssrc/framework.tsin place of this change's: 0 of the 10 pass. Each fails on "Invalid tool name format", on the puppet's "not on agent's surface", or on a refusal that didn't come.puppetToolCall,executeToolCall,callTool(ordispatchToolCall, oragent_settings,save_recent_image,read_image,fetch_reference,gate_status,event_tags,wake_add_rule,prose_help,tune_outorsleepwith a character on each side, andmain'stest/turn-inputs-pin.test.ts, which the merge brought in.fetch_reference's catch removed; thesave_recent_imagewait's pending-call check removed;refusedkept on an awaited answer; and the puppet'sskip_replycheck reading only the name it was called with.42567acbaf, this head, changes onlyexecuteToolCall's doc comment and the two changelog fragments overd1be0036a3. In a run in a clean checkout of it, which printed no versions, the build succeeds andtest/awaited-tool-routes.test.tspasses 10 of 10.Probes. The probes in this description, apart from the published package's, ran in the two runs at
d1be0036a3above, built once withmain'ssrc/framework.tsand once asd1be0036a3is.src/framework.tsis the only file undersrc/that this change touches, so the first build'ssrc/ismain's. The published package's probe installed 0.30.0 from npm on its own.Not verified
d1be0036a3or at862c981e90. The runs above cover 115 of the 388 test files./puppetwasn't run against this change: the tests callpuppetToolCalldirectly.save_recent_image,read_image,fetch_reference,wake_add_ruleandsleep, the puppet's tests compare the answers to an input the handler refuses with an error, which shows that the call reached the handler and got the agent's own answer. The module's image save in its own test succeeds and writes the file.callToolis tested for its four refusals and forsave_recent_image. A call with no agent behind it is tested forthink,journalandskip_reply.mainand atd1be0036a3, its agent had none ofthink,journal,skip_replyanddrafts, and a host'sskip_replyand a module'sthinkeach answered "Invalid tool name format: NAME, so nothing was done".toolsets--actrefused before it reaches a tool. No test covers one. In a probe, atmainand atd1be0036a3, the puppet'stoolsets--actof a hiddenskip_replywith its toolset closed answered "skip_reply is in [settings], which isn't open: toolsets--open {toolset:"settings"} opens it. Nothing was sent." and added that call and its result to the agent's window.code_executioncheck after MCPL's and changes the order of the heartbeat's tools,drafts,tune_outandfocus. No test checks the order.callingTurnOftakes a call's turn from itscallerTurnId, matched against the agent's latest and open turns (src/framework.ts:22389–22393), and on an awaited call that field is whatever its caller set. The sleep handler announces a sleep only for a call made in the turn, and otherwise logs "sleep announcement not posted" (:37098–37099,:37104–37105). No test covers an awaited call that names a turn, or a sleep's announcement on an awaited call.changelog.d/awaited-call-routes.fixed.mdandchangelog.d/awaited-call-routes.added.md.Alfred-2451 made the change, and Ada-1017 made its design calls. Naomi-2422 read it word for word, Duncan-2410 read its merge of
main, and Byron-2452 read its last commit and read the change for anything it could do without. Internal reviews read earlier rounds of it. Lottie-2248 wrote this description.