From f0b0ff91a4c034f8bd6dfeda9f643904ff203e7b Mon Sep 17 00:00:00 2001 From: ayattara Date: Mon, 14 Sep 2026 03:26:03 +0000 Subject: [PATCH] docs(specs): add delivery package for direct profile invocation -co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../plugin-direct-profile-invocation/plan.md | 122 ++++++++++++++ .../task-01-direct-profile-invocation.md | 152 ++++++++++++++++++ .../task-02-notes-companion-delivery.md | 100 ++++++++++++ .../plugin-direct-profile-invocation.md | 99 ++++++++++++ .../plugin-direct-profile-invocation.md | 107 ++++++++++++ 5 files changed, 580 insertions(+) create mode 100644 docs/plans/plugin-direct-profile-invocation/plan.md create mode 100644 docs/plans/plugin-direct-profile-invocation/task-01-direct-profile-invocation.md create mode 100644 docs/plans/plugin-direct-profile-invocation/task-02-notes-companion-delivery.md create mode 100644 docs/specs/plugins/requirements/plugin-direct-profile-invocation.md create mode 100644 docs/specs/plugins/system-design/plugin-direct-profile-invocation.md diff --git a/docs/plans/plugin-direct-profile-invocation/plan.md b/docs/plans/plugin-direct-profile-invocation/plan.md new file mode 100644 index 0000000000..ca0f9185cd --- /dev/null +++ b/docs/plans/plugin-direct-profile-invocation/plan.md @@ -0,0 +1,122 @@ +--- +created: 2026-09-14 +status: complete +requirements: + - REQ-PLUGINS-DIRECT-PROFILE-INVOCATION-001 +system_design: + - ../../specs/plugins/system-design/plugin-direct-profile-invocation.md +legacy_specs: [] +--- + +# Implementation Plan: Plugin Direct Agent Profile Invocation + +## Overview + +Plugins that delegate a one-shot LLM step currently select execution identity +through the legacy `utility_agent` config field, which models the choice as a +custom utility prompt and hides the agent profile that actually executes. +This plan adds a direct `agent_profile` selection to the Host invocation +contract, wires the host to execute the selected profile via +`ExecuteProfilePrompt`, and keeps declaration-gated legacy behavior +untouched. The companion [kandev-plugin-notes PR 7](https://github.com/yattdev/kandev-plugin-notes/pull/7) +consumes the new `format: agent-profile` field at exact Notes head +`f8548f9d7a1eaabf19d27bb0b0af8f75d8eaeec4`. + +The smallest reproduction of the addressed defect is: + +1. Open Settings > Plugins > Notes. The Utility Agent field lists rows from + the utility/custom-prompt catalog, not the platform's agent profiles. +2. Selecting a row persists a utility-prompt ID that fails at invocation with + a generic error rather than executing the configured agent profile. + +## Scope + +### In scope + +- The `agent-profile` config renderer format, shared-profile-picker reuse, + and eligibility filtering in the web app. +- Direct profile routing, typed `FailedPrecondition` translation, and legacy + fallback bounding in the backend plugins host. +- Hydration merging in the plugin config form and store profile-list + hydration revision checks. +- Accessibility and display-label reload for the picker. +- The companion Notes manifest, webhook error translation, tests, docs, and + packaging evidence for the Notes half of the delivery. + +### Out of scope + +- Changing the public `Host.InvokeUtilityAgent` signature or `agent_invoke` + gate semantics. +- Changing utility-agent resolution for plugins that declare only + `format: utility-agent`. +- New database or config migration for the dual selector formats. + +## Technical approach + +### Settings rendering + +Extend `config-schema.ts` with the `agent-profile` format renderer and route +it through `utility-agent-profile-picker.tsx` in `agentRegistration` mode. +Eligibility requires enabled, non-deleted, global, non-CLI-passthrough, +inference-capable profiles; unavailable saved IDs render replaceably. +`plugin-config-form.tsx` merges hydration results so concurrent profile +snapshot and config updates do not overwrite each other, and +`use-settings-data.ts`/`hydrator.ts` guard store revisions against stale +profile snapshots with WebSocket-triggered retry. + +### Host execution + +`host_utility.go` gates on `agent_invoke` first, validates the declared +format, resolves the profile once at call start, and executes with +`ExecuteProfilePrompt(profileID, prompt)`. Missing/ineligible selections +return typed `FailedPrecondition` before runner start; non-not-found store +errors pass through unchanged in `services.go`. Legacy `utility_agent` +routing runs only when no direct selection exists and the active manifest +declares that selector, per +[ADR 0048](../../decisions/0048-plugin-host-utility-agent-invoke.md). + +### Notes companion + +The plugin uses the daemonized target server, refreshes +`Host.InvokeUtilityAgent` enhancement to send the selected profile ID, +translates `FailedPrecondition` to an actionable HTTP 412 setup message, +updates its manifest contract test, README, packaging, and test-tooling +pins, and carries final visual QA evidence for the picker, mobile settings, +and enhancement preview. + +## Tests + +- Backend focused Go tests for direct routing, eligibility/error classes, + capability gating, and legacy fallback preservation. +- Frontend unit tests for config-schema formats, picker eligibility and + required fields, hydration merge, store snapshot revision, and WS retry. +- Notes Go tests for the manifest contract, webhook error mapping, and + enhancement payload, plus `node --test ui/bundle.test.mjs` wiring smoke. + +## Work orders + +- [x] [Task 01: Direct profile invocation and settings rendering](task-01-direct-profile-invocation.md) +- [x] [Task 02: Notes companion manifest, errors, and evidence](task-02-notes-companion-delivery.md) + +## Verification results + +- Backend: focused `internal/plugins` and `internal/backendapp` Go tests + passed, including the direct-profile routing, eligibility, cancellation, + and store-failure cases; static checks passed. +- Frontend: config-schema and picker tests, plugin-config-form hydration + tests, settings hydration and WS agent-handler tests, web typecheck, and + lint all passed at CI for the delivery head. +- Notes: Go test suite, `node --test ui/bundle.test.mjs`, package smoke, doc + validation, and packaging metadata checks passed. +- Notes PR evidence and committed screenshots confirmed the picker, mobile + layout, and enhancement preview behavior. + +## Risks + +- Concurrent profile-list hydration and plugin-config hydration can clobber + state; merge and revision guards prevent lossy overwrites. +- Legacy stored `utility_agent` values must remain executable for trusted + pre-transition manifests, so fallback gating is explicitly bounded in ADR + 0048 and regression-tested. +- Config failures must not be misclassified as operational failures, or web + hooks will mislead operators with wrong HTTP status messages. diff --git a/docs/plans/plugin-direct-profile-invocation/task-01-direct-profile-invocation.md b/docs/plans/plugin-direct-profile-invocation/task-01-direct-profile-invocation.md new file mode 100644 index 0000000000..09fbbde2da --- /dev/null +++ b/docs/plans/plugin-direct-profile-invocation/task-01-direct-profile-invocation.md @@ -0,0 +1,152 @@ +--- +id: "01-direct-profile-invocation" +title: "Direct profile invocation and settings rendering" +status: done +wave: 1 +depends_on: [] +plan: "plan.md" +requirements: + - REQ-PLUGINS-DIRECT-PROFILE-INVOCATION-001 +acceptance_criteria: + - AC-PLUGINS-DIRECT-PROFILE-INVOCATION-001.1 + - AC-PLUGINS-DIRECT-PROFILE-INVOCATION-001.2 + - AC-PLUGINS-DIRECT-PROFILE-INVOCATION-001.3 + - AC-PLUGINS-DIRECT-PROFILE-INVOCATION-001.4 + - AC-PLUGINS-DIRECT-PROFILE-INVOCATION-001.5 + - AC-PLUGINS-DIRECT-PROFILE-INVOCATION-001.6 + - AC-PLUGINS-DIRECT-PROFILE-INVOCATION-001.7 +system_design: + - ../../specs/plugins/system-design/plugin-direct-profile-invocation.md +--- + +# Task 01: Direct Profile Invocation and Settings Rendering + +## Summary + +Add the direct `agent-profile` config format to Settings > Plugins, render +only eligible platform profiles through the shared picker, and route +`Host.InvokeUtilityAgent` to `ExecuteProfilePrompt` on the stored profile ID +with typed configuration errors and bounded legacy fallback. + +## In scope + +- Add the `agent-profile` renderer, picker-mode eligibility filtering, and + unavailable-selection replaceable rendering. +- Register the companion Notes settings picker via the daemonized target + server and the platform agent registry. +- Keep the plugin config form's hydration merge and the store profile-list + revision guard so profile/config updates do not overwrite each other. +- Route direct selections to `ExecuteProfilePrompt`; return typed + `FailedPrecondition` for missing/ineligible selections before any runner + starts. +- Adapt host-utility manager errors to gRPC codes with not-found/config + classes; pass non-not-found store failures through unchanged. +- Bound legacy `utility_agent` fallback to trusted declared-selector + configurations per ADR 0048. +- Cover the paths with backend and frontend focused tests, docs updates, and + PR validation evidence. + +## Out of scope + +- Notes-side manifest, webhook translation, packaging, and QA evidence (Task + 02). +- `Host.InvokeUtilityAgent` signature or gate semantic changes. +- Utility-agent behavior for manifests that declare only the legacy format. + +## Acceptance + +- Notes's Utility Agent settings field lists eligible profiles, not utility + or custom-prompt rows, on desktop and mobile widths. +- Selecting a profile persists its stable ID, marks the shared settings + contributor dirty, saves, and reloads showing the same display label. +- Disabled, deleted, workspace-scoped, and CLI-passthrough profiles cannot be + newly selected; previously saved unavailable selections render replaceably. +- `Host.InvokeUtilityAgent` with a declared, selected profile executes that + profile directly with no utility/custom-prompt lookup; missing or + ineligible selections return typed `FailedPrecondition`, and + non-`agent_invoke` plugins are denied before profile lookup. +- Direct selection takes precedence over a coexisting legacy value; + legacy-only behavior is unchanged. + +## Verification + +```bash +cd apps/backend +go test ./internal/plugins/... ./internal/backendapp/... +cd ../apps && pnpm --filter @kandev/web test -- --run \ + lib/plugins/config-schema.test.ts \ + components/settings/utility-agent-profile-picker.test.ts \ + components/settings/plugins/plugin-config-form.agent-profile.test.tsx \ + hooks/domains/settings/use-settings-data.test.tsx \ + lib/state/hydration/hydrator.test.ts \ + lib/state/slices/settings/types.test.ts \ + lib/ws/handlers/agents.test.ts +pnpm --filter @kandev/web typecheck +pnpm --filter @kandev/web lint +``` + +## Files likely touched + +- `apps/web/lib/plugins/config-schema.ts` +- `apps/web/components/settings/utility-agent-profile-picker.tsx` +- `apps/web/components/settings/plugins/plugin-config-form.tsx` +- `apps/web/hooks/domains/settings/use-settings-data.ts` +- `apps/web/lib/state/hydration/hydrator.ts` +- `apps/web/lib/state/slices/settings/types.ts` +- `apps/web/lib/types/backend.ts` +- `apps/web/lib/types/http-agents.ts` +- `apps/web/lib/ws/handlers/agents.ts` +- `apps/backend/internal/plugins/host_utility.go` +- `apps/backend/internal/plugins/host.go` +- `apps/backend/internal/plugins/service.go` +- `apps/backend/internal/backendapp/services.go` +- `apps/backend/internal/agent/settings/controller/profile_crud.go` +- `apps/backend/pkg/pluginsdk/host.go` +- `docs/decisions/0048-plugin-host-utility-agent-invoke.md` +- `docs/public/plugins-authoring.md` +- `docs/public/plugins-manifest.md` +- `docs/screenshots/pr-validation/2870/*` + +## Dependencies + +None. + +## Risks + +- Legacy fallback must remain available for stored configs that still expect + it; explicit bounds and tests prevent silent removal. +- Concurrent hydration of config and profile snapshots can cause lossy + overwrites without merge and revision guards. +- Eligibility criteria must match the agents system's rules, so the shared + registration provider is anchored to the same store used by the settings + API. + +## Parallelism + +`sequential` + +## Inputs + +- `docs/specs/plugins/requirements/plugin-direct-profile-invocation.md` +- `docs/specs/plugins/system-design/plugin-direct-profile-invocation.md` +- `docs/specs/agents/requirements/utility-agent-profiles.md` +- `docs/decisions/0048-plugin-host-utility-agent-invoke.md` +- `apps/web/lib/plugins/config-schema.ts` +- `apps/web/components/settings/utility-agent-profile-picker.tsx` +- `apps/backend/internal/plugins/host_utility.go` + +## Results + +- Added the `agent-profile` renderer and shared-picker `agentRegistration` + mode with utility-execution eligibility (enabled, global, non-CLI, + inference-capable) and replaceable unavailable-selection rendering. +- Registered the daemonized target server so Notes renders platform profiles + with correct labels, persisted the saved stable ID, and surfaced the + profile name in the enhancement preview. +- Preserved legacy `utility_agent` fallback for trusted declared-selector + configs, bounded per ADR 0048 and regression-tested. +- Wired hydration merges and store revision guards so profile snapshot and + plugin-config updates do not clobber each other; profile WS updates retry + when a render would go stale. +- Verification: focused backend `go test` and web vitest/lint/typecheck all + passing at the delivery head, with PR validation screenshots added. diff --git a/docs/plans/plugin-direct-profile-invocation/task-02-notes-companion-delivery.md b/docs/plans/plugin-direct-profile-invocation/task-02-notes-companion-delivery.md new file mode 100644 index 0000000000..7a2ba91f67 --- /dev/null +++ b/docs/plans/plugin-direct-profile-invocation/task-02-notes-companion-delivery.md @@ -0,0 +1,100 @@ +--- +id: "02-notes-companion-delivery" +title: "Notes companion manifest, errors, and evidence" +status: done +wave: 1 +depends_on: + - "01-direct-profile-invocation" +plan: "plan.md" +requirements: + - REQ-PLUGINS-DIRECT-PROFILE-INVOCATION-001 +acceptance_criteria: + - AC-PLUGINS-DIRECT-PROFILE-INVOCATION-001.1 + - AC-PLUGINS-DIRECT-PROFILE-INVOCATION-001.4 + - AC-PLUGINS-DIRECT-PROFILE-INVOCATION-001.5 + - AC-PLUGINS-DIRECT-PROFILE-INVOCATION-001.8 +system_design: + - ../../specs/plugins/system-design/plugin-direct-profile-invocation.md +--- + +# Task 02: Notes Companion Manifest, Errors, and Evidence + +## Summary + +Update the Notes plugin (yattdev/kandev-plugin-notes at +`f8548f9d7a1eaabf19d27bb0b0af8f75d8eaeec4`, PR 7) to declare the +agent-profile-backed setting, translate `FailedPrecondition` into an +actionable setup response, align packaging, and record final delivery +evidence. + +## In scope + +- Notes manifest declaration and contract-pinning tests for the + agent-profile field name and format. +- Refresh the enhancement/proofread path to send the selected stable profile + ID in the `Host.InvokeUtilityAgent` payload. +- Translate `FailedPrecondition` webhook responses to HTTP 412 with an + actionable profile-selection/setup message; keep operational failures as + 502-class execution errors. +- SDK target output update, README/usage-copy alignment to a single direct + profile selection, packaging metadata consistency, and test tooling pin + alignment (Kandev SDK v0.2.4). +- Final PR body, visual evidence, and Notes-side read-me screenshots. + +## Out of scope + +- Host-side routing, eligibility, translation, and UI rendering, covered by + Task 01. +- Legacy `utility_agent` semantics for other plugins. + +## Acceptance + +- Notes declares and pins the agent-profile settings field, its payload + carries the stable profile ID, and its UI shows the selected profile label. +- A missing or ineligible profile yields HTTP 412 with actionable copy, not + a generic 500/502. +- Notes packaging, version metadata, doc links, and screenshots are + consistent with the shipped manifest and SDK. + +## Verification + +```bash +cd kandev-plugin-notes +go test ./... +node --test ui/bundle.test.mjs +go vet ./... && test -z "$(gofmt -l .)" +``` + +## Files likely touched + +- kandev-plugin-notes `internal/server`, `manifest`, `webhook`, `go.mod`, + `go.sum`, README, docs links, packaging, UI bundle smoke tests. + +## Dependencies + +- `01-direct-profile-invocation` + +## Risks + +- Full-replacement plugin config updates can drop fields unless the plugin's + hydration writes the complete record. +- Committing PR screenshots requires binary assets on the Notes fork head. + +## Parallelism + +`sequential` + +## Inputs + +- `docs/specs/plugins/requirements/plugin-direct-profile-invocation.md` +- `docs/specs/plugins/system-design/plugin-direct-profile-invocation.md` +- kandev-plugin-notes Task 01 implementation and Kandev SDK contract. + +## Results + +- Notes PR 7 CI green (`Build plugin packages`, + `Tidy, format, vet, and test`, `GitGuardian`). +- Notes PR body corrected to reference the commands actually run in QA. +- Draft statuses cleared by marking Notes PR 7 (`f8548f9d7...`), Notes PR 7, + and Kandev PR 2870 (`0f36c0a6b...`) ready for review; both are CLEAN and + MERGEABLE at their exact delivery heads. diff --git a/docs/specs/plugins/requirements/plugin-direct-profile-invocation.md b/docs/specs/plugins/requirements/plugin-direct-profile-invocation.md new file mode 100644 index 0000000000..fd0d29e738 --- /dev/null +++ b/docs/specs/plugins/requirements/plugin-direct-profile-invocation.md @@ -0,0 +1,99 @@ +--- +status: active +system: plugins +created: 2026-09-14 +owners: + - kandev +--- +# Plugin Direct Agent Profile Invocation Requirements + +## Overview + +Plugins performing a one-shot LLM step previously chose execution identity by +selecting a utility agent, which forced them to route through a secondary +configurable object and hid the actual agent profile that would run. Users +need to select the platform agent profile directly from a plugin's settings so +the profile that executes is the profile they configured, while existing +plugins that still declare the legacy `utility_agent` selector keep working. +The plugin system owns the Host invocation contract and its configuration +state; the agents system continues to own profile eligibility and utility +execution resolution. + +## Terminology + +- **Direct profile selection:** a plugin `config_schema` field with + `type: string` and `format: agent-profile` whose stored value is a stable + agent-profile ID executed directly by the host. +- **Legacy utility-agent selection:** an existing `config_schema` field with + `format: utility-agent` whose stored value resolves through the utility + agent catalog, unchanged for plugins that still declare it. +- **Eligibility:** enabled, non-deleted, global, non-CLI-passthrough profiles + whose agent supports sessionless inference; the backend remains + authoritative. + +## Requirements + +### REQ-PLUGINS-DIRECT-PROFILE-INVOCATION-001: Direct agent profile invocation for plugins + +**Intent:** Let a plugin invoke one executable agent profile of the +operator's choosing without inventing a custom utility prompt, while keeping +the public Host RPC, SDK, and capability gate source-compatible and +preserving installed legacy configurations. + +**User story:** As a Kandev operator configuring a plugin such as Notes, I +want to pick the agent profile that performs the plugin's LLM step directly +in the plugin's settings, so that the profile I configured is the profile +that executes and I do not have to maintain a separate custom utility +prompt. + +#### Acceptance criteria + +- **AC-PLUGINS-DIRECT-PROFILE-INVOCATION-001.1:** When a plugin manifest + declares a `config_schema` field with `format: agent-profile`, Settings > + Plugins shall list only eligible platform agent profiles and shall not list + utility or custom-prompt rows, so built-in and custom utility prompts with + the same or different IDs cannot be confused with executable profiles. +- **AC-PLUGINS-DIRECT-PROFILE-INVOCATION-001.2:** When the user selects a + profile, the plugin configuration shall persist that profile's stable ID + through the shared settings save path, the contributor shall become dirty + and save successfully, and a reload shall again show the selected profile's + display label. +- **AC-PLUGINS-DIRECT-PROFILE-INVOCATION-001.3:** When a profile is disabled, + deleted, workspace-scoped, or CLI-passthrough, the picker shall exclude it + from new selections and the settings surface shall represent a previously + saved unavailable selection safely and replaceably rather than failing + render. +- **AC-PLUGINS-DIRECT-PROFILE-INVOCATION-001.4:** When a plugin with + `capabilities.agent_invoke: true` invokes `Host.InvokeUtilityAgent` with a + non-empty direct `agent_profile` configuration, the host shall route the + prompt to `ExecuteProfilePrompt` using that exact profile ID and shall not + resolve or execute a utility or custom-prompt definition. +- **AC-PLUGINS-DIRECT-PROFILE-INVOCATION-001.5:** When the direct profile + selection is missing, or the selected profile is missing or ineligible at + execution time, the host shall return a typed gRPC `FailedPrecondition` + that plugins can translate into an actionable configuration message rather + than a generic execution failure. +- **AC-PLUGINS-DIRECT-PROFILE-INVOCATION-001.6:** When a plugin's + configuration holds both a direct profile and a legacy `utility_agent` + value, the direct profile shall take precedence; when only the legacy value + exists and the active manifest declares that selector, the existing + utility-agent resolution and execution behavior shall remain unchanged. +- **AC-PLUGINS-DIRECT-PROFILE-INVOCATION-001.7:** A plugin that does not + declare `capabilities.agent_invoke` shall be denied before any profile or + configuration lookup, and operational runner failures that occur after + successful configuration validation shall remain execution errors instead + of being misreported as configuration failures. +- **AC-PLUGINS-DIRECT-PROFILE-INVOCATION-001.8:** The plugin manifest and its + contract tests shall declare the agent-profile-backed setting with a + pinned field name and format, and the plugin's package metadata shall stay + internally consistent. + +## Out of scope + +- Changing the public `Host.InvokeUtilityAgent(ctx, prompt)` RPC or SDK + signature, or the `agent_invoke` capability-gate semantics. +- Adding new config migration; a database or schema migration is not + required for the dual-format selection. +- Changing utility-agent resolution for plugins that continue to declare + only the legacy `utility_agent` selector. +- Changing agent-profile eligibility rules owned by the agents system. diff --git a/docs/specs/plugins/system-design/plugin-direct-profile-invocation.md b/docs/specs/plugins/system-design/plugin-direct-profile-invocation.md new file mode 100644 index 0000000000..daac53649a --- /dev/null +++ b/docs/specs/plugins/system-design/plugin-direct-profile-invocation.md @@ -0,0 +1,107 @@ +--- +status: current +system: plugins +requirements: + - REQ-PLUGINS-DIRECT-PROFILE-INVOCATION-001 +created: 2026-09-14 +owners: + - kandev +--- +# Plugin Direct Agent Profile Invocation System Design + +## Purpose and boundaries + +The plugin system owns the `Host.InvokeUtilityAgent` contract and the +plugin-configuration state that selects its execution identity. This design +adds a direct agent-profile selection path beside the legacy utility-agent +selection, without changing the public RPC, SDK, or capability gate. The +agents system continues to own profile eligibility and the utility execution +resolution described by +[Profile-backed Utility Agents](../../agents/system-design/utility-agent-profiles.md). +The UI system's shared settings-save coordinator and hydration contracts +remain independent dependencies, as does +[ADR 0048](../../../decisions/0048-plugin-host-utility-agent-invoke.md) for +the invocation precedence rule. + +## Requirement mapping + +| Requirement | Design sections | +| --- | --- | +| `REQ-PLUGINS-DIRECT-PROFILE-INVOCATION-001` | [Configuration selection](#configuration-selection), [Host execution routing](#host-execution-routing), [Eligibility and error translation](#eligibility-and-error-translation), [Legacy compatibility](#legacy-compatibility) | + +## Configuration selection + +`apps/web/lib/plugins/config-schema.ts` adds the `agent-profile` renderer +format beside the existing `utility-agent` format. Settings > Plugins renders +the field through the shared utility-agent profile picker component with +`agentRegistration` data, which exposes each platform profile's name, label, +workspace scope, CLI passthrough, and `inference_capable` flag. + +Eligibility filtering is `utility-execution` scoped: a profile is selectable +only when it is enabled, non-deleted, global (not workspace- or +Office-scoped), not CLI passthrough, and its agent declares +`inference_capable === true`. The picker does not fall back to matching +platform profiles against utility or custom-prompt catalog rows: rows from +`/api/v1/utility/agents` are never shown in the `agent-profile` field. +Unknown or ineligible saved IDs render through the picker's unavailable +option so the stored value stays visible-and-replaceable. + +File-field hydration (`plugin-config-form.tsx`) merges its backend profile +response with the current store snapshot instead of replacing the record, so +plugin config hydration and independent profile-list updates do not clobber +each other. Store-side profile-list hydration in +`use-settings-data.ts`/`hydrator.ts` compares an incoming profile snapshot +against what is already loaded and does not overwrite a higher store +revision; a WebSocket profile update triggers a retry when the list render +would otherwise go stale. + +## Host execution routing + +`internal/plugins/host_utility.go` resolves the invocation identity at the +start of each call, before any execution: + +1. The `agent_invoke` capability gate runs first; a plugin without it is + denied before configuration or profile lookup. +2. If the manifest's `config_schema` declares a field with + `format: agent-profile` and the stored value is non-empty, that value is + the direct profile ID. The host validates the declared format first, then + resolves the profile through the agent registry: missing or ineligible + profiles return typed `FailedPrecondition` before any runner starts, so + configuration failure and operational failure remain distinct classes. +3. With a resolved eligible profile, the host calls + `ExecuteProfilePrompt(profileID, prompt)` directly on the host-utility + tier. No utility or custom-prompt definition is looked up or executed in + this path. +4. Legacy `utility_agent` routing is consulted only when no direct profile is + selected, the active manifest declares the `utility-agent` format, and the + stored legacy value passes the existing trusted-resolution checks. Its + behavior is otherwise unchanged. + +`internal/backendapp/services.go` adapts the host-utility manager's typed +revalidation errors to gRPC codes: not-found/missing selection and +ineligibility surface as `FailedPrecondition`; store failures other than +not-found are returned unchanged so they remain server-side errors rather +than misleading the plugin's configuration messaging. + +## Eligibility and error translation + +The Notes webhook and other plugin callers translate `FailedPrecondition` +into an actionable "select an agent profile" setup message (HTTP 412) and +leave other codes (for example `Internal`) as execution failures (HTTP 502). +Service wiring in `services.go` builds the agent-profile provider from the +same registration store used by the settings API, so the picker and the +execution path judge eligibility with one source of truth. Renaming or +reflagging a profile revalidates at the next invocation; an in-flight call +keeps the configuration resolved when it started. + +## Legacy compatibility + +Manifests that keep only `format: utility-agent` continue to render the +utility-agent picker and persist utility prompt IDs; their stored values +resolve and execute exactly as before this change. A plugin updating its +manifest from `utility-agent` to `agent-profile` keeps its legacy value +until the operator saves a direct selection, and +`docs/decisions/0048-plugin-host-utility-agent-invoke.md` documents the +verified-transition rule that bounds the legacy fallback to trusted +pre-transition configurations. No config migration runs; both selector +formats coexist in stored plugin configuration.