Installed harnesses in the Threads picker

A provider package being available on a portal is not the same as a person installing it. CLI packages such as Claude Code declare personalInstall: true; the Store copies their Harness/{id} node into the installer's {user}/Harness namespace. The installed node is the selection, while the provider module supplies the runtime IHarness with that id.

Access goes through a subscription and its tier, never through preInstalled. The harness packages are personalInstall: true with a paid tier and no preInstalled, so an unentitled viewer sees the plan (Subscribe) and only an entitled one sees Get. Until Plugins#4962 they were also preInstalled: true: a pre-installed package needs no entitlement and is selected onto every instance by the platform baseline, so every user of every instance was offered Get, and a deployment record could not keep the packages off. validate-repos.py (check_personal_installs) now refuses that combination, a harness that is not a personal install, and a personal install on the free tier; and the Store gives a pre-installed package one Open and never an install step. Which instance carries a harness package at all is the deployment's declared package set (PluginCatalog:InstallByDefault) or an acquisition, like any other gated package.

BuiltInHarnessProvider deliberately omits runtimes whose definition has RequiresInstall. Publishing every loaded CLI runtime in the global Harness namespace would offer it to people who had never installed it. The global catalog still supplies the native MeshWeaver harness.

Harnesses are options of the Threads app, not apps

The coding-assistant harnesses β€” Claude Code, GitHub Copilot, Codex, Cursor, Grok, OpenCode and Antigravity β€” are options of the Threads app (AI/AiThreads), never apps of their own. Each package declares hostedIn: AI/AiThreads (PluginContent.HostedIn), which means:

Which harnesses an instance runs is deployment configuration: the loaded modules and Features:Ai:Clis:*. On the fleet, that belongs in the Models app (/Hosting/Models, the AI domain's fleet app). A switch there is owed; today the flags are set in the record's portal configuration.

The marks

A harness that runs its own agent loop declares the same two marks a language model carries (AI/ModelDataResidency): where prompts are processed (Harness.DataResidency: πŸ‡ͺπŸ‡Ί EU only, 🌍 global, ❔ not established) and where the maker is based (Harness.ModelOrigin + Harness.ModelMaker). The runtime declares them (IHarness.Definition) and HarnessProvenance resolves them by harness id. That way a per-user copy installed before the marks existed shows them too. The native MeshWeaver harness carries none, because it processes wherever the picked model does and each model row shows its own. ❔ is never treated as EU.

Harness Processing Origin Why
Claude Code 🌍 Global πŸ‡ΊπŸ‡Έ Anthropic Anthropic's first-party API (api.anthropic.com) is a global endpoint
OpenAI Codex 🌍 Global πŸ‡ΊπŸ‡Έ OpenAI api.openai.com is a global endpoint
Grok Build 🌍 Global πŸ‡ΊπŸ‡Έ xAI api.x.ai is a global endpoint
Google Antigravity 🌍 Global πŸ‡ΊπŸ‡Έ Google the Gemini API (generativelanguage.googleapis.com) is a global endpoint
GitHub Copilot ❔ not established πŸ‡ΊπŸ‡Έ GitHub (Microsoft) routes to several makers' models; no residency evidence
Cursor ❔ not established πŸ‡ΊπŸ‡Έ Anysphere (Cursor) routes to several makers' models from its own service
OpenCode ❔ not established ❔ unknown runs whichever provider the user configures

The endpoint rule is DataResidency.FromEndpoint, the same one the model marks use. None of these harnesses is EU-only.

The brand icon

A harness's Icon is inline SVG node content (Claude Code's terracotta burst, Copilot's mark). It reaches the page only as an inert image (InlineSvgImage, unplated because a brand mark carries its own colours) and is sized by that component's Size β€” 18px in the picker rows (NodeIcon), 14px on the composer's status chip. The chip's icon is rendered from the razor template, never built in code as a MarkupString: string-built markup carries no CSS-isolation attribute, so the view's scoped sizing rule could not reach it and the image filled the whole chip (Plugins#4631). The native MeshWeaver harness ships a URL icon, so its chip shows the generic hexagon glyph.

One query contract

AgentPickerProjection.BuildHarnessQuery owns the exact namespace union:

namespace:{user}/Harness|{space}/Harness|Harness nodeType:Harness

The builder omits absent or reserved route partitions, deduplicates repeated namespaces and projects the content needed by registry consumers. Both ObserveDefaultComposer and the interactive ThreadChatView picker use it. The picker resolves the current navigation context before deriving the space partition and retains its circuit-user access context for the read. No fan-out across all readable partitions is needed or appropriate.

The /harness skill's global-only literal remains a platform fallback, not the query a context-aware Threads client should execute. The picker recognizes the harness composer field and resolves the canonical registry, just as it does for the agent and model fields.

Diagnose the boundary that failed

On 2026-09-22 an active personal Claude Code harness existed and the Store reported installed, but the picker showed only MeshWeaver. Installation had succeeded. OpenPicker grouped harnesses with custom commands and ran the skill's literal namespace:Harness query, while composer defaults already read the personal + space + global union. The personal copy could never match the interactive picker's query.

Check these separately before changing installation, permissions or runtime registration:

  1. Does the installed node exist at the user's actual {user}/Harness/{id} path and read Active?
  2. Does the picker query include that namespace under the same browser user?
  3. Does the selected full path resolve to an installed node and a registered runtime?
  4. Only then diagnose that runtime's connection and provider-specific login.

HarnessPickerQueriesTest pins the namespace union and its no-user and reserved-context cases. HarnessPickerQueryTest executes it against a mesh containing personal, space, global and uninstalled package entries. PickerHarnessQueriesTest pins the Threads routing from the skill request to that canonical builder. The separate HarnessInstallGateTest covers runtime execution's active-install requirement; passing it alone never proved picker discoverability.