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.