Repository navigation
GH-4825: construct the internal agent messages' routers directly for Native AOT - #4842
Merged
Merged
Conversation
…Native AOT PrepopulateRoutingCache walks every message type the handler graph discovered, and RoutingFor's cache-miss path closes MessageRouter<T> / EmptyMessageRouter<T> over each one reflectively. An application with a durable message store has the framework's own agent messages in that set -- and they are internal, so HandlerRegistryCodeFile.onlyPublic() drops them and the emitted [DynamicDependency] rooting block CANNOT name them: generated code cannot put a non-public type inside a typeof(). In a native image ILC has therefore trimmed exactly those instantiations, and a Balanced-mode durable app dies at startup. Direct construction in _frameworkRouterFactories is the only fix available, which is what GH-4287 already did for IAgentCommand itself. Reported and diagnosed by @chrisbbe in #4827, which is credited as co-author here. That PR overlapped #4828 almost entirely and conflicts with main in seven files after it merged, so this lands the one part main was still missing rather than asking for a wholesale rebase. #4840 and #4841 track the two remaining places where #4827's version is the better one. Held to COVERAGE of the real set rather than to a list, and that is not a formality: #4827 named eight types -- the ones its lane happened to reach -- and the coverage test immediately found SIX MORE with the identical defect (AssignAgent, AssignAgents, ReassignAgent, ReassignAgents, StopRemoteAgent, StopRemoteAgents), the Balanced-mode leader's own assignment messages built in NodeAgentController.EvaluateAssignments and AssignmentGrid. All fourteen are covered now, and adding a new agent message fails the test until its factory exists. The test carries its own vacuity guard: the scan must find something, and every type it finds must really be non-public, which is the reason none of them can be rooted from generated code. Gates: wolverine.slnx Release -f net9.0 0 errors / 0 warnings; CoreTests 3289 green. Co-Authored-By: chrisbbe <chrisbbe@users.noreply.github.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VDUrBeB4tTnKj4AExCS1nj
…-agent-message-routers
…merged Two lines from @chrisbbe's #4827 that main lost: - The header still said "This project carries NO hand-written roots, so the lane fails until the library itself is fixed." That was true when #4826 landed the lane red; the library was fixed in #4828 and the lane passes, so the sentence now reads as a standing excuse for a red lane. - It enumerated three startup failures. There were four. The fourth note also records what the lane does NOT cover, which is the more useful half: this lane runs Solo, where agent messaging is never used, so it cannot reproduce the failure the rest of this branch fixes. GH-4843 tracks that gap. Co-Authored-By: chrisbbe <chrisbbe@users.noreply.github.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VDUrBeB4tTnKj4AExCS1nj
My previous commit put a literal "--" in the csproj's comment block, which makes MSBuild refuse to load the project at all (MSB4025). Every project referencing it fails, which is to say the whole solution. I pushed it because the verification command was `dotnet build ... | grep -E "error| Build succeeded" && git commit`, and grep SUCCEEDS when it finds the word "error" -- so the && ran on a failed build. Check the build's own exit code, not whether a grep matched something. Gates, properly this time: the project loads, and wolverine.slnx Release -f net9.0 is 0 errors / 0 warnings. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VDUrBeB4tTnKj4AExCS1nj
jeremydmiller
added a commit
that referenced
this pull request
Oct 6, 2026
…he HTTP rooting order, run a Balanced native lane (#4847) GH-4840: SideEffectAotRoots roots the interfaces that DECLARE Execute/ExecuteAsync, which is the test findMethod itself applies, instead of the castability proxy. Two shapes where the answers differ are now tests. The IL2037 question is measured: the Fisher lane returns a member-less marker side-effect interface and publishes with zero ILC warnings, so no member-less filter is needed. That measurement found a real bug: HandlerChain.PublishedTypes() listed side-effect return types, so PrepopulateRoutingCache closed EmptyMessageRouter<T> over an application-declared side-effect interface at startup and the native image died. A side effect is never routed; PublishedTypes() now skips INotToBeRouted types. GH-4841: the recorded EndpointMetadataProviderTypes form is kept, and its ordering assumption is loud: under codegen write, HttpGraph.BuildFiles refuses by chain name while any endpoint is unbuilt. Static attach is unaffected. GH-4843: the Marten native lane runs DurabilityMode.Balanced and waits for leadership and the durability agent assignment. Measured limit: with the fourteen #4842 router factories stripped, the single node still boots on net9.0 and net10.0, so the lane covers the clustered path without reproducing that failure. Claude-Session: https://claude.ai/code/session_017DbTkhs3JxSMCWasTzPdRq Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This was referenced Oct 6, 2026
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.
The one part of @chrisbbe's #4827 that
mainwas still missing after #4828 merged, plus six more types their list did not reach. Credited as co-author on the commit.The bug
PrepopulateRoutingCachewalks every message type the handler graph discovered, andRoutingFor's cache-miss path closesMessageRouter<T>/EmptyMessageRouter<T>over each one reflectively.An application with a durable message store has the framework's own agent messages in that set — and they are
internal, soHandlerRegistryCodeFile.onlyPublic()drops them and the emitted[DynamicDependency]rooting block cannot name them: generated code cannot put a non-public type inside atypeof(). In a native image ILC has therefore trimmed exactly those instantiations, and a Balanced-mode durable app dies at startup.Direct construction in
_frameworkRouterFactoriesis the only fix available — which is what #4287 already did forIAgentCommanditself.Worth noting why the Marten AOT lane stays green without this: it runs
DurabilityMode.Solo, where agent messaging is not used. A clustered durable app is where this bites, and no lane in CI is one.Coverage, not a list — and that is not a formality
#4827 named eight types. I wrote the test as coverage of every internal concrete
IAgentCommand/IDeferredAgentWorkin the assembly instead, and it immediately failed on six more:Those are the Balanced-mode leader's own assignment messages, built in
NodeAgentController.EvaluateAssignmentsandAssignmentGrid.Agent— precisely what a clustered durable app routes. The eight in #4827 were the ones that application's lane happened to reach.All fourteen are covered now, and adding a new agent message fails the test until its factory exists. The test carries its own vacuity guards: the scan must find something, and every type it finds must really be non-public — which is the reason none of them can be rooted from generated code.
On #4827
That PR overlapped #4828 almost entirely, and I merged #4828 without seeing it — my mistake, explained on the PR. It now conflicts with
mainin seven files, so this lands the missing part rather than asking @chrisbbe for a wholesale rebase. The two remaining places where their version is better than what shipped are tracked as #4840 (the side-effect interface filter, and an IL2037 hazard the current emitter can still produce) and #4841 (theEndpointMetadataProviderTypesordering assumption, which I filed against my own merged choice).Gates
wolverine.slnxRelease-f net9.0: 0 errors, 0 warningsframework_router_factories_cover_every_agent_message🤖 Generated with Claude Code
https://claude.ai/code/session_01VDUrBeB4tTnKj4AExCS1nj