@openwop/spec-artifacts 2.44.5 → 2.44.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/CORPUS-STAMP.json +49 -47
  2. package/api/seams-v2.yaml +1 -1
  3. package/api/v2/asyncapi.yaml +1 -1
  4. package/api/v2/openapi.yaml +31 -122
  5. package/package.json +1 -1
  6. package/schemas/v2/agent-ref.schema.json +2 -3
  7. package/schemas/v2/budget-policy.schema.json +2 -3
  8. package/schemas/v2/capabilities.schema.json +42 -38
  9. package/schemas/v2/debug-bundle.schema.json +2 -2
  10. package/schemas/v2/error-envelope.schema.json +282 -4
  11. package/schemas/v2/node-pack-manifest.schema.json +2 -2
  12. package/schemas/v2/prompt-ref.schema.json +2 -3
  13. package/schemas/v2/prompt-template.schema.json +3 -3
  14. package/schemas/v2/run-event-payloads.schema.json +5 -5
  15. package/spec/v1/alias-detectors.json +25 -1
  16. package/spec/v1/core-standard-manifest.json +2 -2
  17. package/spec/v1/deprecations.json +89 -1
  18. package/spec/v1/gaps.json +42 -8
  19. package/spec/v1/migrations.json +57 -1
  20. package/spec/v2/core/artifact-type-packs.md +5 -1
  21. package/spec/v2/core/capabilities.md +20 -22
  22. package/spec/v2/core/conformance.md +24 -7
  23. package/spec/v2/core/errors.md +17 -119
  24. package/spec/v2/core/events.md +7 -7
  25. package/spec/v2/core/execution.md +56 -16
  26. package/spec/v2/core/host-services.md +57 -58
  27. package/spec/v2/core/i18n.md +10 -10
  28. package/spec/v2/core/idempotency.md +3 -3
  29. package/spec/v2/core/identity.md +11 -9
  30. package/spec/v2/core/interop.md +6 -8
  31. package/spec/v2/core/interrupt.md +8 -8
  32. package/spec/v2/core/oauth.md +4 -4
  33. package/spec/v2/core/packs.md +11 -5
  34. package/spec/v2/core/persistence.md +11 -13
  35. package/spec/v2/core/portability.md +4 -4
  36. package/spec/v2/core/replay.md +8 -10
  37. package/spec/v2/core/runs.md +20 -7
  38. package/spec/v2/core/security-defaults.md +0 -2
  39. package/spec/v2/core/storage.md +18 -20
  40. package/spec/v2/core/versioning.md +4 -9
  41. package/spec/v2/core/webhooks.md +13 -8
  42. package/spec/v2/declaration.json +3 -3
  43. package/spec/v2/errors.json +206 -2
  44. package/spec/v2/facets/agents.schema.json +367 -0
  45. package/spec/v2/facets/aiProviders.schema.json +2 -2
  46. package/spec/v2/generated/error-codes.md +130 -0
  47. package/spec/v2/release.json +2 -2
  48. package/spec/v2/surface-baseline.json +38 -2
@@ -1,7 +1,7 @@
1
1
  # Execution
2
2
 
3
3
  > **Status: Stable.**
4
- > **Normative home:** `selfHostedRunner`, `subWorkflow`, `multiAgent`.
4
+ > **Normative home:** `selfHostedRunner`, `subWorkflow`, `multiAgent`, `agents`.
5
5
 
6
6
  ## Why this exists
7
7
 
@@ -9,7 +9,7 @@ A step can run on a user's machine, in a child run, or under a supervisor loop;
9
9
 
10
10
  ## `selfHostedRunner`
11
11
 
12
- A runner is a user-operated process that dials out, holds credentials the host cannot reach, and executes single model or tool steps. It receives dispatch frames over SSE and POSTs result frames, on host-defined paths. A host MUST NOT advertise `selfHostedRunner` unless it accepts registrations, routes matching dispatch and delivers results.
12
+ A runner is a user-operated process that dials out, holds credentials the host cannot reach, and executes single model or tool steps. On host-defined paths, it receives dispatch frames over SSE and POSTs result frames. A host MUST NOT advertise `selfHostedRunner` unless it accepts registrations, routes matching dispatch and delivers results.
13
13
 
14
14
  - **Records.** A registration (`schemas/v2/self-hosted-runner-registration.schema.json`) MUST NOT appear in discovery. `dispatchKinds` lists `model`, `tool` or both.
15
15
  - **Subject isolation.** A host MUST NOT route a step to a runner its run's subject does not own, and MUST match on subject before capability.
@@ -23,29 +23,25 @@ A runner is a user-operated process that dials out, holds credentials the host c
23
23
 
24
24
  `core.subWorkflow` starts a child run of another workflow and waits for its terminal status.
25
25
 
26
- - **`workflowId`.** A host MUST refuse the parent run when that workflow is not loaded.
26
+ - **`workflowId`.** A host MUST refuse the parent run with `node_config_invalid` when that workflow is not loaded.
27
27
  - **`waitForCompletion`** defaults to `true`; a host MAY refuse `false` with `validation_error`.
28
28
  - **`onChildFailure`.** `fail-parent` (default) fails the node and the run; `absorb` records the failure and continues.
29
29
  - **Output.** `node.completed` MUST carry `outputs.childRunId` and `outputs.childStatus` (`completed | failed | cancelled`), and MAY add fields.
30
30
  - **`inputMapping`** (`childVar → parentVar`) seeds the child once, at creation, after and over its `variables[].defaultValue`, which MUST seed first. An unset parent variable MUST arrive undefined, never an error or `null`. A host not advertising `subWorkflow.inputMapping` MUST refuse a non-empty `inputMapping` at registration with `validation_error`, naming it in `details.requiredCapability`.
31
31
  - **`outputMapping`** (`parentVar → childVar`). After the child completes, the host MUST copy each mapped variable into the parent, without throwing on or copying an undefined one.
32
32
  - **`propagateCancellation`** (default `true`) cancels the child with its parent.
33
+ - **Parent link.** Where `getRunAncestry` is served, the child's `parent` MUST be the parent run, with `cause: "core.subWorkflow"`. It does not name the dispatching node; that node's `node.completed` carries `outputs.childRunId`. `parentRunId` is fork lineage, not this link.
33
34
 
34
35
  ## `multiAgent`
35
36
 
