@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.
- package/CORPUS-STAMP.json +49 -47
- package/api/seams-v2.yaml +1 -1
- package/api/v2/asyncapi.yaml +1 -1
- package/api/v2/openapi.yaml +31 -122
- package/package.json +1 -1
- package/schemas/v2/agent-ref.schema.json +2 -3
- package/schemas/v2/budget-policy.schema.json +2 -3
- package/schemas/v2/capabilities.schema.json +42 -38
- package/schemas/v2/debug-bundle.schema.json +2 -2
- package/schemas/v2/error-envelope.schema.json +282 -4
- package/schemas/v2/node-pack-manifest.schema.json +2 -2
- package/schemas/v2/prompt-ref.schema.json +2 -3
- package/schemas/v2/prompt-template.schema.json +3 -3
- package/schemas/v2/run-event-payloads.schema.json +5 -5
- package/spec/v1/alias-detectors.json +25 -1
- package/spec/v1/core-standard-manifest.json +2 -2
- package/spec/v1/deprecations.json +89 -1
- package/spec/v1/gaps.json +42 -8
- package/spec/v1/migrations.json +57 -1
- package/spec/v2/core/artifact-type-packs.md +5 -1
- package/spec/v2/core/capabilities.md +20 -22
- package/spec/v2/core/conformance.md +24 -7
- package/spec/v2/core/errors.md +17 -119
- package/spec/v2/core/events.md +7 -7
- package/spec/v2/core/execution.md +56 -16
- package/spec/v2/core/host-services.md +57 -58
- package/spec/v2/core/i18n.md +10 -10
- package/spec/v2/core/idempotency.md +3 -3
- package/spec/v2/core/identity.md +11 -9
- package/spec/v2/core/interop.md +6 -8
- package/spec/v2/core/interrupt.md +8 -8
- package/spec/v2/core/oauth.md +4 -4
- package/spec/v2/core/packs.md +11 -5
- package/spec/v2/core/persistence.md +11 -13
- package/spec/v2/core/portability.md +4 -4
- package/spec/v2/core/replay.md +8 -10
- package/spec/v2/core/runs.md +20 -7
- package/spec/v2/core/security-defaults.md +0 -2
- package/spec/v2/core/storage.md +18 -20
- package/spec/v2/core/versioning.md +4 -9
- package/spec/v2/core/webhooks.md +13 -8
- package/spec/v2/declaration.json +3 -3
- package/spec/v2/errors.json +206 -2
- package/spec/v2/facets/agents.schema.json +367 -0
- package/spec/v2/facets/aiProviders.schema.json +2 -2
- package/spec/v2/generated/error-codes.md +130 -0
- package/spec/v2/release.json +2 -2
- 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.
|
|
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
|
-
|
|
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`.
|
|
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`
|
|
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
|
|
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
|
|
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)
|
|
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
|
|
39
|
-
2. under `agentBindings`, the `config.agentId`
|
|
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
|
-
|
|
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>`,
|
|
50
|
-
- Unless `observability` is `off`, the host MUST emit `prompt.composed`
|
|
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
|
|
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
|
|
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
|
|
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`
|
|
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
|
|
67
|
-
- A `media.*` envelope inlines base64 only up to `maxInlineMediaBytes` (default 256 KiB); above
|
|
68
|
-
- `imageGeneration`, `videoGeneration
|
|
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
|
|
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.
|
|
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` (
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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;
|
|
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.
|
|
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.*
|
package/spec/v2/core/i18n.md
CHANGED
|
@@ -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.
|
|
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
|
|
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
|
|
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
|
|
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`.
|
|
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
|
|
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
|
|
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
|
-
|
|
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.
|
package/spec/v2/core/identity.md
CHANGED
|
@@ -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
|
|
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.**
|
|
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
|
|
55
|
-
-
|
|
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,
|
|
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`.
|
|
107
|
-
- A credential carrying no `iat` MUST be refused with the same code
|
|
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
|
|
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
|
|
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
|
|
package/spec/v2/core/interop.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
## Why this exists
|
|
7
7
|
|
|
8
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.*
|