| Field | Value |
|---|---|
| RFC | 0072 |
| Title | Agent Inventory + Dispatch Normative Surface |
| Status | Accepted |
| Author(s) | David Tufts (@davidscotttufts) |
| Created | 2026-05-26 |
| Updated | 2026-05-29 (Draft → Active → Accepted — the full §A wire surface had landed atomically on main (agent-inventory-response.schema.json, the gated GET /v1/agents{,/{agentId}} OpenAPI paths, peerDependenciesMeta in node-pack-manifest.schema.json, node-packs.md §C, the host-capabilities.md §A normative-inventory note, OpenwopClient.agents.{list,get}, and conformance/src/scenarios/agent-manifest-runtime.test.ts) but the Status was never flipped — a bookkeeping miss made visible by its amendment RFC 0074 already being Accepted (#290) over a Draft base. Active: wire shapes locked. Accepted: the Active → Accepted bar (a non-steward host exercised black-box over the normative inventory) is met — MyndHyve workflow-runtime advertises agents.manifestRuntime: {supported: true, handoffValidation: true, installScope: 'tenant'} live on api.myndhyve.ai and serves the tenant-scoped GET /v1/agents (steward-curl-verified via the RFC 0074 graduation #290: tenant A → 1 agent, tenant B → empty + cross-tenant 404). Steward reference host (apps/workflow-engine routes/agents.ts, AgentRegistry-backed) serves the normative inventory (37 installed manifest agents) and agent-manifest-runtime.test.ts passes non-vacuously under OPENWOP_REQUIRE_BEHAVIOR=true (verified 2026-05-29). The two Unresolved questions are non-blocking (Q1 a future MAY→SHOULD; Q2 decided in-RFC). Comment window waived per GOVERNANCE.md single-maintainer lazy consensus.) |
| Affects | schemas/agent-inventory-response.schema.json (new), schemas/node-pack-manifest.schema.json, api/openapi.yaml, spec/v1/node-packs.md, spec/v1/host-capabilities.md, conformance/src/scenarios/agent-manifest-runtime.test.ts, CHANGELOG.md, INTEROP-MATRIX.md |
| Compatibility | additive per COMPATIBILITY.md |
| Supersedes | — |
| Superseded by | — |
Summary
RFC 0070 made pack-declared manifest agents runnable behind capabilities.agents.manifestRuntime, but their inventory and dispatch were sample-extension only (/v1/host/sample/agents), so a non-steward host could not be exercised black-box and "portable agent" silently degraded to "portable manifest, host-specific runtime." This RFC resolves RFC 0070's three Unresolved questions: (§A) it promotes a read-only agent inventory (GET /v1/agents, GET /v1/agents/{agentId}) to a normative, capability-gated surface; (§B) it pins the normative dispatch path to the existing run surface (WorkflowNode.agent + POST /v1/runs) rather than a bespoke endpoint, so dispatch inherits replay/fork/observability; (§C) it replaces RFC 0070 §D's host-global degrade-vs-refuse choice with a pack-author-declared, per-dependency marker (peerDependenciesMeta.optional); and (§D) it records that the floor's safety guarantees stay mandatory (not sub-flags).
Motivation
"The portable agent protocol for any app" is only credible if any client can point at any conformant host and enumerate the agents it can run — and if that surface is the same everywhere, conformance can _prove_ it. RFC 0070 left that surface in the sample-extension namespace, which (1) no non-steward host exposes, so the conformance scenario soft-skips against them, and (2) means each host surfaces agents differently. That undercuts both universality and the Active → Accepted path (which needs a non-steward host exercised black-box). RFC 0070 also deferred two design calls — the degradation default and flag granularity — that this RFC settles on the universal-adoptability criterion (see docs/OPENWOP-AGENT-RUNTIME-ANALYSIS.md §F and the architect review).
Proposal
§A Normative agent inventory (resolves RFC 0070 §Unresolved #3)
A host that advertises capabilities.agents.manifestRuntime.supported: true MUST serve:
GET /v1/agents→{ "agents": AgentInventoryEntry[], "total": integer }GET /v1/agents/{agentId}→ anAgentInventoryEntry, or404with the canonical error envelope when no such agent is installed.
A host that does not advertise agents.manifestRuntime MUST NOT be required to serve these (they 404/501 and conformance soft-skips). AgentInventoryEntry (new agent-inventory-response.schema.json) is a read projection of the installed AgentManifest — it carries agentId, persona, label, modelClass, packName, packVersion, toolAllowlist, hasHandoffSchemas, and optional memoryShape / confidenceThreshold / degraded[] (the tiers inert on this host per §C). It MUST NOT carry the system-prompt body, resolved handoff schemas, or any credential material (SR-1).
This is the portable discovery surface; the sample-extension /v1/host/sample/agents remains a non-normative convenience alias.
§B Dispatch is a run (resolves RFC 0070 §Unresolved #3, dispatch half)
A manifest agent is dispatched as a run: a workflow node pins the agent via WorkflowNode.agent (the AgentRef from RFC 0002), and the run is created with POST /v1/runs. There is deliberately no bespoke :dispatch endpoint — reusing the run surface means agent dispatch inherits replay (replay.md), POST /v1/runs/{runId}:fork, idempotency (idempotency.md), cancellation, and openwop.* observability for free; a side-channel execution endpoint would need its own determinism story. Hosts MAY accept an optional agentId on the POST /v1/runs body as a convenience that the host expands into a single-agent run; this is an ergonomic shortcut, not a required surface, and MUST behave identically to the equivalent WorkflowNode.agent run.
Full black-box _dispatch_ conformance — driving POST /v1/runs against a pinned manifest agent and asserting the attributed agent.* events — requires the host's executor to run a manifest agent as a workflow node. That executor integration is the sequenced tier from RFC 0070 (crews/orchestrator); this RFC makes the inventory black-box-provable now and states the dispatch contract normatively.
§C Per-dependency graceful degradation (resolves RFC 0070 §Unresolved #1)
RFC 0070 §D posed a host-global default (degrade vs refuse). That is the wrong grain — only the pack author knows whether an agent is correct when a tier is unmet. This RFC moves the decision into the manifest, reusing the npm idiom node-packs.md already anticipates (§"Forward-compat note"):
"peerDependencies": { "type": "object", "additionalProperties": { "type": "string" } },
+ "peerDependenciesMeta": {
+ "type": "object",
+ "description": "Per-peer-dependency metadata. Keys mirror peerDependencies keys.",
+ "additionalProperties": {
+ "type": "object", "additionalProperties": false,
+ "properties": { "optional": { "type": "boolean", "default": false } }
+ }
+ }
- A bare
peerDependenciesentry is required: unmet ⇒ the host MUST refuse install withpack_peer_dependency_missing(today's semantics — least surprise, safe default). - An entry marked
peerDependenciesMeta[cap].optional: trueis degrade-if-unmet: the host MUST install the agent with that tier inert and MUST surface the degraded set (the inventory entry'sdegraded[]per §A, listing the unmet capability keys). It MUST NOT silently accept the manifest and never honor the tier (the RFC 0070 §D MUST-NOT carries forward). - Default (no
peerDependenciesMetaentry) is required — backward-compatible with every pack published before this RFC.
§D Floor safety guarantees are mandatory, not sub-flags (resolves RFC 0070 §Unresolved #2)
toolAllowlist enforcement (RFC 0002 §A14), systemPromptRef resolution (RFC 0003 §C), and BYOK/SR-1 redaction MUST be honored whenever a host advertises agents.manifestRuntime.supported: true — they define what the floor _means_ and are not independently advertised sub-flags. Sub-flagging a safety guarantee would let a host claim the floor while running a signed agent with an unbounded tool surface, fragmenting the floor's uniform meaning across hosts. Only genuine _behavioral variations_ are sub-flags; handoffValidation (RFC 0070 §A) is the canonical example (a host without a JSON-Schema validator can still serve the floor with opaque payloads). No schema change — this is a normative clarification.
§E Positive / negative examples
Positive — a host advertising the floor serves the inventory:
GET /v1/agents → { "agents": [ { "agentId": "core.openwop.agents.code-reviewer.default",
"persona": "Code Reviewer", "modelClass": "coding", "packName": "core.openwop.agents.code-reviewer",
"packVersion": "1.0.0", "toolAllowlist": ["openwop:fs.read"], "hasHandoffSchemas": true } ], "total": 1 }
Negative — an optional unmet tier with no surfaced degradation (violates §C):
// pack: peerDependencies { "agents.memoryBackends": "supported" }, peerDependenciesMeta { "agents.memoryBackends": { "optional": true } }
// host lacks long-term memory; installs the agent but the inventory entry omits `degraded` → NON-CONFORMANT
Compatibility
Additive.
agent-inventory-response.schema.jsonis new;peerDependenciesMetaanddegraded[]are new optional fields; theGET /v1/agentsendpoints are new and gated on the existingagents.manifestRuntimeflag; the optionalagentIdonPOST /v1/runsis a new optional request field.- No existing required field, optional-field type, event shape, endpoint contract,
MUST, or error code changes. Hosts that don't advertiseagents.manifestRuntimeare unaffected; packs published before this RFC validate unchanged (defaultrequiredmatches prior semantics). - Replay/fork safety holds by construction: dispatch reuses the run surface (§B), introducing no new event-log shape.
Conformance
- Retargeted: the inventory leg of
agent-manifest-runtime.test.tsdrives the normativeGET /v1/agents(+/{agentId}), gated onagents.manifestRuntime.supported; soft-skips when unadvertised. Suite@openwop/openwop-conformance1.9.0 → 1.10.0. peerDependenciesMeta: a loader/host scenario asserts required-unmet ⇒ refuse and optional-unmet ⇒ install-degraded-with-degraded[].- Dispatch black-box stays deferred to the executor-integration tier (RFC 0070 crews/orchestrator); the sample-seam dispatch assertion continues to cover the reference host meanwhile.
Alternatives considered
1. A bespoke POST /v1/agents/{id}:dispatch endpoint. Rejected: it fragments the "everything is a run" model and would need its own replay/fork/idempotency story; WorkflowNode.agent + POST /v1/runs already dispatch agents normatively. 2. A host-global degrade-vs-refuse default. Rejected: wrong grain — global degrade risks silent incorrectness, global refuse kills reach; only the pack author knows. §C moves it per-dependency. 3. Sub-flagging toolAllowlist/systemPromptRef/SR-1. Rejected: they're safety invariants, not features; advertising them optional fragments the floor's meaning (§D). 4. Do nothing (keep sample-extension only). Rejected: leaves the inventory unportable + the Active → Accepted path unprovable black-box.
Unresolved questions
1. Should the optional agentId on POST /v1/runs be promoted MAY → SHOULD once a second host implements the single-agent expansion? 2. degraded[] surfacing: on the per-install response, the inventory entry, or both? (This RFC puts it on the inventory entry.)
Implementation notes (non-normative)
- The reference
workflow-enginehost servesGET /v1/agents{,/{agentId}}from the AgentRegistry built in RFC 0070 (routes/agents.ts); the loader (agentLoader.ts) readspeerDependenciesMetaand sets the entry'sdegraded[]. Theexamples/hosts/in-memoryreference host gaining the same surface (+ the executor dispatch integration for full black-box dispatch conformance) is the sequenced follow-on. - CC: SDKs gain
agents.list/agents.get— note in the impl plan.
Acceptance criteria
- [x] Spec text merged (this file + §C in
node-packs.md+ §A normative-inventory note inhost-capabilities.md). - [x]
agent-inventory-response.schema.json+peerDependenciesMetainnode-pack-manifest.schema.json. - [x]
GET /v1/agents{,/{agentId}}inapi/openapi.yaml(gated) +OpenwopClient.agents.{list,get}. - [x] ≥1 capability-gated conformance scenario over the normative inventory (
agent-manifest-runtime.test.ts). - [x] CHANGELOG entry; reference host serves the inventory (
apps/workflow-engineroutes/agents.ts, AgentRegistry-backed — 37 agents, scenario passes non-vacuously underOPENWOP_REQUIRE_BEHAVIOR=true). - [x] Path to
Accepted: a non-steward host exercises the normative inventory black-box. Met — MyndHyveworkflow-runtimeadvertisesagents.manifestRuntime(installScope: 'tenant') live + serves the tenant-scopedGET /v1/agents(steward-verified via the RFC 0074 graduation #290).
References
- RFC 0070 (agent-manifest runtime —
Active), RFC 0002 (AgentRef/WorkflowNode.agent), RFC 0003 (agent packs /peerDependencies). docs/OPENWOP-AGENT-RUNTIME-ANALYSIS.md,spec/v1/node-packs.md,spec/v1/replay.md,spec/v1/idempotency.md.