Composer pickers — one server answer for every client

The problem

Blazor's composer resolves its pickers in-process: the agents through AiSettingsNodeType.ResolveAgentQueriesOnce (the viewer's, the space's and the platform's agents, every readable package's {package}/Agent, and the AI-settings sources), the models through AgentPickerProjection.BuildModelQueries (the platform catalogue, the context's and its node type's Provider, the viewer's _Memex), filtered by the instance's data residency and model ceiling.

The React portal and the React Native app reach the mesh through search only, so they restated part of those queries in clients/react/src/controls/composerSelection.ts. The restatement could not list a package's agents (the package roots are a read of their own) or an AI-settings source, and it skipped the node-type partition and the instance filters.

The surface

Two reserved virtual namespaces, answered by ComposerPickerQueryProvider (src/MeshWeaver.AI):

Query Answers
namespace:"Agent/_Picker/{context}" nodeType:Agent the agents Blazor's /agent picker lists for that context — AiSettingsNodeType.ResolveAgentQueriesWithPackages, one row per slug (CollapseAgentLayers), background generators dropped, ordered by Order then Name
namespace:"Provider/_Picker/{context}" nodeType:LanguageModel the models — AiSettingsNodeType.ResolveModelQueries over the context node's own type, LanguageModel rows only, admitted by AdmittedByResidency and AdmittedByCeiling

ComposerPickerQueries.Agents(context) / .Models(context) build the strings; the context may be omitted. Nothing is stored under either namespace, so every storage provider answers it with nothing — the same shape as core's Auth/_GrantSubjects/{scope} (GrantSubjectsQueryProvider).

Every read runs as the requesting viewer, pinned onto each inner request — the AI settings, the package roots, the context node and the registry rows — so row-level security decides what is listed, exactly as for Blazor's picker. A request the mesh could not stamp with a viewer reads as the explicit anonymous visitor, never as whatever identity is ambient when the inner reads subscribe. A System request is refused with an empty answer: it would list every private agent.

The caller's other terms (name:Tutor, a content filter, free text) are evaluated by the mesh over the same sources and select among the PROJECTED rows: the layers are collapsed first, so a search that matches only a losing layer (the viewer's install copy of a platform agent) cannot surface it. select:/sort:/ limit: do not reach the inner reads. A nodeType: the picker does not answer (nodeType:Agent under Provider/_Picker) gets nothing. A request whose union carries several picker queries answers every branch, each with its own context and terms, distinct by path. The model answer always reads the root provider headers as their OWN complete read (never part of the source union, whose paging could drop them), never displayed: the ceiling reads the administrator's switch-offs off them, so a models-only AI-settings source cannot re-offer a disabled model.

The helpers carry sort:order: the mesh's merge re-orders a provider's rows by the query's sort (typed, unset last, ties by path), so the projection uses the same order. The answer is live and sent as deltas (Removed/Added/Updated) against the previous list; an empty Reset would be dropped by the merge as a duplicate and leave a removed last row on screen.

Matches answers true for every scoped query: the aggregator hands it only the first segment of a path: anchor, and a bare namespace:Agent/_Picker/x parses to exactly that anchor (Agent). A query that is not a picker query costs one substring test and an empty Initial.

Tests

MeshWeaver.AI.Test/ComposerPickerQueryProviderTest, on a mesh with row-level security: the reader's own, the space's and a readable package's agents are listed, an unreadable package's and another user's home are not, generators are not; another viewer sees only what it reads; the model answer carries the platform catalogue and the viewer's own _Memex models and no provider rows; System is refused and an unstamped request reads as anonymous under an ambient System; name: narrows and a foreign nodeType: gets nothing; a search matching only a losing layer surfaces nothing; a union of picker queries answers every branch; the viewer's AI-settings source is honoured and another viewer's is not; the last row removed empties the answer through IMeshService.Query; the order through the mesh matches the projection.