36
- `multiAgent.executionModel.version` is cumulative: a host advertising `N` MUST implement levels 1 through `N`.
37
-
38
- | Level | Adds |
39
- | --- | --- |
40
- | 1 | the supervisor loop and worker handoff |
41
- | 2 | confidence escalation; memory across sub-runs |
42
- | 3 | cross-host causation |
43
- | 4 | replay determinism under nondeterministic models |
44
- | 5 | stateful loop lifecycle and context budget |
45
- | 6 | verifier turn and convergence |
46
-
47
- Levels 2–6 are not yet restated in v2 prose. Their rules are those of their owning RFCs: level 2 [RFC 0039](https://github.com/openwop/openwop/blob/main/RFCS/0039-multi-agent-confidence-and-memory-lifecycle.md), level 3 [RFC 0040](https://github.com/openwop/openwop/blob/main/RFCS/0040-multi-agent-cross-host-causation.md), level 4 [RFC 0041](https://github.com/openwop/openwop/blob/main/RFCS/0041-multi-agent-replay-under-nondeterminism.md), level 5 [RFC 0061](https://github.com/openwop/openwop/blob/main/RFCS/0061-agent-loop-lifecycle.md) and [RFC 0111](https://github.com/openwop/openwop/blob/main/RFCS/0111-context-economy.md), level 6 [RFC 0090](https://github.com/openwop/openwop/blob/main/RFCS/0090-agent-verifier-and-convergence.md).
37
+ `multiAgent.executionModel.version` is cumulative: a host advertising `N` MUST implement levels 1 through `N`. Each level adds:
48
38
 
39
+ 1. the supervisor loop and worker handoff;
40
+ 2. confidence escalation, and memory across sub-runs;
41
+ 3. cross-host causation;
42
+ 4. replay determinism under nondeterministic models;
43
+ 5. stateful loop lifecycle and context budget;
44
+ 6. verifier turn and convergence.
49
45
 
50
46
  At level 1, a workflow whose `core.orchestrator.supervisor` feeds `core.dispatch` runs this loop:
51
47
 
@@ -55,4 +51,48 @@ At level 1, a workflow whose `core.orchestrator.supervisor` feeds `core.dispatch
55
51
  - `output.harvested` fires exactly when a child completes with a non-empty `outputMapping`.
56
52
  - A host not advertising `executionModel` MUST NOT emit `core.workflowChain.event`.
57
53
 
58
- *Sources: RFCs 0007, 0022, 0037, 0122.*
54
+ At level 2:
55
+
56
+ - A `next-worker` or `terminate` decision whose `confidence` is below the floor (`confidenceEscalationFloor`, else `0.5`) MUST NOT execute silently. The host MUST record `core.workflowChain.confidence-escalated`, then fire a `clarification` interrupt (preferred) or an `approval` interrupt, both before any `dispatch.began` for that decision. An absent `confidence` MUST NOT trigger escalation.
57
+ - With `memory` also advertised, a child of `core.dispatch` or `core.subWorkflow` MUST keep memory scoped per `(tenantId, scopeId)` ([host-services.md](host-services.md) §`memory`). When it shares its parent's scope, its writes are visible to the parent from its completion and on later supervisor turns, and an entry's `ttl` MUST run from the child's write time.
58
+ - The host MUST serialize sibling children's writes to the shared scope per parent run. A host advertising `crossChildMemoryConcurrency: "advisory"` is exempt, and SHOULD document last-write-wins.
59
+ - The host MUST persist memory snapshots by log index: before `fromSeq`, a fork's memory reads MUST return the source run's memory as of `fromSeq`, or the fork is refused ([replay.md](replay.md)).
60
+
61
+ At level 3, with `crossHostCausation` advertised:
62
+
63
+ - An event whose `causationId` names an event on another host MUST carry `causationHostId`, equal to that host's `crossHostCausation.hostId`; a same-host one MUST NOT.
64
+ - The host MUST propagate the run's trace context into every outbound MCP request and A2A message, and adopt an inbound one, per [interop.md](interop.md).
65
+ - Under `ancestryEndpointSupported` the host MUST serve `getRunAncestry` ([runs.md](runs.md)); otherwise it returns `404` and clients walk `causationHostId`.
66
+
67
+ At level 4, every LLM-calling node MUST meet [replay.md](replay.md)'s invocation key, refusal-divergence and observable-result rules.
68
+
69
+ At level 5:
70
+
71
+ - Every `runOrchestrator.decided` MUST carry `iteration`, 1-based and incremented by exactly 1 per turn; `maxLoopIterations` bounds it ([runs.md](runs.md)).
72
+ - Turn *i*'s inputs MUST be reproducible on replay: memory as of its log index, the workspace snapshot when `workspace` is advertised, and the log tail bounded by `transcriptWindow`. A turn's writes MUST become visible to turn *i+1*, never to turn *i*.
73
+ - Under `statefulResume`, a resumed loop MUST continue at the same `iteration` with the same snapshot lineage. A heartbeat MAY enqueue a fresh loop run and MUST NOT advance a suspended one.
74
+ - `contextBudget` bounds that transcript in tokens, per its `schemas/v2/capabilities.schema.json` descriptions and `context.summarized`. A replay MUST reuse a recorded summary, never re-summarize.
75
+
76
+ At level 6, with `verifier` advertised:
77
+
78
+ - A critic MUST emit content-free `agent.verified` over a prior result: an `agent.decided` event, a child run or a tool call.
79
+ - Under `verifier.gating`, a `fail` MUST NOT be merged or `terminate` as success, and a `revise` SHOULD route back to an actor turn within `maxLoopIterations`. A missing verdict is not a failure. Without `gating`, verdicts are observational.
80
+ - A consumer MUST NOT treat a `terminate` whose `successCriteria` has a `met: false` entry as goal-satisfied.
81
+
82
+ ## `agents`
83
+
84
+ `agents` and each of its facets are optional; a client MUST tolerate their absence. A host advertising `agents` serves `RunSnapshot.agent` and `runOrchestrator`, emits the `agent.*` family, and suspends a low-confidence `agent.decided`. Each facet's rules are its description in `spec/v2/facets/agents.schema.json`, with the records it names under `schemas/v2/`:
85
+
86
+ | Facets | Records |
87
+ | --- | --- |
88
+ | `profile`, `modelClasses`, `orchestratorPattern`, `orchestrator`, `dispatch`, `dispatchMapping`, `reasoning`, `subRunAttestation` | `dispatch-config` |
89
+ | `manifestRuntime`, `liveRuntime` | `agent-manifest`, `agent-inventory-response` |
90
+ | `memoryBackends`, `memoryConsolidation`, `commitments` | `memory-entry` ([host-services.md](host-services.md)) |
91
+ | `evalSuite` | `agent-eval-suite`, `eval-summary` |
92
+ | `deployment` | `agent-deployment`, `agent-deployment-transition`, `agent-ref` |
93
+ | `roster`, `orgChart` | `agent-roster-entry`, `agent-org-chart` |
94
+ | `proposals`, `goals` | `proposal`, `goal` |
95
+
96
+ A host MUST NOT advertise `liveRuntime` or `roster` without `manifestRuntime`, `orgChart` without `roster`, or `memoryConsolidation` without `long-term` in `memoryBackends`. `roster.installScope` MUST equal `manifestRuntime.installScope`; `orgChart.installScope` SHOULD equal it.
97
+
98
+ *Sources: RFCs 0002, 0007, 0022, 0037, 0039, 0040, 0041, 0061, 0068, 0077, 0086, 0087, 0090, 0111, 0122, 0228.*
@@ -5,71 +5,64 @@
5
5
 
6
6
  ## Why this exists
7
7
 
8
- A node pack invokes advertised `host.*` services through `ctx`. This document states the contract a host takes on by advertising them, and how it schedules runs, matches models and audits tool calls.
8
+ A node pack invokes advertised `host.*` services through `ctx`. Each section below states the contract a host takes on by advertising that family.
9
9
 
10
10
  ## `aiEnvelope`
11
11
 
12
- A host advertising `aiEnvelope`:
13
-
14
- - MUST expose `ctx.aiEnvelope.generate` to pack code;
15
- - MUST refuse a node whose `typeId` requires the family when it does not advertise it;
16
- - MUST expose `ctx.aiEnvelope.await` when, and only when, it advertises `aiEnvelope.await`.
12
+ A host advertising `aiEnvelope` MUST expose `ctx.aiEnvelope.generate` to pack code, and MUST expose `ctx.aiEnvelope.await` when, and only when, it advertises `aiEnvelope.await`. A host MUST refuse a node whose `typeId` requires the family when it does not advertise it.
17
13
 
18
14
  ## `promptLibrary`
19
15
 
20
- A host advertising `promptLibrary`:
21
-
22
- - MUST expose `ctx.promptLibrary.get`;
23
- - MUST return a pinned version verbatim, so that a replayed run resolves the same template;
24
- - MUST fail the calling node rather than substitute an unpinned one.
16
+ A host advertising `promptLibrary` MUST expose `ctx.promptLibrary.get` and MUST return a pinned version verbatim, so a replayed run resolves the same template. It MUST fail the calling node rather than substitute an unpinned version.
25
17
 
26
18
  ## `prompts`
27
19
 
28
- A host advertising `prompts` resolves node `config` PromptRefs (`systemPromptRef`, `userPromptRef`, `fewShotPromptRefs`, `schemaHintPromptRef`); otherwise they are opaque strings.
20
+ A host advertising `prompts` resolves the PromptRefs in node `config` (`systemPromptRef`, `userPromptRef`, `fewShotPromptRefs`, `schemaHintPromptRef`); to any other host they are opaque strings.
29
21
 
30
- - `templateKinds` and `variableSources` narrow what is accepted; `secret` SHOULD appear only with `secrets`.
22
+ - `templateKinds` and `variableSources` narrow what the host accepts; the `secret` source SHOULD appear only with `secrets`.
31
23
  - `maxTemplateBytes` MUST NOT exceed 65536. `observability` is `off`, `hashed` (default) or `full`.
32
- - `endpointsSupported` gates `/prompts*`, `mutableLibrary` its writes, `packsSupported` pack installs; ungated operations answer `501`. `library` carries `id`, `renderEndpoint`, `maxRenderRequestBytes`.
24
+ - `endpointsSupported` gates the `/prompts*` operations, `mutableLibrary` their writes and `packsSupported` pack installs. An operation whose gate is off answers `404 not_found`.
25
+ - `library` carries `id`, `renderEndpoint` and `maxRenderRequestBytes`.
33
26
 
34
27
  ### Resolution
35
28
 
36
- For each `(nodeId, kind)` the host MUST take the first non-null ref below, and MUST emit `agent.prompt-resolved` (one `chain[]` entry per layer tried) before `prompt.composed`:
29
+ For each `(nodeId, kind)`, the host MUST take the first non-null ref from these layers, and MUST emit `agent.prompt-resolved`, with one `chain[]` entry per layer tried, before `prompt.composed`:
37
30
 
38
- 1. node `config`; a ref beats an inline body;
39
- 2. under `agentBindings`, the `config.agentId` agent: for `system` first its own prompt (tagged `agent-intrinsic`), then `promptOverrides`, then MAY a `promptLibraryRef` default. An unknown agent MUST log a warning and be skipped;
31
+ 1. node `config`, where a ref beats an inline body;
32
+ 2. under `agentBindings`, the agent `config.agentId` names: for `system`, first its own prompt (tagged `agent-intrinsic`), then its `promptOverrides`, and last the host MAY use a `promptLibraryRef` default. An unknown agent MUST be logged as a warning and skipped;
40
33
  3. the workflow's `defaults.promptRefs[kind]`;
41
34
  4. `prompts.defaults[kind]`.
42
35
 
43
- All-null is fatal only if the node type says so. A host honoring `ai.promptOverrides` applies it first and MUST record a `run-configurable` entry.
36
+ When every layer is null, the node fails only if its node type says so. A host honoring `ai.promptOverrides` applies it before any layer and MUST record a `run-configurable` entry.
44
37
 
45
38
  ### Composition
46
39
 
47
- - `{{varName}}` substitution is literal. An unbound required variable MUST fail the node; an unbound optional or undeclared one renders empty, and install SHOULD warn on an undeclared one.
40
+ - `{{varName}}` substitution is literal. An unbound required variable MUST fail the node (`node_config_invalid`); an unbound optional or undeclared one renders empty, and install SHOULD warn on an undeclared one.
48
41
  - `secret` values MUST appear only as `[REDACTED:<secretId>]` in observability output.
49
- - Untrusted input MUST be wrapped verbatim in `<UNTRUSTED>…</UNTRUSTED>`, making `contentTrust` `untrusted`.
50
- - Unless `observability` is `off`, the host MUST emit `prompt.composed` per composition, with bodies only under `full`.
51
- - On replay, `hash`, `variableHashes`, `refs`, the resolved ref and `chain[].applied` MUST match, and a mismatch MUST emit `replay.diverged`. Bodies MAY be omitted; `chain[].source` SHOULD match, and a rotated `host-defaults` source MUST be tolerated.
42
+ - Untrusted input MUST be wrapped verbatim in `<UNTRUSTED>…</UNTRUSTED>`, which makes `contentTrust` `untrusted`.
43
+ - Unless `observability` is `off`, the host MUST emit `prompt.composed` for each composition, carrying bodies only under `full`.
44
+ - On replay, `hash`, `variableHashes`, `refs`, the resolved ref and `chain[].applied` MUST match the recording, and a mismatch MUST emit `replay.diverged`. Bodies MAY be omitted. `chain[].source` SHOULD match, and a rotated `host-defaults` source MUST be tolerated.
52
45
 
53
46
  ### Library
54
47
 
55
- - A version-less ref resolves the latest. A stringy ref matching several templates MUST be refused; `libraryId` disambiguates.
48
+ - A ref without a version resolves to the latest. A string ref matching several templates MUST be refused with `validation_error`, `details.field: libraryId`.
56
49
  - `renderPromptTemplate` dispatches nothing, and its `hash` MUST equal the dispatch-time `prompt.composed` hash.
57
- - `getPromptTemplate` SHOULD send `ETag` and `max-age=60`, `immutable` when `version` is pinned.
50
+ - `getPromptTemplate` SHOULD send `ETag` and `max-age=60`, plus `immutable` when `version` is pinned.
58
51
  - Writes MUST be authenticated and SHOULD be role-scoped. An update MUST carry a greater SemVer; built-in and pack templates are read-only.
59
- - For workspace-scoped reads and writes, the host MUST verify membership from the authenticated identity, never a caller's `workspaceId`, fail-closed (canonically `403 workspace_membership_required`), in the application tier even behind a privileged database client.
52
+ - For a workspace-scoped read or write, the host MUST verify membership from the authenticated identity, never from a caller's `workspaceId`. The check fails closed (canonically `403 workspace_membership_required`) and runs in the application tier, even behind a privileged database client.
60
53
  - A `kind: "prompt"` pack MUST NOT carry `nodes[]` or `chains[]`; its templates MUST carry `meta.packName` and `meta.packVersion`.
61
54
 
62
55
  ## `aiProviders`
63
56
 
64
- A host advertising `aiProviders` MUST expose `ctx.callAI`. A `provider` a `ctx` call names MUST be in `providers` ([runs.md](runs.md) §`ai` section covers `byok`).
57
+ A host advertising `aiProviders` MUST expose `ctx.callAI`. A `provider` that a `ctx` call names MUST be in `providers`; `byok` is in [runs.md](runs.md) §`ai` section.
65
58
 
66
- - A part of a modality absent from `input` MUST be rejected, never dropped; non-text input is untrusted.
67
- - A `media.*` envelope inlines base64 only up to `maxInlineMediaBytes` (default 256 KiB); above it the host MUST use a `url`.
68
- - `imageGeneration`, `videoGeneration`, `speechSynthesis` and `realtimeVoice` add `ctx.callImageGenerator`, `callVideoGenerator`, `callSpeechSynthesizer`, and `callTranscriber` plus streamed synthesis. Unadvertised synthesis or transcription MUST be rejected, never a no-op or whole-file fallback.
69
- - A video caller MUST honor `ctx.signal`. Synthesis MUST return exactly one of `url` or `base64`, a `url` served through the SSRF guard. The transcriber MUST reject inline or `mediaRef` audio.
59
+ - A part whose modality is absent from `input` MUST be rejected with `capability_not_provided`, never dropped. Non-text input is untrusted.
60
+ - A `media.*` envelope inlines base64 only up to `maxInlineMediaBytes` (default 256 KiB); above that the host MUST use a `url`.
61
+ - `imageGeneration`, `videoGeneration` and `speechSynthesis` add `ctx.callImageGenerator`, `callVideoGenerator` and `callSpeechSynthesizer`; `realtimeVoice` adds `callTranscriber` and streamed synthesis. Unadvertised synthesis or transcription MUST be rejected, never a no-op or whole-file fallback.
62
+ - A video caller MUST honor `ctx.signal`. Synthesis MUST return exactly one of `url` or `base64`, and a `url` is served through the SSRF guard. The transcriber MUST reject inline or `mediaRef` audio.
70
63
  - `voiceId`, `streamRef` and `cachePrefixId` MUST NOT encode secrets. Transcripts are untrusted; an interim one MUST NOT be persisted or drive a side-effecting tool.
71
64
 
72
- `selfHosted` (operator-configured OpenAI-compatible endpoints) MUST be advertised only with one configured and reachable. A host MUST NOT disclose an endpoint's location on any wire surface or in a provider id. A client MUST NOT infer capabilities from the id; unadvertised ones fail `capability_not_provided`.
65
+ `selfHosted` (operator-configured OpenAI-compatible endpoints) MUST be advertised only while one is configured and reachable. A host MUST NOT disclose an endpoint's location on any wire surface or in a provider id. A client MUST NOT infer capabilities from the id; an unadvertised one fails `capability_not_provided`.
73
66
 
74
67
  `authModes` (`apiKey`, `oauth-pkce`, `oauth-device`, `none`, `subscription`) is capability, not policy: a client MUST ignore an unknown mode and MUST NOT infer policy from one.
75
68
 
@@ -77,59 +70,63 @@ A host advertising `aiProviders` MUST expose `ctx.callAI`. A `provider` a `ctx`
77
70
  - A `subscription` credential MUST bind at user scope; a tenant or workspace binding MUST be rejected with `credential_scope_forbidden`.
78
71
  - A host MUST NOT advertise `subscription` without a reachable acquisition mechanism.
79
72
 
80
- `policies.modes` lists enforced modes; a client MUST tolerate any subset, and absence means `optional` only. The host MUST document its `scopes` precedence. `disabled` always refuses (`provider_disabled`); `required` refuses without `credentialRef` (`byok_required`) or a usable secret (`byok_required_but_unresolved`); `restricted` refuses a model matching no `allowedModels` glob (`model_not_allowed`).
73
+ `policies.modes` lists the enforced modes; a client MUST tolerate any subset, and absence means `optional` only. The host MUST document its `scopes` precedence.
74
+
75
+ - `disabled` always refuses (`provider_disabled`).
76
+ - `required` refuses without a `credentialRef` (`byok_required`) or a usable secret (`byok_required_but_unresolved`).
77
+ - `restricted` refuses a model matching no `allowedModels` glob (`model_not_allowed`). An empty `restricted` policy MUST fail closed.
81
78
 
82
- A refusal MUST carry `policies.errorCode` (or its default), SHOULD carry `reason`, and MUST NOT echo the policy; each decision SHOULD be audited. An empty `restricted` policy MUST fail closed; a resolver outage SHOULD fail open.
79
+ A refusal MUST carry `policies.errorCode` (default `provider_policy_denied`; any other value is a vendor code), SHOULD carry `reason`, and MUST NOT echo the policy. Each decision SHOULD be audited. A resolver outage SHOULD fail open.
83
80
 
84
81
  A host advertising `promptPrefixCache` MAY honor `cachePrefixId` per routed provider, and otherwise MUST ignore it. It MUST key the cache by (authenticated tenant, `cachePrefixId`) and MUST NOT persist prompt or response substrings keyed by it. The envelope and `provider.usage` token counts MUST match on hit and miss, and on replay.
85
82
 
86
83
  ## `agentRuntime`
87
84
 
88
- A host advertising `agentRuntime` MUST expose `spawn`, `delegate`, `consensus` and `messageSend`, and MUST satisfy `agents.manifestRuntime`, which advertising it implies.
85
+ A host advertising `agentRuntime` MUST expose `spawn`, `delegate`, `consensus` and `messageSend`. Advertising it implies `agents.manifestRuntime`, which the host MUST satisfy.
89
86
 
90
87
  ## `mcp`
91
88
 
92
89
  A host advertising `mcp.client` MUST expose to pack code `ctx.mcp.callTool`, `listTools`, `readResource` and `serverHealth`, each against a host-configured `serverId` at the revision `mcp` negotiates.
93
90
 
94
- Each rejects only for an unknown `serverId` (`not_found`), an MCP error response (carried unaltered), or a transport failure.
91
+ Each rejects only for an unknown `serverId` (`not_found`), an MCP error response (carried unaltered), or a transport failure (`upstream_unavailable`).
95
92
 
96
93
  - `callTool` MUST resolve to the server's `CallToolResult` unaltered (`content[]`, `structuredContent`, `isError`, `_meta`), including when `isError` is true. The host handles an `InputRequiredResult` itself and never returns one.
97
94
  - `listTools` MUST resolve to one `ListToolsResult` page unaltered, `outputSchema`, `annotations`, `nextCursor`, `ttlMs` and `cacheScope` included, and MUST forward a pack's `cursor`.
98
95
  - `readResource` resolves to the `ReadResourceResult` unaltered.
99
- - `serverHealth` MUST report `reachable`, `unreachable` or `incompatible` from a `server/discover` probe no older than its `ttlMs`, with the `DiscoverResult` when one was received. It MUST NOT report a connection or session state, which MCP 2026-07-28 does not have.
96
+ - `serverHealth` MUST report `reachable`, `unreachable` or `incompatible` from a `server/discover` probe no older than its `ttlMs`, with the `DiscoverResult` when one was received. It MUST NOT report a connection or session state.
100
97
 
101
98
  ## `secrets`
102
99
 
103
100
  A host advertising `secrets` MUST resolve secrets to opaque references. Raw key material MUST NOT appear in any event, log, trace, prompt, error, export or screenshot, and the host MUST test this before exposing BYOK.
104
101
 
105
- - `scopes` lists the storage scopes it implements; a client MUST tolerate any subset. `resolution` is `host-managed`.
106
- - A host offering `resolveInPack` MUST expose `ctx.secrets.resolve({ ref, purpose })`, returning `plaintext` and optional `expiresAt` and `rotatedAt`.
102
+ `scopes` lists the storage scopes the host implements, and a client MUST tolerate any subset. `resolution` is `host-managed`.
107
103
 
108
- For `resolveInPack`, the host:
104
+ A host offering `resolveInPack` MUST expose `ctx.secrets.resolve({ ref, purpose })`, returning `plaintext` and optional `expiresAt` and `rotatedAt`. It:
109
105
 
110
- - MUST keep the plaintext out of events, spans, logs, snapshots and replay state; a replay re-resolves it, and SHOULD record only `ref`, `purpose` and time;
111
- - MUST resolve `ref` only to a credential the calling run may read, fail a `ref` from another workspace, and never substitute another credential.
106
+ - MUST keep the plaintext out of events, spans, logs, snapshots and replay state. A replay re-resolves it, and the host SHOULD record only `ref`, `purpose` and time;
107
+ - MUST resolve `ref` only to a credential the calling run may read, fail a `ref` from another workspace, and never substitute another credential;
108
+ - MUST reject a missing, revoked or expired secret with `credential_not_found`, a denied one with `credential_forbidden`, and an exhausted quota with `rate_limited`.
112
109
 
113
- A pack MUST pass a non-empty `purpose`, which the host audits. It MUST NOT log the plaintext, keep it past the consuming call, or pass it to any other `ctx` method, and MUST treat it as run input that may differ between runs.
110
+ A pack MUST pass a non-empty `purpose`, which the host audits. The pack MUST NOT log the plaintext, keep it past the consuming call, or pass it to any other `ctx` method, and MUST treat it as run input that may differ between runs.
114
111
 
115
112
  ## `modelCapabilities`
116
113
 
117
- `advertised` lists the capability identifiers the active model offers; a host-private identifier MUST be prefixed `x-host-<host>-`.
114
+ `modelCapabilities.advertised` lists the capability identifiers the active model offers; a host-private identifier MUST be prefixed `x-host-<host>-`.
118
115
 
119
- A host advertising `modelCapabilities` that dispatches a node declaring `requiredModelCapabilities` MUST check, then optionally substitute, then emit, then dispatch or refuse:
116
+ When a host advertising `modelCapabilities` dispatches a node that declares `requiredModelCapabilities`, it MUST check them before dispatch and act on the result:
120
117
 
121
118
  - all met: dispatch;
122
- - unmet, with a `fallbackModel` declared, `substitutionSupported` advertised, and the fallback's provider in `aiProviders.providers` with a resolvable credential: emit `model.capability-substituted` and dispatch the fallback;
123
- - otherwise: emit `model.capability-insufficient` and fail the run `capability_not_provided`.
119
+ - unmet, with a `fallbackModel` declared, `substitutionSupported` advertised, and the fallback's provider in `aiProviders.providers` with a resolvable credential: emit `model.capability-substituted`, then dispatch the fallback;
120
+ - otherwise: emit `model.capability-insufficient` and fail the run with `capability_not_provided`.
124
121
 
125
- It MUST NOT substitute silently or dispatch an unsuitable model. It MUST check a fallback's full capability set, and refuse one that falls short with `fallbackAttempted: true` rather than chain another. Checking capabilities before resolving prompts is RECOMMENDED.
122
+ The host MUST NOT substitute silently or dispatch an unsuitable model. It MUST check a fallback's full capability set, and refuse a fallback that falls short with `fallbackAttempted: true` rather than chain another. Checking capabilities before resolving prompts is RECOMMENDED.
126
123
 
127
124
  ## `scheduling`
128
125
 
129
126
  A host advertising `scheduling` starts runs from the `schedule` trigger in the forms it advertises (`cron`, `delayed`, `calendar`). It MAY do so without `queueBus`. For each schedule it MUST:
130
127
 
131
128
  - persist it so it survives a restart and fires on time;
132
- - fire once per tick, never duplicate concurrent runs;
129
+ - fire once per tick, never as duplicate concurrent runs;
133
130
  - reject a fire time beyond `maxFutureHorizon` with `schedule_horizon_exceeded`, whose `details.maxFutureHorizon` SHOULD echo the cap;
134
131
  - after missing a tick while down, either fire once on recovery or skip to the next tick, as it documents, never the whole backlog.
135
132
 
@@ -138,9 +135,9 @@ A host advertising `scheduling` starts runs from the `schedule` trigger in the f
138
135
  A host advertising `queueBus` MUST expose `ctx.queueBus.publish`, `consume`, `ack` and `nack`; `deadLetter` when it advertises `deadLetterSupported`; and `streamSubscribe` when it advertises `stream`, honoring `fromBeginning` only under `stream.fromBeginning`. It MAY use any of its advertised `backends`.
139
136
 
140
137
  - A tenant's consumer MUST NOT receive another tenant's messages, even on the same topic.
141
- - `ack` MUST remove a message, `nack` MUST return it for redelivery, and `deadLetter` MUST route it to the configured dead-letter queue. That queue holds messages; the `deadLetter` family holds runs ([runs.md](runs.md)).
138
+ - `ack` MUST remove a message, `nack` MUST return it for redelivery, and `deadLetter` MUST route it to the configured dead-letter queue, which holds messages, not runs ([runs.md](runs.md) §Dead letters).
142
139
  - A workflow triggered by a queue consume MUST get one run per inbound message, with no batching or skipping.
143
- - The wire shape MUST NOT vary by backend.
140
+ - The wire shape MUST NOT vary by backend. An unknown topic or expired delivery token rejects `not_found`; an unreachable backend, `upstream_unavailable`.
144
141
 
145
142
  ## `toolHooks`
146
143
 
@@ -148,13 +145,13 @@ A host advertising `toolHooks` extends `agent.toolCalled` and `agent.toolReturne
148
145
 
149
146
  - **`prePostEvents`.** The host MUST set `argsHash`, `principal` and `transport` on the call, and `status` and `durationMs` on the return. `argsHash` is SHA-256 over the RFC 8785 canonical arguments with secrets already redacted. A non-agent egress uses the principal `core.system`. `durationMs` is re-emitted verbatim on replay or fork, never recomputed.
150
147
  - **`perToolAuthorization`.** Before invoking, the host MUST check the principal's scopes against the tool's `requiredScopes`. If one is missing or cannot be evaluated, it MUST NOT invoke, MUST emit `agent.toolReturned` with `status: forbidden`, and MUST answer `403 forbidden` with `details.scope: "tool"`, `toolName` and `requiredScopes`.
151
- - **`perToolRateLimit`.** The host MUST keep a token bucket per `(principal, toolName)`. When it is empty the host MUST NOT invoke, and emits `status: rate_limited` and answers `429 rate_limited` with `details.scope: "tool"`.
148
+ - **`perToolRateLimit`.** The host MUST keep a token bucket per `(principal, toolName)`. When the bucket is empty, the host MUST NOT invoke; it emits `status: rate_limited` and answers `429 rate_limited` with `details.scope: "tool"`.
152
149
 
153
- A host MAY refuse an allowlisted tool at loop start. That return has no call: the host MUST synthesize its `callId` (a stable derivation is RECOMMENDED), MAY omit `causationId`, and MUST NOT invent an `agent.toolCalled`. A consumer MUST tolerate an unpaired `forbidden` or `rate_limited` return.
150
+ A host MAY refuse an allowlisted tool at loop start. Such a return has no call: the host MUST synthesize its `callId` (a stable derivation is RECOMMENDED), MAY omit `causationId`, and MUST NOT invent an `agent.toolCalled`. A consumer MUST tolerate an unpaired `forbidden` or `rate_limited` return.
154
151
 
155
152
  ## `httpClient`
156
153
 
157
- A host advertising `httpClient` MUST advertise `ssrfGuard: true` and a positive `maxResponseBodyBytes`. Before connecting it MUST resolve the target, reject loopback, RFC 1918, link-local and cloud-metadata addresses, and pin the resolved address for the connection (invariant `http-client-ssrf-guard`). `methods` lists the HTTP methods it accepts.
154
+ A host advertising `httpClient` MUST advertise `ssrfGuard: true` and a positive `maxResponseBodyBytes`. Before connecting it MUST resolve the target, reject loopback, RFC 1918, link-local and cloud-metadata addresses, and pin the resolved address for the connection (invariant `http-client-ssrf-guard`). A refused target is `egress_denied`, `reason: ssrf-blocked`; an unreachable one, `upstream_unavailable`. `methods` lists the HTTP methods it accepts.
158
155
 
159
156
  A host MAY expose `ctx.http.safeFetch(url, init?)` to pack code under `safeFetch`. It then:
160
157
 
@@ -172,7 +169,7 @@ An egress is `allowed` only when the address guard and the audience check both p
172
169
 
173
170
  ## `workspace`
174
171
 
175
- A host advertising `workspace` keeps agent files (`schemas/v2/workspace-file.schema.json`) scoped to one `{tenant, workspace}`; no protocol path is defined for them. The host:
172
+ A host advertising `workspace` keeps agent files (`schemas/v2/workspace-file.schema.json`) scoped to one `{tenant, workspace}`; no protocol path serves them. The host:
176
173
 
177
174
  - MUST make each write atomic, bumping `version`, and emit `workspace.updated` on each write or delete; a versioned delete leaves a tombstone;
178
175
  - MUST refuse a stale `If-Match` etag with `409 workspace_conflict` (`details.currentVersion`), and content over `maxFileBytes` with `workspace_too_large`;
@@ -185,7 +182,7 @@ A workflow calling `ctx.workspace` MUST NOT register on a host without the famil
185
182
 
186
183
  ## `memory`
187
184
 
188
- A host advertising `memory` serves agent memory (`schemas/v2/memory-entry.schema.json`) to pack code as `ctx.memory`; no protocol path exists. `list` returns `[]` for an unknown ref and `get` returns `null`; writes are host-internal, and a read-only host sets `writable: false`.
185
+ A host advertising `memory` serves agent memory (`schemas/v2/memory-entry.schema.json`) to pack code as `ctx.memory`; no protocol path serves it. `list` returns `[]` for an unknown ref and `get` returns `null`; writes are host-internal, and a read-only host sets `writable: false`.
189
186
 
190
187
  - **Refs.** `memoryRef` is opaque; a host MUST NOT assume another host's ref resolves. A node MUST guard `ctx.memory`, which may be undefined.
191
188
  - **Tenant isolation.** A ref MUST resolve to one tenant's entries, whatever the caller's permissions. A malformed ref (traversal, embedded null, oversize) MUST return `[]` or `null`. An adapter sharing a store MUST gate on the ref's shape, not trust the store. An adapter error MUST NOT carry entry data.
@@ -194,9 +191,11 @@ A host advertising `memory` serves agent memory (`schemas/v2/memory-entry.schema
194
191
  - **Long-term.** A host whose `agents.memoryBackends` includes `long-term` MUST honor isolation, redaction and expiry end to end. A validator MUST NOT look for `memoryBackends` under `memory`.
195
192
  - **`search`** advertises query `modes` beyond `list`. **`retention.forget`** is a tenant-scoped delete-by-subject of live memory only; replay reads the recorded snapshot and the log is untouched.
196
193
  - **`attribution`.** Under `emitsWriteEvents: true` the host MUST emit a content-free `memory.written` for every memory write a run makes; otherwise a consumer MUST tolerate its absence.
197
- - **`injectionBudget`** makes `list` honor `tokenBudget`, in `tokenCounter` units; otherwise it is ignored. The host MUST return a prefix of the ranked list within budget, omitting, never truncating, an entry that alone exceeds it, and with `limit` MUST honor whichever yields fewer. `rank: relevance` MUST carry `query` and requires `search` mode `semantic`; otherwise a host MUST reject it or fall back, as documented, to `recency` (the default), never fabricate a ranking. Ranking MUST run over the redacted, single-tenant set.
194
+ - **`injectionBudget`** makes `list` honor `tokenBudget`, in `tokenCounter` units; without it the budget is ignored. The host MUST return a prefix of the ranked list within budget, omitting (never truncating) an entry that alone exceeds it. With `limit` as well, it MUST honor whichever yields fewer entries.
195
+ - **Ranking.** `rank: relevance` MUST carry `query` and requires `search` mode `semantic`; otherwise a host MUST reject it or fall back, as documented, to `recency` (the default), and never fabricate a ranking. Ranking MUST run over the redacted, single-tenant set.
198
196
  - **`compaction`** (`trigger: host-managed`) emits `memory.compacted`. Derived content MUST pass the same redaction as a fresh `put`. A client MUST NOT infer compaction or distillation from entry counts.
199
- - **`distillation`** is budgeted compaction ([runs.md](runs.md)); an absent budget MUST default to `maxTokenBudget`, counting input and output. A run MUST read the ref's snapshot, MUST NOT re-expose a redacted secret at any recursion level, and MUST write an immutable, addressable archive, byte-stable per source set and budget, kept for `archiveRetention`. Under `indexEmitted` it updates `MEMORY-INDEX.json` (a `.md` sibling MAY accompany it). It emits `memory.compacted` with `distillation` and `trigger: host-managed`. Tenant isolation covers archive and index.
197
+ - **`distillation`** is budgeted compaction ([runs.md](runs.md) §`distillation` section); an absent budget MUST default to `maxTokenBudget`, counting input and output. It emits `memory.compacted` with `distillation` and `trigger: host-managed`, and tenant isolation covers its archive and index.
198
+ - **Distillation runs.** A distillation run MUST read the ref's snapshot, MUST NOT re-expose a redacted secret at any recursion level, and MUST write an immutable, addressable archive, byte-stable per source set and budget, kept for `archiveRetention`. Under `indexEmitted` it updates `MEMORY-INDEX.json`, and a `.md` sibling MAY accompany it.
200
199
  - **Degraded agents.** When an agent's `memoryShape` needs a dimension the host lacks, its inventory entry MUST set `memoryDegraded` and `degradedMemoryDimensions`; the agent MAY still dispatch. A `role: skill` manifest MUST keep `memoryShape` scratchpad-only, enforced by schema.
201
200
 
202
- *Sources: RFCs 0004, 0012, 0017, 0027, 0028, 0029, 0031, 0048, 0052, 0055, 0057, 0059, 0062, 0064, 0067, 0076, 0079, 0080, 0091, 0105, 0106, 0108, 0113, 0116, 0121, 0131, 0144.*
201
+ *Sources: RFCs 0004, 0012, 0017, 0027, 0028, 0029, 0031, 0048, 0052, 0055, 0057, 0059, 0062, 0064, 0067, 0076, 0079, 0080, 0091, 0105, 0106, 0108, 0113, 0116, 0121, 0131, 0144, 0228.*
@@ -5,7 +5,7 @@
5
5
 
6
6
  ## Why this exists
7
7
 
8
- A host renders some text for humans: interrupt prompts, error messages, extension UI strings. This document states how a client asks for a locale and how a host answers. Machine-readable identifiers stay ASCII, dates and times stay ISO 8601, and number, currency and text-direction formatting are out of scope.
8
+ A host renders some text for humans: interrupt prompts, error messages, extension UI strings. A client asks for a locale and the host answers with the one it used. Machine-readable identifiers stay ASCII and dates and times stay ISO 8601; number, currency and text-direction formatting are out of scope.
9
9
 
10
10
  ## Language tags
11
11
 
@@ -20,7 +20,7 @@ A client MAY send `Accept-Language` ([RFC 9110 §12.5.4](https://www.rfc-editor.
20
20
 
21
21
  A host:
22
22
 
23
- - MUST parse the header without failing the request. A malformed value MUST NOT cause `400`; the host uses its default locale and proceeds.
23
+ - MUST parse the header without failing the request. A malformed value MUST NOT cause `400`; the host proceeds in its default locale.
24
24
  - MUST NOT reject a request because it does not support the requested locale.
25
25
  - MUST honor q-values: the highest-q locale it supports wins, and ties go to request order.
26
26
  - MUST NOT infer a locale from the request body to override the header, which is authoritative.
@@ -47,7 +47,7 @@ A host MAY localize an error `message`, and MAY then add `details.locale`.
47
47
 
48
48
  ## The `i18n` record
49
49
 
50
- A host that negotiates locale SHOULD advertise `i18n`. A host advertising it honors `Accept-Language` on every protected route; a host that omits it serves one locale.
50
+ A host that negotiates locale SHOULD advertise `i18n`. A host advertising it honors `Accept-Language` on every protected route; a host that omits it serves one locale, its default, and ignores the header.
51
51
 
52
52
  - **`defaultLocale`** — the locale returned when no `Accept-Language` entry matches `supportedLocales`; `"en"` when omitted.
53
53
  - **`supportedLocales`** — the locales the host can return for human-facing text. It MUST contain `defaultLocale`, and its order carries no meaning.
@@ -59,16 +59,16 @@ Locale is chosen at request time. `Content-Language` is request-scoped and not r
59
59
 
60
60
  ## Localized content
61
61
 
62
- A host advertising `content` serves authored pages (`schemas/v2/localized-content-*.schema.json`) on the `/content/*` operations. A section is one record, base `data` plus sparse `localizations`, never one per locale.
62
+ A host advertising `content` serves authored pages (`schemas/v2/localized-content-*.schema.json`) on the `/content/*` operations. A section is one record, base `data` plus sparse `localizations`, never one record per locale.
63
63
 
64
- - A host advertising `content` MUST advertise `i18n`, with `content.baseLocale` equal to `i18n.defaultLocale` and every locale in `baseLocale` ∪ `supportedLocales` also in `i18n.supportedLocales`. It SHOULD advertise only locales it delivers, in the case §Language tags recommends.
65
- - Delivery negotiates locale only by `Accept-Language` (§Fallback); a `?locale=` parameter is not defined. A section negotiated to `baseLocale` resolves to `data`; otherwise to `data` shallowly overlaid by the first `localizations` entry for the exact tag, its `ll-Ssss` script family, or its language, else `data` alone. Every host MUST resolve identically.
66
- - A write names its locale in the body: `baseLocale` upserts `data`, any other its overlay.
67
- - Publication is atomic across locales. Public delivery MUST serve published content only and MUST NOT cache a draft in a shared entry.
68
- - Every read and write MUST be tenant-scoped; another tenant's id MUST get the same `404` as a missing one. Anonymous tenant resolution is host-defined, so identical resolution holds per resolved `(tenant, locale)`.
64
+ - **Advertisement.** A host advertising `content` MUST advertise `i18n`, with `content.baseLocale` equal to `i18n.defaultLocale` and every locale in `baseLocale` ∪ `supportedLocales` also in `i18n.supportedLocales`. It SHOULD advertise only locales it delivers, in the case §Language tags recommends.
65
+ - **Delivery.** Locale is negotiated only by `Accept-Language` (§Fallback); there is no `?locale=` parameter. A section negotiated to `baseLocale` resolves to `data`. Any other section resolves to `data` shallowly overlaid by the first `localizations` entry for the exact tag, its `ll-Ssss` script family, or its language, else to `data` alone. Every host MUST resolve identically.
66
+ - **Writes.** A write names its locale in the body: `baseLocale` upserts `data`, and any other locale upserts its overlay.
67
+ - **Publication** is atomic across locales. Public delivery MUST serve published content only and MUST NOT cache a draft in a shared entry.
68
+ - **Tenancy.** Every read and write MUST be tenant-scoped, and another tenant's id MUST get the same `404` as a missing one. Anonymous tenant resolution is host-defined, so identical resolution holds per resolved `(tenant, locale)`.
69
69
 
70
70
  ## Clients
71
71
 
72
- A client that wants localized content MUST check discovery for `i18n` before sending `Accept-Language`. A host that does not advertise it ignores the header and returns its default locale.
72
+ A client that wants localized content MUST check discovery for `i18n` before sending `Accept-Language`.
73
73
 
74
74
  *Sources: RFCs 0103, 0206.*
@@ -5,7 +5,7 @@
5
5
 
6
6
  ## Why this exists
7
7
 
8
- A retried request MUST NOT create a second run, and a retried node MUST NOT issue a second external effect. A host MUST implement Layer 1 for every mutating endpoint; a host that advertises `idempotency` MUST implement Layer 2 for every node executor that performs an external side effect ([security-defaults.md](security-defaults.md)).
8
+ A retried request MUST NOT create a second run, and a retried node MUST NOT issue a second external effect. A host MUST implement Layer 1 for every mutating endpoint; a host advertising `idempotency` MUST implement Layer 2 for every node executor that performs an external side effect ([security-defaults.md](security-defaults.md)).
9
9
 
10
10
  ## Layer 1: `Idempotency-Key`
11
11
 
@@ -37,7 +37,7 @@ The code's registry `retriable: false` means not retryable without waiting.
37
37
 
38
38
  ## Layer 2: effect identity
39
39
 
40
- Layer 2 is bound by advertising `idempotency` ([security-defaults.md](security-defaults.md)). Its unit is the **effect**, identified once and stable across every transport or provider retry.
40
+ Layer 2's unit is the **effect**, identified once and stable across every transport or provider retry.
41
41
 
42
42
  - **Keying.** An effect MUST be keyed on its business identity (`keying: business-identity`): derived from the business operation, stable across every entry point, containing no `runId`, `nodeId` or ordinal. The activity recipe (`keying: activity-recipe`: tenant, run, node, ordinal, `providerKey`) is the fallback for a provider with no business key.
43
43
  - **Attempts.** The retry counter MUST NOT participate in the identity. Two distinct logical invocations MUST receive different identities.
@@ -62,7 +62,7 @@ Layer 1 deduplicates the caller's request; Layer 2 deduplicates the run's effect
62
62
 
63
63
  ## Multi-region
64
64
 
65
- `multiRegion` and `crossRegion` are the two region facets of `idempotency`, and they claim different things.
65
+ The two region facets of `idempotency`, `multiRegion` and `crossRegion`, claim different things.
66
66
 
67
67
  - `crossRegion` names the host's deployment posture for this axis and MUST be held constant for the life of an advertisement.
68
68
  - `multiRegion` is the behavioural claim. When a host advertises it, both layers MUST hold across regions: an `Idempotency-Key` replayed into a second region MUST resolve to the first region's response rather than starting new work, and effect identity MUST collapse a duplicate effect wherever it is observed.
@@ -46,13 +46,15 @@ An end user reaching the host through an A2A peer is `kind: anonymous`, `lane: a
46
46
 
47
47
  ### 1.5 `anonymousActor`
48
48
 
49
- A host advertising `anonymousActor` MUST give a run dispatched through a public agent surface, an operator-configured entry point for unauthenticated callers, an anonymous subject.
49
+ A public agent surface is an operator-configured entry point for unauthenticated callers. A host advertising `anonymousActor` MUST give a run dispatched through one an anonymous subject.
50
50
 
51
51
  - **The subject.** Its `subjectId` MUST be host-minted, opaque, PII-free and scoped to one surface session. It MUST NOT correlate two sessions or resolve to another subject, workspace or session.
52
- - **Authority.** Only the surface's explicit tool allowlist. A host MUST NOT resolve a role, scope or default tool baseline for it, or widen it within a session.
52
+ - **Authority.** The subject's only authority is the surface's explicit tool allowlist. A host MUST NOT resolve a role, scope or default tool baseline for it, or widen it within a session.
53
53
  - **`failClosed`.** A call whose grant is absent, unresolvable or errors MUST be denied.
54
- - **`tiers`.** A host MUST list only tiers it enforces. `read`: tenant-scoped tools with no egress and no secret or BYOK reach. `bounded-write-egress`: writes or egress behind a control, over the SSRF-guarded egress path, attaching a credential only when its audience covers the destination and policy permits anonymous use.
55
- - **`writeEgressControls`.** REQUIRED iff `bounded-write-egress` is listed, else absent: `hitl` or `rate-limit-session-cap` (a hard rate limit plus a per-session action cap).
54
+ - **`tiers`.** A host MUST list only tiers it enforces:
55
+ - `read` — tenant-scoped tools with no egress and no secret or BYOK reach;
56
+ - `bounded-write-egress` — writes or egress behind a control, over the SSRF-guarded egress path, attaching a credential only when its audience covers the destination and policy permits anonymous use.
57
+ - **`writeEgressControls`.** REQUIRED when `bounded-write-egress` is listed, and absent otherwise: `hitl` or `rate-limit-session-cap` (a hard rate limit plus a per-session action cap).
56
58
  - **Audit.** Every anonymous tool call MUST emit `authorization.decided` carrying no PII or credential. A denial's `reason` is `anon-not-granted`, `anon-write-ungated` or `anon-egress-denied`.
57
59
 
58
60
  `listTools` scoped to the subject reads the effective grant.
@@ -101,13 +103,13 @@ Every lane MUST name its trust root as `subject.issuer` and MUST advertise it in
101
103
 
102
104
  #### `exp-only`
103
105
 
104
- `exp-only` names a host that honors `exp` and never re-checks revocation: it consults no introspection endpoint, no userinfo endpoint, no revocation list and no host-side epoch or `validAfter` record. A credential revoked at the trust root is accepted until its own `exp`, so the window is the only bound, and it is enforced.
106
+ `exp-only` names a host that honors `exp` and never re-checks revocation: it consults no introspection endpoint, userinfo endpoint, revocation list, or host-side epoch or `validAfter` record. A credential revoked at the trust root is accepted until its own `exp`, so the enforced window is the only bound.
105
107
 
106
- - A host advertising `exp-only` on a lane MUST refuse a credential presented on that lane with `401 credential_lifetime_exceeded` when **either** `exp − iat` (total lifetime) **or** `exp − now` (remaining lifetime) exceeds the advertised `revocationWindowSeconds`. Both bounds matter: `exp − iat` alone admits a ten-year token minted ten years ago; `exp − now` alone admits a fresh ten-year token in its ninth year.
107
- - A credential carrying no `iat` MUST be refused with the same code, because the first bound cannot be evaluated without it (§2.1 fail-closed).
108
+ - A host advertising `exp-only` on a lane MUST refuse a credential presented on that lane with `401 credential_lifetime_exceeded` when **either** `exp − iat` (total lifetime) **or** `exp − now` (remaining lifetime) exceeds the advertised `revocationWindowSeconds`.
109
+ - A credential carrying no `iat` MUST be refused with the same code (§2.1 fail-closed).
108
110
  - A host that cannot enforce both bounds MUST NOT advertise `exp-only`.
109
111
  - `exp-only` SHOULD be advertised with a window of one hour or less. No maximum is set.
110
- - `exp-only` MUST NOT be advertised on the `api-key` or `session` lane, where the host issued the credential and controls revocation itself. `auth.schema.json` refuses that pairing (invariant `lane-exp-only-lifetime-bounded`).
112
+ - `exp-only` MUST NOT be advertised on the `api-key` or `session` lane; `auth.schema.json` refuses that pairing (invariant `lane-exp-only-lifetime-bounded`).
111
113
 
112
114
  ### 2.3 Minimum assurance
113
115
 
@@ -158,7 +160,7 @@ The record and both `SubjectRef`s are closed; `a`, `b`, `keyClass`, `issuer`, `t
158
160
 
159
161
  Deactivation sets `deniedAt`; the SAML decision path MUST consult it (the leaver contract). The link is a reference, not a merge: nothing rewrites a subject already stamped on a run.
160
162
 
161
- Advertising both `saml` and `scim` lanes implies the link contract; there is no `auth.subjectLinking` flag. Lanes stay separate facets, with no single "enterprise identity" profile. The `auth.subjectLinkKey` facet (`opaque-idp | configured-immutable`) names the key class the host forms links under.
163
+ Advertising both `saml` and `scim` lanes implies the link contract. The `auth.subjectLinkKey` facet (`opaque-idp | configured-immutable`) names the key class the host forms links under.
162
164
 
163
165
  ## 4. Resume tokens
164
166
 
@@ -5,7 +5,7 @@
5
5
 
6
6
  ## Why this exists
7
7
 
8
- This document covers the two embedded protocols, A2A and MCP: how a host advertises them, how a version is negotiated, and what every negotiation leaves behind. Capability shapes are in [capabilities.md](capabilities.md); the peer identity is the Subject of [identity.md](identity.md).
8
+ A2A and MCP are embedded protocols: a host advertises them, negotiates a version, and records every negotiation. Capability shapes are in [capabilities.md](capabilities.md); the peer identity is the Subject of [identity.md](identity.md).
9
9
 
10
10
  ## REST is the wire
11
11
 
@@ -48,7 +48,7 @@ When no `A2A-Version` header is present, a host MUST serve the agent card of `pr
48
48
  "negotiated": "…" | null, "outcome": "accepted" | "downgraded" | "refused", "reason": "…" }
49
49
  ```
50
50
 
51
- The event is content-free: `peer` MUST be a digest of the peer origin, never the origin in clear. The event is the normative witness of the invariants `a2a-version-no-silent-downgrade` and `mcp-version-no-silent-downgrade`; the seams profile ([conformance.md](conformance.md)) drives the exchange and captures the wire leg.
51
+ The event is content-free: `peer` MUST be a digest of the peer origin, never the origin in clear. The event is the normative witness of the invariants `a2a-version-no-silent-downgrade` and `mcp-version-no-silent-downgrade`.
52
52
 
53
53
  **The refresh SLA.** A host MUST re-evaluate its advertised `versions[]` / `revisions[]` against the upstream registry within the window its `refreshedAt` declares, and that window MUST NOT exceed 90 days. An advertisement older than its window is non-conformant.
54
54
 
@@ -78,7 +78,7 @@ What the map does not name is opaque: it MUST round-trip where upstream requires
78
78
 
79
79
  **A2A error details.** A host:
80
80
 
81
- - on a 1.0 `JSONRPC` interface, MUST make an A2A error's `error.data` an array of objects each carrying `@type`, including exactly one `type.googleapis.com/google.rpc.ErrorInfo` whose `reason` is the map row's `reason` and whose `domain` is `a2a-protocol.org` (a tightening of A2A §9.5's SHOULD);
81
+ - on a 1.0 `JSONRPC` interface, MUST make an A2A error's `error.data` an array of objects each carrying `@type`, including exactly one `type.googleapis.com/google.rpc.ErrorInfo` whose `reason` is the map row's `reason` and whose `domain` is `a2a-protocol.org`;
82
82
  - on an `HTTP+JSON` interface, MUST answer A2A §11.6's `google.rpc.Status`;
83
83
  - MUST NOT answer with the OpenWOP error envelope on an interface URL the card lists, including a refusal before dispatch;
84
84
  - MUST NOT vary `TaskNotFoundError` details between an unknown task and an unreadable one, apart from an echo of the requested id;
@@ -113,7 +113,7 @@ A host MAY serve the MCP Tasks extension `io.modelcontextprotocol/tasks` (revisi
113
113
  A host advertising `a2a.pushNotifications` treats each push as a webhook egress: webhooks.md §Egress binds at delivery time as well as registration, a `3xx` is a failed delivery, and the push credential is bound as security-defaults.md §"Onward hops" states.
114
114
 
115
115
  - A host MUST attempt each push at least once; a host that retries follows `webhooks.md` `retryPolicy` semantics.
116
- - The body is an A2A 1.0 `StreamResponse` sent as `application/a2a+json`, carrying `Authorization: {scheme} {credentials}` from the config. When only `token` is set, a host SHOULD send it as `Authorization: Bearer <token>` (A2A v1.0.1 leaves its carriage undefined).
116
+ - The body is an A2A 1.0 `StreamResponse` sent as `application/a2a+json`, carrying `Authorization: {scheme} {credentials}` from the config. When only `token` is set, a host SHOULD send it as `Authorization: Bearer <token>`.
117
117
  - A host MUST NOT add an OpenWOP signature.
118
118
  - Push dead-letters are not visible to A2A clients; a client recovers with `GetTask`.
119
119
  - A `replay` fork MUST NOT push re-emitted history, and no fork inherits a source run's push configs.
@@ -144,11 +144,9 @@ The card MUST NOT carry anything the inventory entry may not, and does not repla
144
144
 
145
145
  gRPC is not part of the core wire. Its document lives at `spec/v2/ext/grpc-transport/` with `witness: unwitnessable` and `adoption: none`; its requirements are SHOULDs of that extension.
146
146
 
147
- - A host MUST NOT advertise a `grpc` capability block — an unwitnessable family is not advertisable.
147
+ - A host MUST NOT advertise a `grpc` capability block.
148
148
  - `api/v2/openapi.yaml` and the AsyncAPI document are the only canonical API descriptions.
149
149
 
150
- The extension re-enters core only by an additive RFC that generates the proto from `spec/v2/declaration.json` and lands a suite client.
151
-
152
150
  ## Trace context
153
151
 
154
152
  A host that propagates W3C Trace Context:
@@ -160,6 +158,6 @@ A receiver prefers the in-message value, ignores a malformed one, and MUST NOT d
160
158
 
161
159
  ## Threat model
162
160
 
163
- `SECURITY/threat-model-interop.md` is the threat model for this document; its invariants are rows of `SECURITY/invariants.yaml`. Peer identity and authorization at the boundary are governed by security-defaults.md; a peer MUST NOT gain authority the caller's Subject does not hold.
161
+ `SECURITY/threat-model-interop.md` is the threat model for this document. Peer identity and authorization at the boundary are governed by security-defaults.md; a peer MUST NOT gain authority the caller's Subject does not hold.
164
162
 
165
163
  *Sources: RFCs 0175, 0198, 0207, 0208, 0211, 0214.*