Status: Draft v1.x (filed via RFC 0027, 2026-05-19; first cut 2026-05-20). Lands the wire shape for portable, versioned, variable-bound prompts referenced by workflow nodes and agent manifests. Closes the gap where
core.ai.callPromptconfig (workflow-chain-packs.mdline 69,host-capabilities.mdline 347) andAgentManifest.systemPrompt | systemPromptRef(agent-manifest.schema.jsonlines 34–41) accept inline prompt bodies but offer no shared addressing, library distribution, variable schema, or observability of the composed result. Keywords MUST, SHOULD, MAY follow RFC 2119. Seeauth.mdfor the status legend. Fields marked (stable) lock; fields marked (in-flight) may shift compatibly within v1.x.
Why this exists
Two v1 surfaces already accept prompt bodies, but neither establishes prompt-as-a-resource:
workflow-chain-packs.mdline 71 demonstratescore.ai.callPromptconfig carrying"systemPrompt": "You are a senior PM. Write a PRD for: {{params.productIdea}}\nAudience: {{params.targetAudience}}". The{{params.X}}substitution is convention-only — no spec'd variable schema, no enumeration of allowed sources, no observability of the composed result.schemas/agent-manifest.schema.jsonlines 34–41 + 104–105 locksystemPrompt XOR systemPromptRef. Both are tarball-resident; neither can be referenced across packs or replaced at run time.
Authors who want canvas-editor patterns of (a) maintain a named, versioned prompt library, (b) reuse the same prompt across multiple nodes or workflows, (c) preview the composed body before dispatch, or (d) audit what every agent actually saw in a multi-agent run have no protocol-level surface for any of these. Host-internal libraries exist (the reference myndhyve impl persists PromptEntry/PromptLibrary documents in Firestore at workspaces/{workspaceId}/prompt-libraries/{canvasTypeId} with built-in/override/version semantics), but the values they hold cross the wire as opaque interpolated strings — losing the ID, the version, the variable schema, and the resolution chain.
This document closes the wire-shape gap. Phase A only — the shape, the capability flag, and the observability event. RFC 0028 lands the registry surface (/v1/prompts/* endpoints + kind: "prompt" pack distribution); RFC 0029 lands the agent-scoped resolution hierarchy + agent.promptResolved event.
What a Prompt Template is (and is not)
A PromptTemplate is openwop's canonical wire format for a named, versioned, variable-bound prompt body. A PromptTemplate is a single JSON document whose top-level shape is fixed by schemas/prompt-template.schema.json, whose kind is selected from the shared prompt-kind.schema.json enum, whose text field MAY contain Mustache-compatible {{varName}} placeholders, and whose variables[] array typed-declares each placeholder.
A PromptTemplate is distinct from AIEnvelope (ai-envelope.md) and distinct from RunEventDoc (run-event.schema.json):
| Concern | PromptTemplate | AIEnvelope | RunEventDoc |
|---|---|---|---|
| Direction | Authored — host library, pack-distributed | Inbound — LLM → engine | Outbound — host → client |
| Source of truth | Host library (built-in + installed packs + user) | Single emission, recorded as RunEventDoc | Append-only run event log |
| Type discriminator | kind ∈ {system, user, few-shot, schema-hint} | Open-ended kind catalog, host-advertised | Fixed 51-variant enum, FINAL v1 |
| Lifecycle | Authored once; resolved at every node execution | Validated → gated → routed → recorded | Immutable after appendAtomic |
| Audience | Workflow editors, node dispatcher | Engine, node dispatcher | Clients, observability, replay |
In short: PromptTemplates are what the host sends to the LLM. AIEnvelopes are what the LLM sends back. RunEventDocs are what the engine reports to clients.
When a host composes a PromptTemplate for an LLM call (per the resolution chain in RFC 0029), it MAY emit a prompt.composed RunEventDoc capturing the composed bodies, refs, variable bindings, and content-trust marker — see §"Composition + observability" below.
PromptKind
schemas/prompt-kind.schema.json (NEW) holds the shared kind enum referenced by every schema that names a prompt kind. The enum has four values:
| Kind | Composed as | Notes |
|---|---|---|
system | LLM system message | Carries behavior + output discipline. Composed once per call. |
user | LLM user message | The variable-substitution surface — per-call task content. |
few-shot | User-message prefix (or alternating user/assistant turns per host policy) | Example bodies. Hosts MAY support multiple few-shot templates per node via additionalPromptRefs. |
schema-hint | Injected into system or user message at compose time (per host policy) | Structured-output schema description (e.g., the JSON Schema the LLM is asked to populate). |
A node MAY reference one template of each kind via the WorkflowNode.config.{systemPromptRef, userPromptRef, fewShotPromptRefs, schemaHintPromptRef} convention — defined normatively in §"Resolution chain" Layer 1 below and in the config description of schemas/workflow-definition.schema.json.
Adding a new kind in a future RFC is a single edit to prompt-kind.schema.json; consumers automatically pick it up. Hosts MAY narrow the accepted kinds via capabilities.prompts.templateKinds[].
PromptTemplate
The canonical wire shape — see schemas/prompt-template.schema.json for the full schema. Required fields:
interface PromptTemplate {
templateId: string; // ^[a-z0-9][a-z0-9._-]{0,127}$
version: string; // SemVer 2.0.0
kind: PromptKind; // shared enum, see above
text: string; // <= 65536 bytes
}
Optional fields: name, description, variables, modelHints, tags, meta.
Variable interpolation
The text body uses Mustache-compatible {{varName}} placeholders. No control-flow logic — substitution is purely literal. Hosts MUST:
- Resolve each
{{varName}}against the bound value (fromvariables[].source). - Fail the node with
prompt_variable_unresolvedwhen a required variable has no binding. - Render unresolved optional variables as the empty string (the
onUnresolved: 'empty'semantics from the myndhyve reference impl).
A {{varName}} placeholder that has no matching entry in variables[] MAY appear (treated as an optional variable with source input and no default). Hosts SHOULD warn at install time when a placeholder lacks a corresponding declaration.
PromptVariable
interface PromptVariable {
name: string; // ^[a-zA-Z_][a-zA-Z0-9_]{0,63}$
type: 'string' | 'number' | 'boolean' | 'array' | 'object';
required: boolean;
source?: 'input' | 'variable' | 'secret' | 'context'; // default: 'input'
extractPath?: string; // JSONPath into the source
defaultValue?: unknown; // used when required: false and source resolves to undefined
description?: string;
}
The source value determines where the binding comes from:
input(default) — the node's input port whose name matchesname.variable— a run-scoped variable resolved viactx.variables.get(name)(orextractPath).secret— a BYOK secret reference. MUST be redacted to[REDACTED:<secretId>]markers in any observability output (prompt.composedevents, debug bundles) per SECURITY/threat-model-secret-leakage.md §SR-1.context— a host-provided implicit context value (canonical names recommended below; hosts MAY define vendor-specific keys).
Recommended context variable names (non-normative)
To improve cross-host portability of templates that use the context source, hosts SHOULD recognize these canonical names where the underlying value exists:
currentUserId— the authenticated user dispatching the run.runId— the current run identifier.workflowId— the workflow being executed.workflowName— the human-readable workflow name.tenantId— the tenant scope (when the host supports multi-tenancy).nodeId— the executing node's id.now— ISO 8601 UTC timestamp at composition time.
Hosts MAY surface additional context keys; templates that depend on host-specific keys lose cross-host portability.
PromptRef
A PromptRef is the reference type that workflow-node config and agent-manifest fields carry to point at a PromptTemplate. Two equivalent forms (see schemas/prompt-ref.schema.json):
Stringy form — canonical for inline use in WorkflowNode.config:
prompt:writer-system@1.0.0
prompt:vendor.acme.writer.v2
prompt:critic-system
Pattern: ^prompt:<templateId>(@<version>)?$. When version is omitted, the host resolves the latest version available.
Object form — canonical when libraryId disambiguation, per-reference variable overrides, or version pinning need to be explicit:
{
"libraryId": "vendor.acme.editorial-prompts",
"templateId": "writer-system",
"version": "1.0.0",
"variableOverrides": { "tone": "formal" }
}
variableOverrides apply at composition time and take precedence over node-input bindings (matching the resolution chain in RFC 0029 §A).
Conflict resolution
When two installed packs ship the same templateId, the stringy form is rejected with prompt_ref_ambiguous and consumers MUST use the object form with explicit libraryId to disambiguate (per RFC 0028 §B).
Capability advertisement
A host advertises its prompt-resolution support via capabilities.prompts (per capabilities.schema.json). The block carries two independent axes — node-execution PromptRef resolution (Phase A) and the /v1/prompts* REST surface (Phase B) — each gated by its own flag so a host can implement either without the other.
{
"prompts": {
"supported": true,
"endpointsSupported": false,
"templateKinds": ["system", "user", "schema-hint"],
"variableSources": ["input", "variable", "context"],
"maxTemplateBytes": 16384,
"observability": "full"
}
}
Field semantics:
| Field | Required | Semantics |
|---|---|---|
supported | yes | RFC 0027 Phase A gate. When true, the host resolves PromptRef values on WorkflowNode.config.{systemPromptRef, userPromptRef, additionalPromptRefs} at node-execution time and emits prompt.composed events. When false or absent, those keys are treated as opaque strings and never composed. **Does NOT imply the /v1/prompts* REST surface is available** — see endpointsSupported. |
endpointsSupported | no | RFC 0028 Phase B gate. When true, the host serves the /v1/prompts REST surface (at minimum the read endpoints). When false or absent, every /v1/prompts request returns 501 capability_not_provided. Independent of supported. |
templateKinds | no | Subset of PromptKind values the host accepts. Default: all four. |
variableSources | no | Subset of PromptVariable.source values supported. secret SHOULD only appear when capabilities.secrets.supported: true. |
maxTemplateBytes | no | Host cap on text length. MUST NOT exceed the schema cap (65536). |
observability | no | off / hashed / full — controls prompt.composed emission per §"Composition + observability" below. Default: hashed. |
Phase B (RFC 0028) extends this block with packsSupported (pack-install path; requires endpointsSupported: true to be meaningful), mutableLibrary (write endpoints; requires endpointsSupported: true), and library (per-library knobs). Phase C (RFC 0029) extends it with defaults and agentBindings. This document covers Phase A; the Phase B fields are documented in §"Discovery & distribution" below.
Composition + observability
When a node executes and the host has resolved a PromptRef (per the resolution chain in RFC 0029), the host composes the prompt body by:
1. Substituting {{varName}} placeholders against the resolved variable bindings. 2. Wrapping any input flagged meta.contentTrust: "untrusted" (per mcp-integration.md §"Trust boundary" + RFC 0020 §D) in <UNTRUSTED>...</UNTRUSTED> markers — the markers MUST be preserved verbatim into the composed body per SECURITY/threat-model-prompt-injection.md. 3. Replacing any secret-sourced variable values with [REDACTED:<secretId>] markers in any observability projection (the markers do NOT appear in the actual dispatched body — the host resolves real values via capabilities.secrets after redaction-projection).
When the host advertises capabilities.prompts.observability !== "off", the host MUST emit a prompt.composed RunEventDoc per node composition:
interface PromptComposedPayload {
nodeId: string;
refs: string[]; // ["prompt:writer-system@1.0.0", "prompt:writer-user@1.0.0"]
kind: 'system+user' | 'system-only' | 'user-only' | 'agent-reasoning';
hash: string; // sha256:... of composed body
// observability: 'full' only
systemPrompt?: string;
userPrompt?: string;
variableBindings?: Record<string, unknown>;
// observability: 'hashed' or 'full'
variableHashes?: Record<string, string>; // name → sha256:...
contentTrust?: 'trusted' | 'untrusted';
}
Under observability: "hashed" (default), only hash + variableHashes are populated — the bodies stay out of the event log. Under observability: "full", the composed bodies appear with secret redaction and trust-marker preservation.
Replay determinism
prompt.composed events participate in replay. Invariants:
hashMUST replay identically.variableHashes[name]MUST replay identically.refsMUST replay identically.systemPrompt/userPrompt/variableBindingsMAY be omitted on replay even when present in the original run (the host'sobservabilitysetting may differ between original and replay); replay consumers MUST tolerate omission.
Divergence of hash MUST emit a replay.diverged event with divergencePoint: "prompt.composed" per replay.md.
Three-surface taxonomy (non-normative)
OpenWOP distinguishes three orthogonal observability surfaces for LLM-call inspection. Multi-agent debugging tools render all three to reconstruct what an agent saw + thought + emitted:
| Surface | What it captures | Source |
|---|---|---|
prompt.composed.systemPrompt / userPrompt | The body the host sent to the LLM, post-substitution + post-redaction | This document |
AIEnvelope.payload.reasoning | Chain-of-thought the LLM emitted as part of structured output | RFC 0030 (parallel track, Draft) |
agent.reasoning.delta + agent.reasoned | The LLM's interleaved thinking-tokens stream | RFC 0024 (Accepted) |
None of these replaces the others. A complete multi-agent visualization renders all three streams in temporal order so an operator can see what the host instructed, what the model thought through, and what the model returned as structured output.
Security invariants
The prompt.composed event MUST carry two SECURITY invariants once a host actually emits the event (per the staging precedent in RFC 0021):
prompt-composed-secret-redaction— Any variable whosesourceissecretMUST appear as[REDACTED:<secretId>]insystemPrompt,userPrompt, andvariableBindings. Never as plaintext.prompt-composed-trust-marker— When ANY contributing input was taggedmeta.contentTrust: "untrusted"(per RFC 0020 §D), the composed bodies MUST wrap the untrusted segments in<UNTRUSTED>...</UNTRUSTED>markers AND the payload'scontentTrustMUST be"untrusted".
Both invariants live in SECURITY/invariants.yaml once a reference host emits the event. Until then, the rows are RFC-tracked but not gate-enforced — matching the RFC 0021 envelope-shape staging precedent (the invariants land alongside the reference impl that emits the event, not at Draft merge).
The RFC 0028 Tier-2 endpoint surface carries two additional invariants covering the workspace-scoped authz contract:
prompt-mutation-workspace-membership-enforced— Hosts advertisingcapabilities.prompts.mutableLibrary: trueMUST verify the authenticated principal's workspace membership before honoring anyPOST/PUT/DELETEto a workspace-scoped/v1/prompts*resource.workspaceIdcarried by the caller MUST NOT be trusted as authorization on its own. The canonical refusal envelope on 403 iserror: "workspace_membership_required".prompt-read-workspace-membership-enforced— Hosts advertisingcapabilities.prompts.supported: trueMUST verify the authenticated principal's workspace membership before returning any workspace-scoped content viaGET /v1/prompts?workspaceId=,GET /v1/prompts/{templateId}(when the host derives a workspace from the templateId), orPOST /v1/prompts:render(whenworkspaceIdis in the request body). A 200 response containing another workspace's templates is a cross-tenant data leak — the read-side invariant is symmetric to the write-side. Same canonical refusal envelope.
See §"Discovery & distribution" §"REST endpoints" §"Workspace membership on workspace-scoped reads and writes" below for the full normative text, including the application-tier-check-MUST-replicate-the-vendor's-RLS-rules guidance.
Discovery & distribution (RFC 0028)
Phase B of the prompt-library track adds two complementary surfaces:
REST endpoints
Six operations under /v1/prompts*, all gated on capabilities.prompts.endpointsSupported: true (NOT supported; see §"Capability advertisement" above for the two-axis split — supported gates node-execution PromptRef resolution, endpointsSupported gates this REST surface). The mutating three (POST / PUT / DELETE) are additionally gated on capabilities.prompts.mutableLibrary: true. Hosts that don't advertise the relevant capability return 501 capability_not_provided.
| Method | Path | OperationId | Purpose |
|---|---|---|---|
GET | /v1/prompts | listPromptTemplates | Paginated list with ?kind, ?tag, ?modelClass, ?source filters + opaque cursor + limit. |
POST | /v1/prompts | createPromptTemplate | Create a user-source template (mutable libraries only). Returns 201 with a Location header. |
GET | /v1/prompts/{templateId} | getPromptTemplate | Fetch a single template, optionally pinned via ?version. ETag + If-None-Match revalidation. ?libraryId disambiguates when packs collide. |
PUT | /v1/prompts/{templateId} | updatePromptTemplate | Replace a user-source template; submitted SemVer MUST be strictly greater than stored. |
DELETE | /v1/prompts/{templateId} | deletePromptTemplate | Delete a user-source template; 403 on host-built-in or pack-sourced. |
POST | /v1/prompts:render | renderPromptTemplate | Render with supplied bindings; returns composed body + sha256 hash + per-variable hashes. Does NOT dispatch an LLM call. |
Deterministic-render invariant. The :render response's hash MUST equal the hash that a matching prompt.composed event would carry at dispatch time for the same (ref, variables, contentTrust) inputs. This is the same determinism contract prompt.composed participates in for replay (per §"Replay determinism" above) — the :render endpoint is the preview surface that lets clients validate hashes before dispatch.
Cache semantics. GET /v1/prompts/{templateId} responses SHOULD set ETag: "<sha256-of-body>" and Cache-Control: max-age=60. When the request pinned ?version, hosts SHOULD upgrade to Cache-Control: public, max-age=31536000, immutable (mirrors node-packs.md §"Immutable artifact" semantics).
Authorization. Mutating endpoints MUST require authentication per auth.md. Hosts SHOULD scope by writer role; the spec defers role-mapping to host policy.
Workspace membership on workspace-scoped reads and writes (normative, RFC 0048 §D). When a request to /v1/prompts* targets a workspace-scoped resource — either a mutating POST / PUT / DELETE whose body or URL carries a workspaceId, OR a read (GET /v1/prompts, GET /v1/prompts/{templateId}, POST /v1/prompts:render) whose query string or body carries an explicit workspaceId — hosts MUST verify that the authenticated principal is a member of the target workspace BEFORE honoring the request. A workspaceId supplied by the caller (in the request body, URL path, or query string) MUST NOT be trusted as authorization on its own — the host MUST resolve workspace membership from the authenticated identity (per auth.md §"Identity claims — tenant · workspace · principal" and RFC 0048 §D's cross-workspace isolation MUST-NOT), independent of any caller-supplied workspace identifier. Non-members MUST be rejected fail-closed before any persistence occurs (mutations) or any workspace-scoped content is returned (reads). The canonical refusal envelope is 403 { "error": "workspace_membership_required" } per rest-endpoints.md §"Common error codes"; hosts MAY refuse with other 4xx/5xx codes (401 if interpreting the failure as authentication-level; 404 to avoid existence disclosure) — the canonical envelope shape is pinned only when the host chooses 403.
Hosts that authenticate against an identity provider but persist via a database vendor's privileged admin client (Firebase Admin SDK, Supabase service-role key, raw Postgres with a server-side connection, Convex equivalent) MUST replicate the membership check at the application tier rather than relying solely on the database vendor's row-level security rules — privileged admin clients bypass those rules. The vendor's rules are a defense-in-depth layer below the application-tier membership check, not a substitute for it. This pattern is the failure mode that lets adopters convince themselves the check is redundant: a developer reading the route handler can see the Firestore rules and reason "the rules will catch a cross-tenant write," when in fact the Admin SDK code path bypasses them entirely. The application-tier check is mandatory.
Read paths are NOT exempt just because they don't write. A GET /v1/prompts?workspaceId=<not-mine> that returns another workspace's templates is a cross-tenant data leak with the same blast radius as a cross-tenant write — sensitive prompt text, model hints, and variable schemas can carry intellectual property, customer data, or operational secrets. The read-side authz check is symmetric to the write-side check; the same workspace_membership_required envelope applies. Read-only hosts that expose workspace-scoped reads are subject to this invariant even if they never advertise mutableLibrary: true.
Verified by the prompt-mutation-workspace-membership-enforced SECURITY invariant (write path; capability-gated on mutableLibrary: true) and the prompt-read-workspace-membership-enforced SECURITY invariant (read path; capability-gated on prompts.supported: true with workspaceId-acceptance auto-detected from the probe response).
Prompt-pack distribution
A third pack kind alongside node packs (RFC 0003) and workflow-chain packs (RFC 0013). Distinguished by kind: "prompt" in the manifest:
{
"name": "vendor.acme.editorial-prompts",
"version": "1.0.0",
"kind": "prompt",
"engines": { "openwop": ">=1.1.0 <2.0.0" },
"prompts": [
{
"templateId": "writer-system",
"version": "1.0.0",
"kind": "system",
"text": "You are a careful editorial writer.",
"tags": ["editorial"]
}
]
}
See schemas/prompt-pack-manifest.schema.json for the full shape.
Install-time validation. When a host installs a prompt pack:
1. Verify the Ed25519 signature per registry-operations.md §"Signature verification" — same flow as node and chain packs. 2. Verify SRI integrity per registry-operations.md §"Subresource Integrity" — unchanged. 3. Compile each prompts[].text against prompt-template.schema.json and assert variable-reference closure (every {{varName}} in text either appears in variables[] or matches a canonical context key). 4. Resolve every entry in the manifest's dependencies block; an unresolvable entry rejects the install with prompt_pack_dependency_unresolvable. 5. Reject install with prompt_template_invalid on any of the above.
Pack-kind discriminator invariant. A manifest with kind: "prompt" MUST NOT also carry nodes[] or chains[] arrays. Same posture as RFC 0013's "negative example."
Conflict resolution (cross-pack templateId collision). When two installed prompt packs ship the same templateId:
- Both surface in
GET /v1/promptswith distinctmeta.source: "pack"+meta.packName+meta.packVersiondiscriminators. - A stringy
PromptRef(prompt:writer-system@1.0.0) withoutlibraryIdis rejected withprompt_ref_ambiguouswhen more than one match exists. - Clients disambiguate by using the structured object form:
{ libraryId: "vendor.acme.editorial-prompts", templateId: "writer-system", version: "1.0.0" }.
Capability advertisement extensions
Phase B extends capabilities.prompts with three additional optional fields:
| Field | Semantics |
|---|---|
packsSupported: boolean | Host installs kind: "prompt" packs and exposes their templates at GET /v1/prompts with meta.source: "pack". False or absent = no pack support. |
mutableLibrary: boolean | Host honors POST / PUT / DELETE /v1/prompts*. False or absent → 501 on those endpoints. Pack-sourced + host-built-in templates remain read-only regardless. |
library.{id, renderEndpoint, maxRenderRequestBytes} | Per-library configuration knobs. id enables structured-PromptRef libraryId lookup. renderEndpoint overrides the default /v1/prompts:render path. maxRenderRequestBytes caps :render request body size. |
Provenance fields on PromptTemplate.meta
When a template is pack-sourced, the host MUST populate two additional meta fields:
meta.packName— matches the installed pack'sname. Required whenmeta.source: "pack".meta.packVersion— matches the installed pack'sversion. Required whenmeta.source: "pack".
A JSON-Schema if/then conditional in prompt-template.schema.json enforces this at install time.
Resolution chain (normative)
When a host advertises capabilities.prompts.supported: true and a workflow node carries one or more *PromptRef keys (per §"Composition + observability" above), the host MUST resolve each (nodeId, kind) pair to a single PromptRef (or null) by walking the four ordered layers below and selecting the first non-null result. Lower-numbered layers take precedence over higher-numbered layers. The traversal order is the same for all four kinds (system, user, few-shot, schema-hint); per-kind branches are noted inline.
For every (nodeId, kind) pair the host attempts to resolve, the host MUST emit one agent.promptResolved RunEventDoc (per schemas/run-event-payloads.schema.json agentPromptResolved) before the corresponding prompt.composed event (when emitted). The event's chain[] array carries one entry per layer attempted with applied: true on the winning layer.
Layer 1 — Node config (highest precedence)
The host first consults the executing WorkflowNode.config:
| Kind | Field consulted |
|---|---|
system | WorkflowNode.config.systemPromptRef |
user | WorkflowNode.config.userPromptRef |
few-shot | WorkflowNode.config.fewShotPromptRefs[] (first non-empty entry; further entries surface as additionalPromptRefs for composition) |
schema-hint | WorkflowNode.config.schemaHintPromptRef |
If the field is set, the resolved ref is that value and resolution halts. When the node carries an _inline_ string body in the corresponding sibling field (e.g., config.systemPrompt) AND a PromptRef, the ref wins and the warn-log rule from RFC 0027 §C applies (prompt_ref_supersedes_inline).
Layer 2 — Agent binding
Layer 2 is consulted only when capabilities.prompts.agentBindings: true is advertised AND the node carries config.agentId referencing a known AgentManifest. Resolution depends on the kind:
systemkind:
- If AgentManifest.systemPrompt | systemPromptRef is set (the RFC 0003 intrinsic surface), the resolved ref is the agent's intrinsic prompt — a synthetic PromptRef projected from the manifest's tarball-relative path or inline body. Hosts MUST tag this chain entry as layer: "agent-intrinsic". - Else if AgentManifest.promptOverrides.system is set, that ref applies. Chain entry: layer: "agent-overrides". - Else if AgentManifest.promptLibraryRef is set, hosts MAY look up a same-kind default template from that library. Chain entry: layer: "agent-library-default". (The lookup convention — e.g., prompt:<libraryId>.default-<kind> — is host policy in v1.x.) - Else fall through to layer 3.
- Other kinds (
user,few-shot,schema-hint):
- If AgentManifest.promptOverrides[kind] is set, that ref applies. Chain entry: layer: "agent-overrides". - Else if AgentManifest.promptLibraryRef is set, host MAY apply library-default per above. - Else fall through to layer 3.
If config.agentId is unset, layer 2 is skipped entirely; resolution proceeds to layer 3. If config.agentId references an unknown agent, hosts MUST emit a log.appended warning with code: "agent_binding_unresolvable" and skip layer 2.
Layer 3 — Workflow defaults
If WorkflowDefinition.defaults.promptRefs[kind] is set, that ref applies. Chain entry: layer: "workflow-defaults". Else fall through to layer 4.
Layer 4 — Host built-ins (lowest precedence)
If the host's capabilities.prompts.defaults[kind] advertises a PromptRef, that ref applies. Chain entry: layer: "host-defaults". Hosts MAY ship per-kind defaults; the openwop spec ships none.
If all four layers yield null, the resolved ref for that (nodeId, kind) is null and the emitted agent.promptResolved.resolved is null. The node MAY still execute — for example, a core.ai.callPrompt node with userPrompt provided as a direct inline string in config doesn't need a user-kind ref. Whether a null resolution is fatal is per-node-type semantics, not protocol policy.
Run-configurable extension layer (optional, non-normative)
Hosts MAY honor RunOptions.configurable.promptOverrides as the highest-precedence layer — applied _before_ layer 1 in the traversal, taking precedence over node-config refs. When a host implements this extension, it MUST emit a chain entry with layer: "run-configurable" ahead of the node entry so cross-host debuggers render the additional step. This extension is non-normative in v1.x; a future RFC may promote it to a normative layer.
Replay determinism
agent.promptResolved events are durable and participate in replay. Invariants:
resolvedMUST replay identically. Divergence MUST emitreplay.divergedwithdivergencePoint: "agent.promptResolved".chain[].appliedMUST replay identically whenresolvedmatches.chain[].sourceSHOULD replay identically; if a host has rotated host-built-in defaults between original and replay, thehost-defaultsentry'ssourceMAY differ. Replay tooling MUST tolerate this and surface the rotation explicitly rather than treating it as an integrity failure.
Orthogonality with model-capability gating (non-normative)
Per the envelope-track RFC 0031 (Draft), NodeModule.requiredModelCapabilities and NodeModule.fallbackModel answer a different question — _which model can dispatch this node?_ — and emit model.capability.substituted / model.capability.insufficient events. The two surfaces are orthogonal axes:
- Prompt resolution (this section) answers "which PromptRef applies at
(nodeId, kind)?" — does not influence model selection. - Model-capability gating (RFC 0031) answers "which model can dispatch this node?" — does not influence prompt selection.
A node MAY carry both surfaces independently. The agent.promptResolved event emitted by this section and the model.capability.* events emitted by RFC 0031 are distinct observability surfaces and MAY both fire for the same node execution. No precedence rule applies between them.
Open spec gaps
| # | Gap | Owner / RFC |
|---|---|---|
| P1 | Reference-host implementation of the four-layer resolution chain + agent.promptResolved emission in core.ai.callPrompt | Acceptance-gate item per RFC 0029 (wire shape landed in this document §"Resolution chain (normative)") |
| P2 | Reference-host emission of prompt.composed from core.ai.callPrompt in the workflow-engine sample | Acceptance-gate item per RFC 0027 |
| P3 | First non-steward host advertises capabilities.prompts.supported: true | Acceptance-gate item per RFC 0027 |
| P4 | Reference-host implementation of /v1/prompts* REST endpoints + prompt-pack install flow | Acceptance-gate item per RFC 0028 |
| P5 | Nested template includes ({{include:prompt:other@1.0.0}}) — deferred to a future RFC if demand emerges | (open) |
| P6 | Canonical enumeration of context source variable names — currently non-normative recommendations | (open) |
| P7 | Cross-validation of modelHints.envelopeType against capabilities.supportedEnvelopes and the Tier-1 portability subset (RFC 0030) | RFC 0030 (Draft, parallel track) |
| P8 | Cross-pack dependencies semantics — extends: template inheritance, transitive closure resolution | RFC follow-up to 0028 (deferred) |
Cross-reference
schemas/workflow-definition.schema.json(configdescription) + §"Resolution chain" Layer 1 above — the convention by whichWorkflowNode.configcarries PromptRef values.capabilities.md— discovery handshake (this document extends thepromptsblock).host-capabilities.md§host.aiEnvelope — the existing LLM-call surface that prompt resolution feeds.mcp-integration.md§"Trust boundary" + RFC 0020 §D —meta.contentTrustpropagation thatprompt.composed.contentTrustmirrors.replay.md— replay invariants this document extends withprompt.composed.RFCS/0027-prompt-templates.md— Phase A wire-shape RFC (this document's source).RFCS/0028-prompt-library-endpoints.md— Phase B registry + endpoint surface.RFCS/0029-prompt-override-hierarchy.md— Phase C resolution chain +agent.promptResolved.RFCS/0030-envelope-reasoning-and-tier-one-subset.md— parallel-track sibling for the LLM-emission side of the three-surface taxonomy.SECURITY/threat-model-secret-leakage.mdSR-1 —[REDACTED:<id>]marker discipline.SECURITY/threat-model-prompt-injection.md—<UNTRUSTED>...</UNTRUSTED>marker discipline.- External: MyndHyve
PromptEntry/PromptLibrary/WorkflowPromptService.resolveForExecution()reference impl — closest single-host prior-art for the resolution-chain pattern RFC 0029 normates.