One state, one query set — an agent list never hops between query chains

What was seen

On Plugins CI on 2026-10-13, ThreadAgentIntegrationTest.FullFlow_CreateThread_SendMessage_StreamResponse_SaveReply created its own agent, waited until the chat client's agent list contained it, and started a round. The round answered:

Selected agent 'ThreadAgentIntegrationTestAgent-wnJ3…' was not found among the available agents
([Agent/Assistant, Agent/Researcher, ACME/Agent/ThreadAgentIntegrationTestAgent-z-D4K…, …]).

The 14-agent list held the agent of the class's earlier fact but not the one just created. That list was a snapshot from before the create, delivered after the client had already listed the newer state.

How the list is built

AgentPickerProjection.ObserveAgents resolves the viewer's agent query set (AiSettingsNodeType.ObserveAgentQueries). That set depends on two inputs: the viewer's AI Settings (their agent sources) and the package roots the viewer can read (each package's /Agent namespace is read in place). Each distinct set is served by a cached, shared synced query — one chain per (id, query set), Replay(1) and connected for the life of the process — and the list Switches to the chain of the newest set.

Why it went backwards

The rule

One state gives one query set; nothing provisional is emitted ahead of the real answer.

A genuine change — the viewer edits their AI Settings — still mints a new set and a hop; that is a real state change, not a provisional one. The cost is that the first paint of a picker waits for the roots and settings reads (a few tens of milliseconds locally); both reads complete on their own, so nothing can hang on them.

The same applies to every list built on these two inputs: ObserveSkillQueries and ObserveModelQueries.

Tests

MeshWeaver.AI.Test/AgentListOneChainTest: