docs(specs): add delivery package for direct profile invocation

-co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
ayattara
2026-09-14 03:26:03 +00:00
parent 0f36c0a6b0
commit f0b0ff91a4
5 changed files with 580 additions and 0 deletions
@@ -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.
@@ -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.
@@ -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.
@@ -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.
@@ -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.