feat: tools and the gate know who the run is for #35
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/caller-context"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
This is the fourth step of the library track Shikigami's runtime needs: "Caller context" in its PROJECT_PLAN (Shikigami ADR 0021; ADR 0022 needs the gate to decide by the run's profile). It adds an opaque caller state that every tool call and the tool-call gate read, so a tool knows the familiar, chat and run it works for without being built around them. It is opt-in: with no state set, nothing changes for Orihon, Kagura or Tsumugi.
What's in
AgentOptions.CallerState(object?)ToolInvocationContext.CallerState) and to the gate (ToolTurnReview.CallerState). It never looks inside.Clone()copies the reference. ACreateSubAgentsub-agent shares the object unless its configure callback sets another. It is in the options registry (AgentOwnOptions, and the every-option fixture).Reading it back
GetCallerState<T>()on both records returns the state as its own type, or as a type it derives from or implements.InvalidOperationExceptionwhen there is no state ("set AgentOptions.CallerState to the Run its tools and gate read") or when the state is another type ("is a Run, not a String").SubAgentTool— this was open in the track's notes, and I chose this; easy to reverse if you'd rather.The critical-section contract
ITool.InvokeAsync's doc now says what the loop already did: a tool that returns after the run's token is cancelled (finishing a write it must not leave half done) has its answer recorded, the run ends as cancelled, and the calls after it are answered as not run.One behaviour fix the review found
MaxIterationsReached, andOnMaxRoundsReachedwas offered more rounds for a cancelled run.ChatAsyncthrows, or it returnsCancelledwithReturnResult.Docs
CreateSubAgent(o => o.CallerState = run, sp.GetServices<ITool>()), since a sub-agent starts with no tools.ToolInvocationContextparagraph andTool<TParams>.ExecuteAsync's param doc.Not in this step
AgentSnapshot(the cap hook's view) carries no caller state. No consumer needs it, and the hook closes over its own run.Verification
The whole solution passes 1660/1660, in local time and under
TZ=UTC: core 1415, Agents 238 (+14), Imaging 7. The build has 0 warnings.CallerStateTests(8 tests, one a theory of 4 rows):Assert.Same, counts pinned first);nulland reading one gives the exact message;CreateSubAgentshares the state unless configure replaces it;SubAgentToolacross all four combinations: calling run with or without a state, the tool's options with or without one. The base options are left untouched.Cancellation:
ReturnResultmode, the same case:Cancelled, the closing event says so, the hook isn't asked, and the round sits inRoundswith no cut-off round or partial.Mutation probes: 13 sites, 13 killed, 0 survived. The sites:
Clonedropping the state;SubAgentToolignoring the run, preferring its own options, or dropping the fallback;GetCallerStates cast instead of checking;They ran before the second pass. Its fixes were docs and one test, and add no production logic.
Review (read-only workflow; every count is from its saved JSON):
ToolInvocationContextparagraph and param doc; the Upgrading list; the test summary; the Highlights wrap.CreateSubAgentgives it one; its doc point is covered.Notes
🤖 Generated with Claude Code
Summary
Summary
Coverage
OpenRouter.Net - 98.1%
OpenRouter.Net.Agents - 91.7%
OpenRouter.Net.Imaging - 81.8%