Repository navigation
.NET: MessageInjectingChatClient - support multi-instance hosts (pluggable message source, surface injected messages) #9531
Description
Problem
MessageInjectingChatClient can only deliver a message to a run that lives in the same process as the caller. Its pending queue is stored in AgentSession.StateBag and guarded by a per-process ConditionalWeakTable<AgentSession, SemaphoreSlim>, and the class is sealed, so a host cannot back the queue with a shared store.
This affects any host that runs more than one replica without session affinity. A run streams from one replica, but a user's mid-run message can arrive at another. EnqueueMessagesAsync on the receiving replica only reaches a deserialized copy of the session, so the live run never sees the message.
Separately, drained messages are added to the request but never yielded as response updates. A streaming client that owns the transcript therefore cannot tell that a message was injected, or where.
Use Case and Evidence
A chat UI talks to an agent through a streaming HTTP endpoint (AG-UI) behind a load balancer. The browser owns the transcript and sends it with each run. While a long tool call is running, the user types a correction. We want the agent to see it before its next model call, as MessageInjectingChatClient is designed to do, rather than after the run finishes.
With several replicas:
- The correction may land on a replica that is not running the stream, so it cannot reach the live session.
- Even when it does reach the run, the client is never told the message was consumed or where it was inserted. On the next run the client cannot replay the transcript in the order the model saw it.
The same process-local limitation is discussed for background agents in #8760.
Desired Outcome
- A host can deliver a message to a run that is in progress on another instance, so it is injected before that run's next model call.
- A streaming consumer can learn when an injected message was consumed and its position relative to the surrounding response updates, so a client that owns the transcript can record it in place.
Alternatives Considered
- A custom
DelegatingChatClientbelow function invocation that reads a shared queue (Redis/Valkey) before each call and adds the messages. This works, but duplicatesMessageInjectingChatClientand diverges from the framework's injection semantics, such as re-calling the model when messages are pending after a final response. - A relay to the owning replica (pub/sub keyed by run) that calls
EnqueueMessagesAsyncon the live session there. Every host must then build its own registry of live session objects per run, and the injected message is still not surfaced to the stream. - Sticky sessions. Fragile across deploys and scale-in, and not available in every hosting environment.
- Waiting until the run ends and sending the message as a new run. Works today, but the agent cannot change course mid-run.
Proposed Direction
- An optional pending-message source, set through options, that
MessageInjectingChatClientdrains before each inner call alongside its in-memory queue. A shared-store implementation could then feed a run from any instance. - An option to yield drained messages as response updates (for example a user-role update with the message id) before the inner client's updates for that call.
I'm happy to contribute the .NET implementation of both once the shape is agreed.
Scope and Non-Goals
No response
Language
.NET
Contribution Intent
I am willing to implement this
Acknowledgements
- I searched existing issues and did not find a duplicate.
- I will wait for explicit maintainer agreement before starting implementation of a non-trivial change.
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsNo status
Activity
[-][Feature]: .NET: MessageInjectingChatClient - support multi-instance hosts (pluggable message source, surface injected messages)[/-][+].NET: [Feature]: .NET: MessageInjectingChatClient - support multi-instance hosts (pluggable message source, surface injected messages)[/+][-].NET: [Feature]: .NET: MessageInjectingChatClient - support multi-instance hosts (pluggable message source, surface injected messages)[/-][+].NET: MessageInjectingChatClient - support multi-instance hosts (pluggable message source, surface injected messages)[/+]JeffinSiby commentedon Oct 19, 2026
Roger Barreto (@rogerbarreto) If you think this issue is worth pursing, this was what I had in mind:
IncludeInjectedMessagesInResponseyields injected messages ahead of the inner updates (non-streaming: prepended to the response).FunctionInvokingChatClientkeeps injected messages in its history without per-service-call persistence, which overlaps with .NET: fix: keep injected messages in function loop history #6059. Happy to restrict or document that.UseMessageInjection(options)orChatClientAgentOptions.MessageInjectionOptions.HarnessAgentwould need its own setting.Would love to hear your thoughts on it.