@openwop/spec-artifacts 2.42.6 → 2.42.7
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 +64 -63
- package/api/seams-v2.yaml +187 -322
- package/api/v2/asyncapi.yaml +83 -113
- package/api/v2/openapi.yaml +339 -724
- package/package.json +1 -1
- package/schemas/audit-verify-result.schema.json +2 -2
- package/schemas/v2/audit-verify-result.schema.json +2 -2
- package/schemas/v2/capabilities.schema.json +2 -5
- package/spec/v1/core-standard-manifest.json +2 -2
- package/spec/v1/gaps.json +171 -1
- package/spec/v2/README.md +34 -36
- package/spec/v2/core/capabilities.md +186 -119
- package/spec/v2/core/conformance.md +78 -27
- package/spec/v2/core/connection-packs.md +8 -3
- package/spec/v2/core/conversation.md +10 -4
- package/spec/v2/core/errors.md +30 -9
- package/spec/v2/core/events.md +116 -80
- package/spec/v2/core/form-content-packs.md +11 -13
- package/spec/v2/core/headers.md +22 -14
- package/spec/v2/core/host-services.md +22 -5
- package/spec/v2/core/idempotency.md +48 -36
- package/spec/v2/core/identity.md +110 -42
- package/spec/v2/core/interop.md +82 -21
- package/spec/v2/core/interrupt.md +79 -40
- package/spec/v2/core/node-pack-runtimes.md +18 -4
- package/spec/v2/core/oauth.md +40 -6
- package/spec/v2/core/overview.md +61 -33
- package/spec/v2/core/packs.md +53 -28
- package/spec/v2/core/persistence.md +61 -80
- package/spec/v2/core/replay.md +56 -42
- package/spec/v2/core/runs.md +198 -60
- package/spec/v2/core/security-defaults.md +57 -33
- package/spec/v2/core/tool-catalog.md +29 -6
- package/spec/v2/core/versioning.md +95 -49
- package/spec/v2/core/webhooks.md +59 -20
- package/spec/v2/core/workflow-chain-packs.md +16 -14
- package/spec/v2/corrections.json +17 -1
- package/spec/v2/corrections.schema.json +1 -1
- package/spec/v2/declaration.json +24 -7
- package/spec/v2/declaration.schema.json +10 -0
- package/spec/v2/errors.json +109 -2
- package/spec/v2/errors.schema.json +38 -0
- package/spec/v2/ext/README.md +36 -23
- package/spec/v2/ext/a2uiSurface/README.md +75 -89
- package/spec/v2/ext/brand/README.md +15 -15
- package/spec/v2/ext/canvas/README.md +15 -15
- package/spec/v2/ext/chat/README.md +15 -15
- package/spec/v2/ext/coordination/README.md +15 -15
- package/spec/v2/ext/dataIntegration/README.md +17 -20
- package/spec/v2/ext/entities/README.md +15 -15
- package/spec/v2/ext/grpc-transport/README.md +6 -8
- package/spec/v2/ext/kanban/README.md +15 -15
- package/spec/v2/ext/knowledge/README.md +15 -15
- package/spec/v2/ext/launchStudio/README.md +16 -16
- package/spec/v2/ext/messaging/README.md +15 -15
- package/spec/v2/ext/portability/README.md +3 -17
- package/spec/v2/ext/provider-idempotency/README.md +7 -10
- package/spec/v2/ext/restTransport/README.md +30 -19
- package/spec/v2/ext/sandbox-runtime-notes/README.md +7 -9
- package/spec/v2/ext/webResearch/README.md +16 -16
- package/spec/v2/profiles.json +6 -2
- package/spec/v2/release.json +2 -2
- package/spec/v2/surface-baseline.json +542 -2
package/spec/v2/core/replay.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Replay and Fork
|
|
2
2
|
|
|
3
|
-
> **Status: Stable
|
|
3
|
+
> **Status: Stable.**
|
|
4
4
|
> **Normative home:** `eventLog`, `replay`, `nondeterminismPolicy`.
|
|
5
5
|
|
|
6
6
|
## Why this exists
|
|
@@ -9,14 +9,23 @@
|
|
|
9
9
|
|
|
10
10
|
## The surface
|
|
11
11
|
|
|
12
|
-
A host that advertises `replay` (capabilities.md) serves `forkRun` (`api/v2/openapi.yaml`, `POST /runs/{runId}:fork`) and `getEffectSeamManifest` (`GET /host/effect-seams`).
|
|
12
|
+
A host that advertises `replay` ([capabilities.md](capabilities.md)) serves `forkRun` (`api/v2/openapi.yaml`, `POST /runs/{runId}:fork`) and `getEffectSeamManifest` (`GET /host/effect-seams`).
|
|
13
13
|
|
|
14
|
-
The
|
|
14
|
+
The `replay` facet (`spec/v2/facets/replay.schema.json`) is `{ modes[], retention?, effectSeamsManifest }`:
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
- `modes` enumerates `replay | branch` (the `forkRun` `mode` values);
|
|
17
|
+
- `effectSeamsManifest` is the constant `/host/effect-seams`.
|
|
18
|
+
|
|
19
|
+
Suppression is the only conforming replay behavior; no field turns it off, and `none` is not a value.
|
|
20
|
+
|
|
21
|
+
The request body, `fromSeq` defaults and `201` response are [runs.md §Fork](runs.md). Events with `sequence < fromSeq` are fixed history; events `>= fromSeq` are re-executed.
|
|
22
|
+
|
|
23
|
+
Refusals:
|
|
24
|
+
|
|
25
|
+
- `fromSeq` out of range — `400`.
|
|
26
|
+
- A sequence absent from the source log — `422`.
|
|
27
|
+
- A `fromSeq` greater than the sequence of the source run's terminal run event MUST be refused `422 fork_point_invalid`: the fork would inherit a terminal event and then execute. This binds only where a compensation tail follows the terminal event.
|
|
28
|
+
- Source run not visible to the caller — `404`.
|
|
20
29
|
|
|
21
30
|
## Modes
|
|
22
31
|
|
|
@@ -26,13 +35,13 @@ The request body, `fromSeq` defaults and `201` response are runs.md §Fork. Even
|
|
|
26
35
|
|
|
27
36
|
## Byte-equivalence of the prefix
|
|
28
37
|
|
|
29
|
-
The replay contract is observable-output-sequence determinism, not bit-equivalent execution
|
|
38
|
+
The replay contract is observable-output-sequence determinism, not bit-equivalent execution:
|
|
30
39
|
|
|
31
|
-
1. The events at indices `[0, fromSeq)` MUST be byte-equivalent between source and replay, modulo per-region clock fields
|
|
40
|
+
1. The events at indices `[0, fromSeq)` MUST be byte-equivalent between source and replay, modulo per-region clock fields and ULID time-component entropy when ULIDs are minted fresh. The event at `fromSeq` is governed by §Divergence.
|
|
32
41
|
2. `variables`, `channels`, and `status` of the run snapshot at each index in that range MUST be byte-equivalent.
|
|
33
42
|
3. The bytes on the wire of underlying tool and LLM calls MAY differ, provided the observable state at each index is byte-equivalent.
|
|
34
43
|
|
|
35
|
-
A host MUST cache the observable result (return value, workflow-state effects, emitted events), not merely the tool-call boundary. The cache key for LLM-calling nodes is the
|
|
44
|
+
A host MUST cache the observable result (return value, workflow-state effects, emitted events), not merely the tool-call boundary. The cache key for LLM-calling nodes is the content-addressed invocation key defined in [RFC 0041](https://github.com/openwop/openwop/blob/main/RFCS/0041-multi-agent-replay-under-nondeterminism.md); for other tool-calling nodes it MUST be content-addressable, never a host-internal sequence number or timestamp.
|
|
36
45
|
|
|
37
46
|
## Determinism caveats (`replay` mode)
|
|
38
47
|
|
|
@@ -40,23 +49,27 @@ A host MUST cache the observable result (return value, workflow-state effects, e
|
|
|
40
49
|
2. `ctx.interrupt(K)` MUST short-circuit to the persisted `interrupt.resolved` value.
|
|
41
50
|
3. `ctx.getVersion` pins from the source run are fixed history; the replay MUST take the recorded branch.
|
|
42
51
|
4. Nodes MUST consume time via `ctx.now()` where available; direct clock reads make replay non-deterministic.
|
|
43
|
-
5. Recorded-fact events such as `memory.written`
|
|
44
|
-
6. Approver eligibility recorded on a resume event is fixed history; a host MUST NOT re-resolve membership during replay
|
|
52
|
+
5. Recorded-fact events such as `memory.written` are fixed history. A replay MUST re-emit them verbatim from the log and MUST NOT regenerate their identifiers or timestamps — never a new `memoryId`. A `branch` MAY perform its own memory writes with fresh identifiers.
|
|
53
|
+
6. Approver eligibility recorded on a resume event is fixed history; a host MUST NOT re-resolve membership during replay.
|
|
45
54
|
|
|
46
55
|
## Divergence
|
|
47
56
|
|
|
48
|
-
When a replayed node produces an event different from the source at the same sequence, the host
|
|
57
|
+
When a replayed node produces an event different from the source at the same sequence, the host:
|
|
49
58
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
59
|
+
- MUST continue;
|
|
60
|
+
- MUST emit `replay.diverged` `{ originalEventId, replayEventId, divergencePoint }`;
|
|
61
|
+
- MUST surface it in `debug` stream mode and as OTel attribute `openwop.replay.diverged: true`.
|
|
62
|
+
|
|
63
|
+
Divergence codes (`spec/v2/errors.json`):
|
|
64
|
+
|
|
65
|
+
- **`replay_diverged_at_refusal`** (fork fails, `409`) — the source obtained a valid envelope and the replay a refusal, or the reverse. The host MUST NOT substitute silently; it MUST emit `replay.diverged-at-refusal` naming the node and both envelope kinds and fail the replay with this code.
|
|
66
|
+
- **`replay_source_missing`** (`node.failed` payload; the fork request still returns `201`) — a side-effecting node reached with no recorded source outcome for `(nodeId, attempt)` (§Suppression).
|
|
67
|
+
- **`replay_memory_snapshot_unavailable`** (fork refused, `409`) — the host cannot serve memory state as-of `fromSeq`. It MUST refuse rather than substitute current memory; `details.fromSeq` SHOULD name the index.
|
|
68
|
+
- **`replay_context_summary_unavailable`** (fork refused, `409`) — the host advertises `multiAgent.executionModel.contextBudget.summarization` and cannot serve, as-of `fromSeq`, a summary artifact (`context.summarized.summaryRef`) the replay would reuse. It MUST refuse rather than re-summarize; `details.fromSeq` SHOULD name the index.
|
|
56
69
|
|
|
57
70
|
## Suppression
|
|
58
71
|
|
|
59
|
-
Suppression is an obligation of the `replay` surface
|
|
72
|
+
Suppression is an obligation of the `replay` surface: advertising `replay` binds it, and a host that cannot suppress MUST NOT advertise `replay`. A relaxation is an operator setting recorded in the certification bundle (security-defaults.md).
|
|
60
73
|
|
|
61
74
|
For a fork with `mode: replay`:
|
|
62
75
|
|
|
@@ -69,53 +82,54 @@ For a fork with `mode: replay`:
|
|
|
69
82
|
|
|
70
83
|
Pure nodes and LLM calls served from the invocation log MUST re-execute live; otherwise divergence detection is vacuous.
|
|
71
84
|
|
|
72
|
-
**Fan-out.** A host that projects its log outward — webhook delivery, A2A push, outbound streams, analytics or audit sinks — MUST NOT deliver events a replay re-emits as fixed history, and a fork of either mode MUST NOT inherit its source's A2A push configs. Replay-ness MUST be read from the run, never from the event type; the fork's own log MUST still carry the re-emitted events (webhooks.md).
|
|
85
|
+
**Fan-out.** A host that projects its log outward — webhook delivery, A2A push, outbound streams, analytics or audit sinks — MUST NOT deliver events a replay re-emits as fixed history, and a fork of either mode MUST NOT inherit its source's A2A push configs. Replay-ness MUST be read from the run, never from the event type; the fork's own log MUST still carry the re-emitted events ([webhooks.md](webhooks.md)).
|
|
73
86
|
|
|
74
87
|
**Branch.** A branch re-fires effects for sequences `>= fromSeq`; those are effects the operator asked for. A host MAY suppress branch effects and MUST NOT report that as replay suppression. A host SHOULD surface the re-fire in operator-facing fork UI.
|
|
75
88
|
|
|
76
89
|
### The effect-seam manifest
|
|
77
90
|
|
|
78
|
-
A host advertising `replay` MUST publish `schemas/v2/effect-seam-manifest.schema.json`-shaped data at `GET /host/effect-seams
|
|
91
|
+
A host advertising `replay` MUST publish `schemas/v2/effect-seam-manifest.schema.json`-shaped data at `GET /host/effect-seams`: `{ manifestVersion: "1", host: { name, build }, seams[] }`, one row per outbound effect path its node runtime can reach, `{ seam, kind, guarded: true, guardedBy, branchReFires?, note? }`. The host owns the list. The suite drives one seam of each kind it can reach and observes no re-fire (`effect-seam-manifest`, [conformance.md](conformance.md)).
|
|
92
|
+
|
|
93
|
+
`kind` names the outbound wire mechanism the seam leaves the host by — not the suite's driving mechanism, and not the business purpose. It MUST be one of `http`, `smtp`, `queue`, `storage`, `provider-sdk`, `webhook-fanout`, `other`.
|
|
79
94
|
|
|
80
|
-
|
|
95
|
+
- Two seams a host guards through one code path but that leave by different mechanisms are different `kind`s; two that leave by the same mechanism for different business reasons are one.
|
|
96
|
+
- `other` is the escape for a mechanism this list does not name — raw TCP, gRPC, a filesystem write, a device SDK. A row using it MUST carry `note` naming that mechanism.
|
|
81
97
|
|
|
82
|
-
**Completeness outranks driveability.** Every outbound effect path the node runtime can reach MUST be listed, including one the suite cannot drive (typically `smtp` or `other`)
|
|
98
|
+
**Completeness outranks driveability.** Every outbound effect path the node runtime can reach MUST be listed, including one the suite cannot drive (typically `smtp` or `other`); the scenario records that one `inapplicable`, naming the mechanism. A host MUST NOT omit a seam because the suite cannot drive it, and MUST NOT relabel it as a `kind` the suite can drive. The manifest is a self-declaration whose false negatives are found by audit, not a witness.
|
|
83
99
|
|
|
84
100
|
## Replay-from-event-log internals
|
|
85
101
|
|
|
86
102
|
1. Load the source run's events with `sequence < fromSeq` through the storage boundary, where an era-`2` log is translated (persistence.md).
|
|
87
103
|
2. Fold them to a projected state.
|
|
88
104
|
3. Initialize the new run with that state, copy-on-write into its own log.
|
|
89
|
-
4. For `replay`, resolve side-effecting nodes from the source run's recorded outcomes keyed on `(sourceRunId, nodeId, attempt)`; LLM invocations additionally consult the invocation log via the
|
|
105
|
+
4. For `replay`, resolve side-effecting nodes from the source run's recorded outcomes keyed on `(sourceRunId, nodeId, attempt)`; LLM invocations additionally consult the invocation log via the content-addressed invocation key.
|
|
90
106
|
5. For `branch`, executor invocations create new invocation-log entries keyed on the new `runId`.
|
|
91
107
|
|
|
92
108
|
## Forking a v1 run
|
|
93
109
|
|
|
94
|
-
A v2 host MUST fork a run created before the cut (era `2`, persistence.md
|
|
110
|
+
A v2 host MUST fork a run created before the cut (era `2`, [persistence.md](persistence.md)). The `fork-a-v1-run` scenario witnesses this.
|
|
111
|
+
|
|
112
|
+
- The fork's prefix MUST be byte-equivalent to the *translated* parent — the parent as read through the codemap, not its stored bytes.
|
|
113
|
+
- `run.started` on the fork MUST carry the legacy Subject (`issuer: urn:openwop:legacy`, [identity.md](identity.md)) where the parent had none.
|
|
114
|
+
- A backfill of an era-`2` log is permitted only atomically per run with the original preserved, so this obligation stays checkable.
|
|
95
115
|
|
|
96
116
|
## Cross-engine ordering
|
|
97
117
|
|
|
98
|
-
When a host advertises both `idempotency.multiRegion` and
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
differ. A host that advertises one of the two and not the other keeps the
|
|
105
|
-
single-region contract above; a host that advertises neither is single-region
|
|
106
|
-
and the cross-region claim does not apply.
|
|
118
|
+
When a host advertises both `idempotency.multiRegion` and `eventLog.crossEngineOrdering`, a fork served by a region other than the one that wrote the run MUST produce the same observable state at the `fromSeq` boundary as a fork served by the writing region:
|
|
119
|
+
|
|
120
|
+
- `status`, `variables`, and the projected event log up to `fromSeq` MUST be byte-equivalent across regions.
|
|
121
|
+
- Per-region wall-clock and entropy fields in events after the boundary MAY differ.
|
|
122
|
+
|
|
123
|
+
A host that advertises only one of the two keeps the single-region contract above; a host that advertises neither is single-region and the cross-region claim does not apply.
|
|
107
124
|
|
|
108
125
|
## Retention
|
|
109
126
|
|
|
110
127
|
A host advertising `replay` MUST document retention for source snapshots, source logs, the invocation records replay depends on, and forked runs; `retention.days` MAY advertise the window. When the range `fromSeq` needs has expired, the host MUST reject the fork with `410` or `422`; `details` SHOULD carry `sourceRunId`, `fromSeq`, and the boundary.
|
|
111
128
|
|
|
112
|
-
See also: events.md, runs.md, persistence.md, security-defaults.md.
|
|
113
|
-
|
|
114
129
|
## Declared nondeterminism
|
|
115
130
|
|
|
116
|
-
`nondeterminismPolicy` is the host's statement of which nondeterministic sources it
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
a fork replays the recorded value rather than re-drawing it; a source the host neither
|
|
120
|
-
declares nor suppresses is a replay defect, not a policy choice.
|
|
131
|
+
`nondeterminismPolicy` is the host's statement of which nondeterministic sources it declares rather than suppresses. A host advertising `nondeterminismPolicy.declared` MUST record every declared source in the run's event log at the point it is read, so a fork replays the recorded value rather than re-drawing it. A source the host neither declares nor suppresses is a replay defect, not a policy choice.
|
|
132
|
+
|
|
133
|
+
See also: events.md, runs.md, persistence.md, security-defaults.md.
|
|
121
134
|
|
|
135
|
+
*Sources: RFC 0036, RFC 0039, RFC 0041, RFC 0057, RFC 0104, RFC 0111, RFC 0140, RFC 0173, RFC 0176, RFC 0194.*
|
package/spec/v2/core/runs.md
CHANGED
|
@@ -1,129 +1,267 @@
|
|
|
1
1
|
# Runs
|
|
2
2
|
|
|
3
|
-
> **Status: Stable
|
|
3
|
+
> **Status: Stable.**
|
|
4
4
|
> **Normative home:** `runList`, `limits`, `conversationPrimitive`, `dataResidency`.
|
|
5
5
|
|
|
6
6
|
## Why this exists
|
|
7
7
|
|
|
8
|
-
A run is the unit of execution, ownership and observation. This document
|
|
8
|
+
A run is the unit of execution, ownership and observation. This document covers the run surface of `api/v2/openapi.yaml`: how a run is created, read, streamed, cancelled, paused, forked and diffed. Every host projects the same snapshot shape from the same event log ([events.md](events.md)).
|
|
9
9
|
|
|
10
10
|
## Identity
|
|
11
11
|
|
|
12
|
-
Every id `$ref`s `schemas/v2/ids.schema.json` (identity.md). A `runId` is tenant-bound
|
|
12
|
+
Every id `$ref`s `schemas/v2/ids.schema.json` ([identity.md](identity.md)). A `runId` is host-minted and tenant-bound: `<tenantId>/<opaque>`.
|
|
13
|
+
|
|
14
|
+
- A caller MUST treat every id as opaque.
|
|
15
|
+
- A host MUST reject a `runId` whose tenant segment is not the caller's with `403 id_tenant_mismatch`, and MUST NOT disclose whether the run exists.
|
|
13
16
|
|
|
14
17
|
## Surface
|
|
15
18
|
|
|
16
|
-
Every operation accepts `OpenWOP-Version` (overview.md)
|
|
19
|
+
Every operation accepts `OpenWOP-Version` ([overview.md](overview.md)) and every response carries it. Every mutating operation accepts `Idempotency-Key` ([idempotency.md](idempotency.md)). Scopes are the `auth.md` vocabulary.
|
|
17
20
|
|
|
18
21
|
| Operation | Method and path | Scope | Gate |
|
|
19
22
|
| --- | --- | --- | --- |
|
|
20
23
|
| `createRun` | `POST /runs` | `runs:create` | — |
|
|
21
24
|
| `getRun` | `GET /runs/{runId}` | `runs:read` | — |
|
|
22
|
-
| `
|
|
23
|
-
| `
|
|
25
|
+
| `listRuns` | `GET /runs` | `runs:read` | `runList` |
|
|
26
|
+
| `streamRunEvents` | `GET /runs/{runId}/events` | `runs:read` | [events.md](events.md) |
|
|
27
|
+
| `pollRunEvents` | `GET /runs/{runId}/events/poll` | `runs:read` | [events.md](events.md) |
|
|
24
28
|
| `cancelRun` | `POST /runs/{runId}/cancel` | `runs:cancel` | — |
|
|
25
29
|
| `bulkCancelRuns` | `POST /runs:bulk-cancel` | `runs:cancel` | — |
|
|
26
30
|
| `pauseRun` | `POST /runs/{runId}:pause` | `runs:cancel` | — |
|
|
27
31
|
| `resumeRun` | `POST /runs/{runId}:resume` | `runs:cancel` | — |
|
|
28
|
-
| `forkRun` | `POST /runs/{runId}:fork` | `runs:create` + `runs:read` | `replay`
|
|
29
|
-
| `diffRun` | `GET /runs/{runId}:diff?against=` | `runs:read` on both | OPTIONAL
|
|
30
|
-
| `getRunAncestry` | `GET /runs/{runId}/ancestry` | `runs:read` | `multiAgent.executionModel.crossHostCausation.ancestryEndpointSupported
|
|
31
|
-
| `createAnnotation` / `listAnnotations` | `POST` / `GET /runs/{runId}/annotations` | `runs:annotate` / `runs:read` | `feedback
|
|
32
|
+
| `forkRun` | `POST /runs/{runId}:fork` | `runs:create` + `runs:read` | `replay` |
|
|
33
|
+
| `diffRun` | `GET /runs/{runId}:diff?against=` | `runs:read` on both | OPTIONAL |
|
|
34
|
+
| `getRunAncestry` | `GET /runs/{runId}/ancestry` | `runs:read` | `multiAgent.executionModel.crossHostCausation.ancestryEndpointSupported` |
|
|
35
|
+
| `createAnnotation` / `listAnnotations` | `POST` / `GET /runs/{runId}/annotations` | `runs:annotate` / `runs:read` | `feedback` |
|
|
32
36
|
| `getArtifact` | `GET /runs/{runId}/artifacts/{artifactId}` | `artifacts:read` | — |
|
|
33
|
-
| `getEvalSummary` | `GET /runs/{runId}/eval-summary` | `runs:read` | `agents.evalSuite
|
|
34
|
-
| `getRunCompensation` | `GET /runs/{runId}/compensation` | `runs:read` | `compensation`
|
|
35
|
-
| `getRunEffects` | `GET /runs/{runId}/effects` | `runs:read` | `idempotency`
|
|
36
|
-
|
|
37
|
+
| `getEvalSummary` | `GET /runs/{runId}/eval-summary` | `runs:read` | `agents.evalSuite` |
|
|
38
|
+
| `getRunCompensation` | `GET /runs/{runId}/compensation` | `runs:read` | `compensation` |
|
|
39
|
+
| `getRunEffects` | `GET /runs/{runId}/effects` | `runs:read` | `idempotency` |
|
|
40
|
+
|
|
41
|
+
A gated operation the host does not advertise (or an absent `diffRun`) answers `404`; for annotations it is `404 not_found`.
|
|
37
42
|
|
|
38
43
|
## Create
|
|
39
44
|
|
|
40
|
-
The `createRun` body is closed
|
|
45
|
+
The `createRun` body is closed (`unevaluatedProperties: false`). Its fields:
|
|
46
|
+
|
|
47
|
+
- `workflowId` — REQUIRED unless `mode: eval`.
|
|
48
|
+
- `inputs`, `residency`, `tenantId`, `scopeId`.
|
|
49
|
+
- `callbackUrl` — see [interrupt.md](interrupt.md) §Callback delivery. A refused value is `400 validation_error` with `details.field: "callbackUrl"`.
|
|
50
|
+
- `mode`, `evalSuiteRef`, `agentId`.
|
|
51
|
+
- The `RunOptions` fields `configurable`, `tags`, `metadata`. A body without `RunOptions` MUST be accepted as if it were `{}`.
|
|
52
|
+
|
|
53
|
+
### Request headers
|
|
41
54
|
|
|
42
55
|
| Header | Rule |
|
|
43
56
|
| --- | --- |
|
|
44
|
-
| `Idempotency-Key` | RECOMMENDED
|
|
57
|
+
| `Idempotency-Key` | RECOMMENDED. A replayed create MUST NOT create a second run, and carries `OpenWOP-Idempotent-Replay: true`. |
|
|
45
58
|
| `OpenWOP-Dedup: enforce` | The host MUST reject a duplicate `(tenantId, scopeId)` with `409 run_already_active` and `Retry-After`. |
|
|
46
|
-
| `OpenWOP-Force-Engine-Version` | Test keys only
|
|
59
|
+
| `OpenWOP-Force-Engine-Version` | Test keys only (the seams profile). A host MUST reject it on a production credential with `403`. |
|
|
60
|
+
|
|
61
|
+
### Response
|
|
62
|
+
|
|
63
|
+
The `201` response is `{ runId, status, eventsUrl, statusUrl? }`. `status` is one of `pending`, `running`, `waiting-approval`, `waiting-input`, `waiting-external`.
|
|
47
64
|
|
|
48
|
-
|
|
65
|
+
- `eventsUrl` and `statusUrl` MUST resolve under the origin the request was made to — a relative path, or an absolute URL on the same origin — and MUST NOT downgrade the scheme. A link naming a different host, or `http://` on an `https://` origin, is non-conformant.
|
|
66
|
+
- The base that minted `runId` MUST resolve it: `GET /runs/{runId}` and `GET /runs/{runId}/events/poll` at that base MUST answer `200` for the returned id, percent-encoded per [identity.md](identity.md) §5. (A front door that decodes `%2F` before routing makes every tenant-bound id unreachable.)
|
|
49
67
|
|
|
50
|
-
|
|
68
|
+
### Refusals
|
|
69
|
+
|
|
70
|
+
- `mode: eval` makes `evalSuiteRef` and `agentId` REQUIRED. It starts an eval-suite projection that emits the content-free `eval.*` family and terminates with an `EvalSummary`. A host that does not advertise `agents.evalSuite` MUST reject it.
|
|
71
|
+
- A host advertising `dataResidency` MUST reject a `residency.region` outside `dataResidency.regions` with `422 residency_unavailable` and create no run (see §"Conversation and residency capabilities").
|
|
72
|
+
- A workflow that references a capability-gated reserved node type on a host that does not advertise the capability MUST be rejected with `422 capability_required`.
|
|
73
|
+
|
|
74
|
+
### The start event
|
|
75
|
+
|
|
76
|
+
`run.started` ([events.md](events.md)) MUST echo the run's `owner` block exactly as `RunSnapshot.owner` carries it. Its `transport` records `rest`, `mcp`, `a2a` or `ui`.
|
|
51
77
|
|
|
52
78
|
## Run options
|
|
53
79
|
|
|
54
80
|
`schemas/v2/run-options.schema.json` is `{ configurable?, tags?, metadata? }`.
|
|
55
81
|
|
|
56
|
-
`configurable` is `schemas/v2/configurable.schema.json`: closed, nested and versioned
|
|
82
|
+
`configurable` is `schemas/v2/configurable.schema.json`: closed, nested and versioned. The request body `$ref`s it directly; there is no `allOf`-merge of an open map. `version` is REQUIRED and is `1`. It has five sections:
|
|
83
|
+
|
|
84
|
+
| Section | Keys |
|
|
85
|
+
| --- | --- |
|
|
86
|
+
| `run` | `recursionLimit`, `runTimeoutMs`, `maxLoopIterations`, `escalationThreshold` |
|
|
87
|
+
| `ai` | `provider`, `model`, `temperature` (0..2), `maxTokens`, `credentialRef`, `promptOverrides`, `mockProvider`, `reasoningVerbosity` (`none` \| `summary` \| `full`), `maxRefusals` |
|
|
88
|
+
| `distillation` | `tokenBudget` |
|
|
89
|
+
| `budget` | `schemas/v2/budget-policy.schema.json` — the run's budget policy |
|
|
90
|
+
| `extensions` | `<org>: {…}` — a vendor key lives under its registered org and nowhere else |
|
|
91
|
+
|
|
92
|
+
### `run` section
|
|
57
93
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
| `distillation` | `tokenBudget` | Resolves to `min(tokenBudget, memory.distillation.maxTokenBudget)`; a run that cannot distill within it MUST fail atomically with `token_budget_exceeded`. |
|
|
63
|
-
| `budget` | `schemas/v2/budget-policy.schema.json` | The run's budget policy. |
|
|
64
|
-
| `extensions` | `<org>: {…}` | A vendor key lives under its registered org and nowhere else. |
|
|
94
|
+
- `recursionLimit` is clamped to `limits.maxNodeExecutions`.
|
|
95
|
+
- `runTimeoutMs` resolves to `min(runTimeoutMs, limits.maxRunDurationMs)`. An out-of-range value MUST return `400 validation_error` at create. A breach MUST emit `cap.breached { kind: 'run-duration' }` and terminate the run `failed` with `run_timeout`.
|
|
96
|
+
- `maxLoopIterations` resolves against `limits.maxLoopIterations`. A breach MUST emit `cap.breached { kind: 'loop-iterations' }` and fail with `loop_limit_exceeded`.
|
|
97
|
+
- `escalationThreshold` is the `low-confidence` threshold ([interrupt.md](interrupt.md)).
|
|
65
98
|
|
|
66
|
-
|
|
99
|
+
### `ai` section
|
|
67
100
|
|
|
68
|
-
`
|
|
101
|
+
- `provider` MUST be in `aiProviders.providers`, else `400 validation_error`.
|
|
102
|
+
- `credentialRef` MUST reference a provider in `aiProviders.byok`, else `403 credential_forbidden`. It never carries key material.
|
|
103
|
+
- `mockProvider` is test-keys-only: a host MUST refuse it on a production credential with `403`.
|
|
104
|
+
- `maxRefusals` is the refusal ceiling ([events.md](events.md) E5).
|
|
105
|
+
|
|
106
|
+
### `distillation` section
|
|
107
|
+
|
|
108
|
+
`tokenBudget` resolves to `min(tokenBudget, memory.distillation.maxTokenBudget)`. A run that cannot distill within it MUST fail atomically with `token_budget_exceeded`.
|
|
109
|
+
|
|
110
|
+
### Validation and persistence
|
|
111
|
+
|
|
112
|
+
- An unknown root key, an unknown key inside a section, or a dotted key (`ai.provider` as a string key) MUST be rejected with `400 validation_error`.
|
|
113
|
+
- A host MUST persist `RunOptions` on the run at creation, MUST surface the same `configurable` to every attempt of a node, and MUST NOT allow `configurable` to change after creation.
|
|
114
|
+
- A workflow's `configurableSchema` MUST be validated against at create time and MUST be surfaced on `getWorkflow`.
|
|
115
|
+
|
|
116
|
+
### `tags` and `metadata`
|
|
117
|
+
|
|
118
|
+
- `tags` is an opaque string array: at most 100 entries, each at most 256 characters, valid UTF-8. A host MUST NOT reject a tag on format, and MUST return `400 validation_error` over the limits.
|
|
119
|
+
- `metadata` is a free-form JSON object. A host MUST persist it, and the engine MUST NOT consume it for any execution decision.
|
|
120
|
+
- Both surface unchanged on `RunSnapshot`.
|
|
69
121
|
|
|
70
122
|
## Snapshot
|
|
71
123
|
|
|
72
|
-
`getRun` returns `schemas/v2/run-snapshot.schema.json
|
|
124
|
+
`getRun` returns `schemas/v2/run-snapshot.schema.json`: the fold of the event log through the run projection. The object is closed. `runId`, `workflowId`, `status`, `owner` and `eventLogSchemaVersion` are REQUIRED.
|
|
73
125
|
|
|
74
|
-
| Field |
|
|
126
|
+
| Field | Meaning |
|
|
75
127
|
| --- | --- |
|
|
76
|
-
| `owner` | `{ tenant, workspace?, subject }`, closed
|
|
77
|
-
| `status` |
|
|
78
|
-
| `eventLogSchemaVersion` | The era key, integer ≥ 2 (persistence.md §"The era key")
|
|
79
|
-
| `engineVersion` | Integer
|
|
80
|
-
| `compensationStatus` | `none`, `pending`, `running`, `completed`, `partial`, `failed`, `manual
|
|
81
|
-
| `currentNodeId` | Set while suspended; names the node holding the interrupt
|
|
82
|
-
| `error` | `{ code, message, details? }` on terminal `failed
|
|
83
|
-
| `configurable`, `tags`, `metadata` | The persisted `RunOptions
|
|
84
|
-
| `agent`, `runOrchestrator` | `schemas/v2/agent-ref.schema.json
|
|
85
|
-
| `metrics.openwopCost` | `{ usd, tokens { input, output }, model, provider, duration_ms }`; absence is not zero
|
|
86
|
-
|
|
87
|
-
|
|
128
|
+
| `owner` | `{ tenant, workspace?, subject }`, closed; `subject` REQUIRED (`schemas/v2/subject.schema.json`) |
|
|
129
|
+
| `status` | Run state (below) |
|
|
130
|
+
| `eventLogSchemaVersion` | The era key, integer ≥ 2 ([persistence.md](persistence.md) §"The era key") |
|
|
131
|
+
| `engineVersion` | Integer |
|
|
132
|
+
| `compensationStatus` | `none`, `pending`, `running`, `completed`, `partial`, `failed`, `manual` |
|
|
133
|
+
| `currentNodeId` | Set while suspended; names the node holding the interrupt |
|
|
134
|
+
| `error` | `{ code, message, details? }` on terminal `failed` |
|
|
135
|
+
| `configurable`, `tags`, `metadata` | The persisted `RunOptions` |
|
|
136
|
+
| `agent`, `runOrchestrator` | `schemas/v2/agent-ref.schema.json` |
|
|
137
|
+
| `metrics.openwopCost` | `{ usd, tokens { input, output }, model, provider, duration_ms }`; absence is not zero |
|
|
138
|
+
|
|
139
|
+
Field rules:
|
|
140
|
+
|
|
141
|
+
- `status` is one of `pending`, `running`, `paused`, `waiting-approval`, `waiting-input`, `waiting-external`, `completed`, `failed`, `cancelling`, `cancelled`. `waiting-external` MUST be used when the suspended interrupt's `kind` is `external-event`. `cancelling` is the state between an accepted cancel and the terminal `cancelled`. The vocabulary grows by [overview.md](overview.md) §0.
|
|
142
|
+
- `owner`: a run created before the host emitted subjects reads with the subject rule in [identity.md](identity.md), stamped at first read and never rewritten.
|
|
143
|
+
- `compensationStatus`: a host that does not advertise `compensation` MUST omit it. A host that does MUST include it on every snapshot, `none` when never requested.
|
|
144
|
+
- `runOrchestrator` MUST NOT change for the run's lifetime.
|
|
145
|
+
|
|
146
|
+
### Caching and encoding
|
|
147
|
+
|
|
148
|
+
- The `200` SHOULD carry a strong `ETag` derived from the latest persisted `sequence`. When present it MUST change on every observable transition and be stable otherwise.
|
|
149
|
+
- When the host sends an `ETag`, a request whose `If-None-Match` matches it MUST receive `304` with no body.
|
|
150
|
+
- A host MAY compress (`gzip` baseline; `br` and `zstd` only where advertised under `extensions["<org>.rest-transport"].contentEncodings`, [ext/restTransport](../ext/restTransport/README.md)). It MUST then set `Content-Encoding` and `Vary: Accept-Encoding`. The decoded body is byte-identical.
|
|
88
151
|
|
|
89
152
|
## List
|
|
90
153
|
|
|
91
|
-
`GET /runs` (
|
|
154
|
+
`GET /runs` (gated on `runList`) returns `{ runs: RunSnapshot[], nextCursor? }`: the caller's runs, newest first.
|
|
155
|
+
|
|
156
|
+
- Only runs whose tenant segment is the caller's appear, and every `runId` is bound ([identity.md](identity.md) §5). A run the caller created MUST appear.
|
|
157
|
+
- A page MUST NOT exceed `runList.maxPageSize`.
|
|
158
|
+
- `cursor` is opaque. A cursor the host did not mint MUST be refused with `400 validation_error`.
|
|
159
|
+
- `workflowId` and `status` are exact-match filters when `runList.filters` names them. An unadvertised filter is ignored.
|
|
92
160
|
|
|
93
161
|
## Cancel
|
|
94
162
|
|
|
95
|
-
`cancelRun` accepts `{ reason? }` and answers `200 { runId, status }
|
|
163
|
+
`cancelRun` accepts `{ reason? }` and answers `200 { runId, status }`, where `status` is `cancelling` or `cancelled`. The cascade MAY be asynchronous; the run emits `run.cancelled` when it completes.
|
|
164
|
+
|
|
165
|
+
- A cancel on a terminal run (`completed`, `failed`, `cancelled`) MUST be refused with `409 run_terminal`. A `200` echoing the terminal state is non-conformant.
|
|
166
|
+
- Cancelling a parent MUST NOT silently abandon an active compensation ([security-defaults.md](security-defaults.md)).
|
|
167
|
+
- `run.cancelled.parentRunId` with `reason: parent-cancelled` records a cascade from a parent.
|
|
168
|
+
|
|
169
|
+
### Bulk cancel
|
|
170
|
+
|
|
171
|
+
`bulkCancelRuns` accepts `{ runIds[1..100], reason? }`.
|
|
172
|
+
|
|
173
|
+
- Over the host's cap (RECOMMENDED 100) it MUST return `400 validation_error` with `details.maxRunIds`.
|
|
174
|
+
- The host MUST process each id independently, and MUST return `200 { results[] }` in request order, even when every id failed.
|
|
175
|
+
- The host MUST enforce authorization per id. A run the caller cannot see yields `ok: false` with an error envelope in that entry, never a top-level `403`.
|
|
176
|
+
|
|
177
|
+
Per-entry errors ([errors.md](errors.md)):
|
|
96
178
|
|
|
97
|
-
|
|
179
|
+
| Condition | Code |
|
|
180
|
+
| --- | --- |
|
|
181
|
+
| The id's tenant segment is not the caller's ([identity.md](identity.md) §5 applies inside an entry as on a path) | `id_tenant_mismatch`, or `not_found` where existence is not leaked |
|
|
182
|
+
| A run in the caller's tenant the caller may not cancel | `run_forbidden` |
|
|
183
|
+
| A run already terminal | `run_terminal` |
|
|
98
184
|
|
|
99
|
-
`
|
|
185
|
+
`ok: true` carries `status` `cancelling` or `cancelled`; `ok: false` carries the error envelope.
|
|
100
186
|
|
|
101
187
|
## Pause and resume
|
|
102
188
|
|
|
103
|
-
`pauseRun` accepts `{ reason?, drainPolicy? }`
|
|
189
|
+
`pauseRun` accepts `{ reason?, drainPolicy? }` and answers `202 { runId, status: 'paused', pausedAt? }`. The transition emits `run.paused`, whose payload echoes the request's `drainPolicy` word.
|
|
190
|
+
|
|
191
|
+
`drainPolicy` is one of:
|
|
192
|
+
|
|
193
|
+
- `drain-current-node` (default) — the executing node reaches a terminal first.
|
|
194
|
+
- `immediate` — the run is snapshotted between events. The executing attempt is cut: it has no terminal node event, a host MUST NOT record `node.failed` (or any terminal node event) for it, and the resumed run's `node.started` begins a fresh attempt. `run.paused` itself records the interruption; its payload MAY carry `interruptedNodeId` and `interruptedAttempt` so a `debug` consumer can see which attempt was cut.
|
|
195
|
+
|
|
196
|
+
`resumeRun` accepts `{ reason? }`, answers `202 { runId, status: 'running', resumedAt? }`, and emits `run.resumed`.
|
|
197
|
+
|
|
198
|
+
Rules:
|
|
199
|
+
|
|
200
|
+
- A pause on a run that is already paused, terminal, or otherwise unpausable MUST receive `409`.
|
|
201
|
+
- A resume on a run that is not paused MUST return `409`.
|
|
202
|
+
- In both cases the code is `run_terminal` when the run is terminal, else `run_state_conflict` with `details.runStatus` naming the refusing status.
|
|
203
|
+
- Only `resumeRun` or a cancel exits `paused`.
|
|
204
|
+
- A replay MUST fold `run.paused` and `run.resumed` as no-ops for projected state.
|
|
104
205
|
|
|
105
206
|
## Fork
|
|
106
207
|
|
|
107
|
-
`forkRun` accepts `{ mode: replay | branch, fromSeq?, runOptionsOverlay? }
|
|
208
|
+
`forkRun` accepts `{ mode: replay | branch, fromSeq?, runOptionsOverlay? }`. Events with `sequence < fromSeq` are fixed history; events `≥ fromSeq` re-execute.
|
|
209
|
+
|
|
210
|
+
- `fromSeq` is REQUIRED for `branch` and defaults to `0` for `replay`.
|
|
211
|
+
- `runOptionsOverlay` is `branch`-only. A `replay` with a non-empty overlay MUST be rejected with `400`.
|
|
212
|
+
- A `fromSeq` not in the source log MUST be rejected with `422 fork_point_invalid`.
|
|
213
|
+
|
|
214
|
+
The `201` response is `{ runId, sourceRunId, fromSeq?, mode, status, eventsUrl }`. The child's `owner` is copied verbatim from the parent. Determinism, side-effect suppression, and forking an era-2 parent are in [replay.md](replay.md).
|
|
108
215
|
|
|
109
216
|
## Diff and ancestry
|
|
110
217
|
|
|
111
|
-
`diffRun` returns `schemas/v2/run-diff-response.schema.json`: `divergedAtSeq`, ordered `eventDiffs[]`, `stateDiff`, optional `truncated`.
|
|
218
|
+
`diffRun` returns `schemas/v2/run-diff-response.schema.json`: `divergedAtSeq`, ordered `eventDiffs[]`, `stateDiff`, optional `truncated`.
|
|
219
|
+
|
|
220
|
+
- The diff MUST be a pure function of the two logs. Identical logs MUST yield `divergedAtSeq: null` and empty `eventDiffs`.
|
|
221
|
+
- `eventId`, `runId`, `timestamp` and other run-scoped fields MUST be excluded from comparison.
|
|
222
|
+
- A host that diffs an in-flight prefix MUST set `truncated: true`.
|
|
223
|
+
- A caller lacking `runs:read` on either run MUST receive `403`.
|
|
224
|
+
|
|
225
|
+
`getRunAncestry` returns `schemas/v2/run-ancestry-response.schema.json` (`runId`, `hostId`, `parent` or `null`). A client walks the chain one hop at a time via `parent.wellKnownUrl`.
|
|
112
226
|
|
|
113
227
|
## Annotations, artifacts, eval summary
|
|
114
228
|
|
|
115
|
-
|
|
229
|
+
### Annotations
|
|
230
|
+
|
|
231
|
+
`createAnnotation` accepts `schemas/v2/annotation-create.schema.json` and returns `201` with `schemas/v2/annotation.schema.json`. `listAnnotations` returns `{ annotations[] }`.
|
|
232
|
+
|
|
233
|
+
An annotation is a live notification (`run.annotated`), never a run event. It MUST NOT enter the event log and MUST be excluded from fork, replay and diff.
|
|
234
|
+
|
|
235
|
+
### Artifacts
|
|
236
|
+
|
|
237
|
+
`getArtifact` answers `application/json` with an implementation-defined object or, when `Accept` prefers `application/a2a+json`, an A2A `Artifact` (`schemas/v2/artifact.schema.json`). A host SHOULD offer the latter.
|
|
238
|
+
|
|
239
|
+
- A body served as `application/a2a+json` MUST validate against that schema, with `artifactId` equal to the path's.
|
|
240
|
+
- A `url` Part in it MUST NOT resolve beyond the caller's `artifacts:read` authorization.
|
|
241
|
+
|
|
242
|
+
### Eval summary
|
|
243
|
+
|
|
244
|
+
`getEvalSummary` returns `schemas/v2/eval-summary.schema.json` for a terminal eval run, `409` while it is running, and `404` when the run is not an eval run. The summary MUST be free of task output, rubric prose and credentials.
|
|
116
245
|
|
|
117
246
|
## Conversation and residency capabilities
|
|
118
247
|
|
|
119
|
-
`conversationPrimitive`
|
|
120
|
-
|
|
121
|
-
does not advertise `
|
|
122
|
-
|
|
248
|
+
### `conversationPrimitive`
|
|
249
|
+
|
|
250
|
+
`conversationPrimitive` carries no payload: its presence is the claim ([capabilities.md](capabilities.md) §2). A host that does not advertise it MUST refuse a workflow whose `nodes[].typeId` references `core.conversationGate` — at registration or at run creation — with `422 capability_required`, naming the family in `details.requiredCapability`.
|
|
251
|
+
|
|
252
|
+
A conversation turn MAY carry `parts`: a non-empty array of A2A `Part` objects (`schemas/v2/part.schema.json`) that marks the turn A2A-shaped. A producer SHOULD emit it and keep `content` readable by consumers that predate it. A turn without `parts` stays valid on emission, replay and fork.
|
|
253
|
+
|
|
254
|
+
### `dataResidency`
|
|
255
|
+
|
|
256
|
+
A host advertising `dataResidency` MUST honor-or-reject, and MUST NOT silently accept-and-ignore:
|
|
257
|
+
|
|
258
|
+
- accept a `residency` constraint naming a region in `dataResidency.regions`;
|
|
259
|
+
- refuse one it does not advertise with `residency_unavailable`.
|
|
260
|
+
|
|
261
|
+
A host that does not advertise `dataResidency` MAY ignore or reject a `residency` constraint, but MUST NOT claim to honor it.
|
|
262
|
+
|
|
263
|
+
## During the v1 overlap
|
|
123
264
|
|
|
124
|
-
A
|
|
265
|
+
A non-terminal run inherited from v1 continues, or is cancelled `v1_pin_unsupported`, per [persistence.md](persistence.md) §"Runs pinned to v1".
|
|
125
266
|
|
|
126
|
-
|
|
127
|
-
region in `dataResidency.regions`, refuse one it does not advertise with `residency_unavailable`,
|
|
128
|
-
and MUST NOT silently accept-and-ignore. A host that does not advertise `dataResidency` MAY ignore or
|
|
129
|
-
reject a `residency` constraint but MUST NOT claim to honor it.
|
|
267
|
+
*Sources: RFC 0170, RFC 0171, RFC 0176, RFC 0182.*
|