@sema-agent/sdk 19.0.0 → 20.0.0
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/README.md +56 -0
- package/dist/errors.d.ts +4 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +4 -1
- package/dist/errors.js.map +1 -1
- package/dist/events.d.ts +15 -15
- package/dist/events.d.ts.map +1 -1
- package/dist/idempotency.d.ts +11 -0
- package/dist/idempotency.d.ts.map +1 -1
- package/dist/idempotency.js +11 -0
- package/dist/idempotency.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/resources/fleet.d.ts +7 -0
- package/dist/resources/fleet.d.ts.map +1 -1
- package/dist/resources/fleet.js.map +1 -1
- package/dist/resources/leader.d.ts +9 -0
- package/dist/resources/leader.d.ts.map +1 -1
- package/dist/resources/leader.js +9 -0
- package/dist/resources/leader.js.map +1 -1
- package/dist/resources/runs.d.ts +16 -0
- package/dist/resources/runs.d.ts.map +1 -1
- package/dist/resources/runs.js +22 -0
- package/dist/resources/runs.js.map +1 -1
- package/dist/resources/session-sync.d.ts +14 -5
- package/dist/resources/session-sync.d.ts.map +1 -1
- package/dist/resources/session-sync.js +3 -2
- package/dist/resources/session-sync.js.map +1 -1
- package/dist/resources/sessions.d.ts +33 -2
- package/dist/resources/sessions.d.ts.map +1 -1
- package/dist/resources/sessions.js +29 -1
- package/dist/resources/sessions.js.map +1 -1
- package/dist/resources/tasks.d.ts +10 -2
- package/dist/resources/tasks.d.ts.map +1 -1
- package/dist/resources/tasks.js +8 -0
- package/dist/resources/tasks.js.map +1 -1
- package/dist/sync.d.ts +23 -0
- package/dist/sync.d.ts.map +1 -1
- package/dist/sync.js +43 -0
- package/dist/sync.js.map +1 -1
- package/dist/types.d.ts +156 -6
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/openapi.yaml +370 -20
- package/package.json +1 -1
package/openapi.yaml
CHANGED
|
@@ -87,7 +87,9 @@ info:
|
|
|
87
87
|
browser guard refuses to hold this in a browser; the portal's browser talks to its own BFF) and
|
|
88
88
|
`x-agent-principal: <principal>` (REQUIRED; opaque to the service; principal-first, CORE-PRINCIPLES §3).
|
|
89
89
|
• Submit-class POSTs (`/v1/tasks`, `/v1/runs`) carry an `Idempotency-Key` (the SDK auto-generates one;
|
|
90
|
-
in-process dedupe window = 15 min; cross-replica hard-dedupe = the session-active 409, see §below
|
|
90
|
+
server <= 7.124.x: in-process dedupe window = 15 min; cross-replica hard-dedupe = the session-active 409, see §below.
|
|
91
|
+
server >= 7.125.0: the key is a unique key on the RUN ROW — a same-key resubmit reads that row and answers from it, across
|
|
92
|
+
replicas and restarts; the process only merges concurrent in-flight duplicates. `GET /v1/runs?idempotencyKey=` reads what a key built).
|
|
91
93
|
• New contract fields are OPTIONAL with no default (CORE-PRINCIPLES §6).
|
|
92
94
|
• Async results are delivered by POLL (`GET /v1/runs/:id`) or SSE (`/v1/runs/:id/events`).
|
|
93
95
|
There is NO generic webhook/callback and NO signing (SERVICE-CORE-CONTEXT §2.4).
|
|
@@ -427,6 +429,12 @@ paths:
|
|
|
427
429
|
`verify` / `cascade` run's earlier tier already ran turns and only the last tier is refused, the SAME code arrives
|
|
428
430
|
as 200 with a failed body (`terminal.code`; billed; a same-key resend replays). Multi-tier callers must read both
|
|
429
431
|
forms. On a park the 200 body is the result itself plus the three poll-receipt keys (`SyncSubmitResult`, S-173).
|
|
432
|
+
🔴 IDEMPOTENT REPLAY (server >= 7.125.0, contract §0.7): a same-key resubmit that finds the run row the key already built (another process, another
|
|
433
|
+
replica, a restart, or an original request that has since settled) does NOT run again and does NOT answer the first answer's engine-result shape —
|
|
434
|
+
it answers `200` with that run's READ FACE (`GET /v1/runs/{taskId}`) plus `replay: true` (`RunReplayRecord`). Branch on `replay === true`; the outcome is
|
|
435
|
+
in `result` (a nested `TaskResult`), and `wiringManifest` / `notice` are absent. A same-key duplicate that is still IN FLIGHT in the same process is merged
|
|
436
|
+
instead and gets the first answer's bytes (no `replay` key). Only a submit that builds a row can be replayed this way (a run store AND a `sessionId`);
|
|
437
|
+
otherwise a settled key re-runs. A key whose row the caller cannot read answers 409 `conflict.run_exists`.
|
|
430
438
|
parameters:
|
|
431
439
|
- $ref: '#/components/parameters/IdempotencyKey'
|
|
432
440
|
requestBody:
|
|
@@ -437,13 +445,17 @@ paths:
|
|
|
437
445
|
responses:
|
|
438
446
|
'200':
|
|
439
447
|
description: >-
|
|
440
|
-
The task result (`SyncSubmitResult`): the sealed + redacted engine result; on a park (`terminal.kind:"paused"`)
|
|
448
|
+
One of two shapes, told apart by the `replay` key. (1) The task result (`SyncSubmitResult`): the sealed + redacted engine result; on a park (`terminal.kind:"paused"`)
|
|
441
449
|
it additionally carries the poll receipt `{taskId, sessionId, status}` with `status` ∈ {suspended, needs_review}
|
|
442
450
|
(server >= 7.104.0 / S-173 — before that a park answered ONLY those three keys). Terminal bodies carry no `status`
|
|
443
451
|
on >= 7.64; a server <= 7.63 answers the flat form instead (terminal word in `status`, no `terminal`) — see SyncSubmitResult.
|
|
452
|
+
(2) server >= 7.125.0, same-key replay: the existing run's read face with `replay: true` (`RunReplayRecord`) — NOT an engine result.
|
|
444
453
|
content:
|
|
445
454
|
application/json:
|
|
446
|
-
schema:
|
|
455
|
+
schema:
|
|
456
|
+
oneOf:
|
|
457
|
+
- $ref: '#/components/schemas/SyncSubmitResult'
|
|
458
|
+
- $ref: '#/components/schemas/RunReplayRecord'
|
|
447
459
|
'400': { $ref: '#/components/responses/BadRequest' }
|
|
448
460
|
'401': { $ref: '#/components/responses/Unauthorized' }
|
|
449
461
|
'403': { $ref: '#/components/responses/Forbidden' } # (declared 2026-08-01) 带 `sessionId` 复用他人会话:归属不匹配 ⇒ 403(自公开快照起点即有,spec 此前漏登记)。server 3.15.0 起多一个触发条件:多租部署上 session store 无归属判别能力时,复用**任何** caller 自报的 sessionId 都 403(含本 worker 自己刚铸的 —— 无 register seam 时归属从未被记下)。core 2.11.0 起默认内存店自带该能力 ⇒ 该触发条件在默认部署上不再出现。 ⚠️ (declared 2026-08-05,server >=7.0.0)`memory.admission_denied` 也走本状态 —— 本状态的 producer 全清单与分支判据写在 `Forbidden` 组件的 description 里(**那段是被解析的**,本行注释只是指路,不复述以免两处漂移)。
|
|
@@ -467,6 +479,10 @@ paths:
|
|
|
467
479
|
deployment has NO file-history store but the request asks to restore files; the request is well-formed, the
|
|
468
480
|
deployment shape conflicts with it (change the deployment, not the request; `capabilities.rewindFiles` is false
|
|
469
481
|
there). Before 7.104.0 it arrived as 200 with a failed body.
|
|
482
|
+
`conflict.run_exists` (server >= 7.125.0, contract §0.7): the submit's `Idempotency-Key` (or `body.taskId`) already points at a run row you cannot read,
|
|
483
|
+
or at a row that is not the one this submit's `taskId` names. The sentence is `a run already exists for this submission's taskId or Idempotency-Key`
|
|
484
|
+
(the code is unchanged; only the sentence moved); nothing about the row is echoed (no cross-tenant oracle). Submit under a NEW key. On
|
|
485
|
+
`POST /v1/tasks/stream`, once the stream is open the same refusal arrives as the `done` frame's `{status:"failed", errorCode, errorMessage}`.
|
|
470
486
|
content:
|
|
471
487
|
application/json:
|
|
472
488
|
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
|
@@ -653,6 +669,53 @@ paths:
|
|
|
653
669
|
'429': { $ref: '#/components/responses/RateLimited' }
|
|
654
670
|
'501': { $ref: '#/components/responses/NotImplemented' }
|
|
655
671
|
'503': { $ref: '#/components/responses/Unauthorized' }
|
|
672
|
+
get:
|
|
673
|
+
tags: [runs]
|
|
674
|
+
operationId: runsGetByIdempotencyKey
|
|
675
|
+
x-status: live
|
|
676
|
+
summary: Read the run an `Idempotency-Key` built (look before you re-submit).
|
|
677
|
+
description: >
|
|
678
|
+
server >= 7.125.0 (contract §0.7). Answers "which run did this submit key build?" for a client that crashed or lost the connection
|
|
679
|
+
after a submit and must decide whether to submit again. `idempotencyKey` is the RAW value the submit was sent with; the server scopes
|
|
680
|
+
it exactly as the submit did (your verified identity × resource family `run` — `POST /v1/tasks`, `/v1/tasks/stream`, `/v1/runs` and A2A
|
|
681
|
+
`message/send` all share family `run`, so a key used on any of them is found here). Another caller's key resolves to a different key and
|
|
682
|
+
reads as the same 404, byte-for-byte (no existence oracle). 200 = the same read face as `GET /v1/runs/{taskId}` (`RunRecord`), and the gates
|
|
683
|
+
run in the same order: 501 no run store → 401 → read row → owner gate → `?session=` assertion. A submit that never built a row leaves
|
|
684
|
+
nothing to find (404): no run store, or a `POST /v1/tasks` / `/v1/tasks/stream` submit without `sessionId` (those legs build a row only
|
|
685
|
+
with both), and a zero-work refusal (the key is released). NOT a submit: no billing, no admission gates.
|
|
686
|
+
parameters:
|
|
687
|
+
- in: query
|
|
688
|
+
name: idempotencyKey
|
|
689
|
+
required: true
|
|
690
|
+
schema: { type: string, minLength: 1 }
|
|
691
|
+
description: 'The RAW `Idempotency-Key` the submit carried (the server applies the scope; send it unhashed and unmodified). Missing or empty ⇒ 400 `request.query_invalid`.'
|
|
692
|
+
- in: query
|
|
693
|
+
name: session
|
|
694
|
+
required: false
|
|
695
|
+
schema: { type: string }
|
|
696
|
+
description: >-
|
|
697
|
+
The session assertion `GET /v1/runs/{taskId}` honors: for a session-bound run a non-operator caller sends the conversation's `sessionId` here;
|
|
698
|
+
a mismatch answers the same 404 as an unknown key.
|
|
699
|
+
responses:
|
|
700
|
+
'200':
|
|
701
|
+
description: The run row's read face — byte-for-byte what `GET /v1/runs/{taskId}` returns for that run. NO `replay` marker (that is only on a submit's replay body).
|
|
702
|
+
content:
|
|
703
|
+
application/json:
|
|
704
|
+
schema: { $ref: '#/components/schemas/RunRecord' }
|
|
705
|
+
'400':
|
|
706
|
+
description: 'errorCode `request.query_invalid` — `idempotencyKey` is missing or empty. (Unlike `GET /v1/leader?idempotencyKey=`, no key-shape check: the key is only hashed here.)'
|
|
707
|
+
content:
|
|
708
|
+
application/json:
|
|
709
|
+
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
|
710
|
+
'401': { $ref: '#/components/responses/Unauthorized' }
|
|
711
|
+
'404':
|
|
712
|
+
description: >-
|
|
713
|
+
errorCode `not_found.run` — this key (under your identity) built no run row, or the row is not yours, or `?session=` mismatched. One
|
|
714
|
+
code, one sentence, for every cause: absence of the key is not distinguishable from another caller's run.
|
|
715
|
+
content:
|
|
716
|
+
application/json:
|
|
717
|
+
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
|
718
|
+
'501': { $ref: '#/components/responses/NotImplemented' }
|
|
656
719
|
|
|
657
720
|
/v1/runs/{taskId}:
|
|
658
721
|
parameters:
|
|
@@ -1777,6 +1840,48 @@ paths:
|
|
|
1777
1840
|
properties:
|
|
1778
1841
|
error: { type: string }
|
|
1779
1842
|
total: { type: integer, description: 'present on the out-of-range `?message=` 404 — the message count available.' }
|
|
1843
|
+
patch:
|
|
1844
|
+
tags: [sessions]
|
|
1845
|
+
operationId: sessionsPatch
|
|
1846
|
+
x-status: live # server >= 7.125.0 (contract section 20).
|
|
1847
|
+
summary: Rename and/or pin a session (the session row's two writable bits).
|
|
1848
|
+
description: >
|
|
1849
|
+
server >= 7.125.0 (contract section 20) — write the session row's two writable bits: `title` (rename, or `null` = clear back to the
|
|
1850
|
+
automatic title) and `pinned`. ONE COLUMN, ONE FACT: `title` is a single column with two writers under one law — the user writes it through
|
|
1851
|
+
this verb UNCONDITIONALLY (a string renames; `null` clears the column back to NULL); the service's auto-titler fills it ONLY when it is
|
|
1852
|
+
NULL (read once per submit). So a session the user named is never overwritten by the auto-titler, and after a clear the next submit has
|
|
1853
|
+
the auto-titler fill it again. There is no second column. `pinned` is one boolean column (default `false`).
|
|
1854
|
+
`200` is the session's LIST ROW — the same projection as the matching row of `GET /v1/sessions`, key for key (including the derivation of
|
|
1855
|
+
`tempo` and the always-present `pinned`). Renaming / pinning does NOT move `lastActivityAt` (the list's recency order is unchanged).
|
|
1856
|
+
PROBE: there is no capability bit; probe by status code — `405` (`Allow` without `PATCH`, `errorCode: method_not_allowed`) = an older server (<= 7.124.x);
|
|
1857
|
+
`501 capability.session_store_required` = this deployment has no durable session store (the in-memory dev store has no row read / write face).
|
|
1858
|
+
Same gate as `GET /v1/sessions/{sessionId}`, same order: store fact (501) -> ownership (the SAME predicate as the session-view read — whoever can read
|
|
1859
|
+
the row can write it, never wider; an operator / service token gets no extra reach; a stranger and "no such session" answer byte-identical
|
|
1860
|
+
`404 not_found.session`, no existence oracle) -> body (400) -> the store's one write door -> read back and answer with the list row.
|
|
1861
|
+
Only rows on the LIST face are writable: a placed (delegation-transcript) session is not in the list, so a PATCH on it answers 404.
|
|
1862
|
+
requestBody:
|
|
1863
|
+
required: true
|
|
1864
|
+
content:
|
|
1865
|
+
application/json:
|
|
1866
|
+
schema: { $ref: '#/components/schemas/SessionPatchBody' }
|
|
1867
|
+
responses:
|
|
1868
|
+
'200':
|
|
1869
|
+
description: The session's list row (same projection as the matching `GET /v1/sessions` row, key for key).
|
|
1870
|
+
content:
|
|
1871
|
+
application/json:
|
|
1872
|
+
schema: { $ref: '#/components/schemas/SessionSummary' }
|
|
1873
|
+
'400':
|
|
1874
|
+
description: >-
|
|
1875
|
+
Malformed body, told apart by `errorCode` (branch on the code, never the sentence). `request.body_shape` — not a JSON object / empty body /
|
|
1876
|
+
a key outside the closed set `{ title, pinned }` / `title: ""` (the only spelling of "clear" is `null`). `request.field_invalid` — `title`
|
|
1877
|
+
neither a string nor `null`, longer than 120 characters, or with leading / trailing whitespace; `pinned` not a boolean. Invalid JSON answers
|
|
1878
|
+
like every other JSON-body route (`request.invalid_json`). The row is untouched on any 400.
|
|
1879
|
+
content:
|
|
1880
|
+
application/json:
|
|
1881
|
+
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
|
1882
|
+
'401': { $ref: '#/components/responses/Unauthorized' }
|
|
1883
|
+
'404': { $ref: '#/components/responses/NotFound' }
|
|
1884
|
+
'501': { $ref: '#/components/responses/NotImplemented' }
|
|
1780
1885
|
delete:
|
|
1781
1886
|
tags: [sessions]
|
|
1782
1887
|
operationId: sessionsDelete
|
|
@@ -1810,7 +1915,9 @@ paths:
|
|
|
1810
1915
|
deleted: { type: boolean }
|
|
1811
1916
|
'400':
|
|
1812
1917
|
# (declared 2026-07-31) errorCode "request.id_invalid" — the path sessionId must decode to a
|
|
1813
|
-
#
|
|
1918
|
+
# canonical uuid of ANY version (`routes/sessions.ts`; server >= 7.125.0 — the ONE shape gate shared with sync / fork / policy;
|
|
1919
|
+
# <= 7.124.x accepted only uuidv7 here); a malformed id never reaches the store. Branch on the code, never the sentence
|
|
1920
|
+
# (the sentence changed with the gate: "invalid session id (must be a canonical uuid)").
|
|
1814
1921
|
$ref: '#/components/responses/BadRequest'
|
|
1815
1922
|
'401': { $ref: '#/components/responses/Unauthorized' }
|
|
1816
1923
|
'404': { $ref: '#/components/responses/NotFound' }
|
|
@@ -1845,8 +1952,9 @@ paths:
|
|
|
1845
1952
|
summary: Branch a NEW session off this one's whole history.
|
|
1846
1953
|
description: >
|
|
1847
1954
|
E17 (shell-host) — branch a fresh session off the source's WHOLE history (a "branch this conversation" /
|
|
1848
|
-
regenerate-from-here). Owner-scoped (non-owner → 404; the fork is owned by the caller). 400 on
|
|
1849
|
-
|
|
1955
|
+
regenerate-from-here). Owner-scoped (non-owner → 404; the fork is owned by the caller). 400 `request.id_invalid` on an id that is not
|
|
1956
|
+
a canonical uuid — ANY uuid version passes since server >= 7.125.0 (the ONE shape gate shared with sync / policy / delete; <= 7.124.x accepted only
|
|
1957
|
+
uuidv7 here, so a shell-preset v4 id was creatable and syncable but not forkable). 501 without a durable session store.
|
|
1850
1958
|
responses:
|
|
1851
1959
|
'200':
|
|
1852
1960
|
description: The new branched session id.
|
|
@@ -2364,7 +2472,7 @@ paths:
|
|
|
2364
2472
|
rules: { $ref: '#/components/schemas/StoredSessionRules' }
|
|
2365
2473
|
'400':
|
|
2366
2474
|
# (declared 2026-07-31) errorCode "request.id_invalid" — GET and PUT share the SAME id-shape check
|
|
2367
|
-
# in `routes/sessions.ts` (uuidv7
|
|
2475
|
+
# in `routes/sessions.ts` (a canonical uuid of any version since server >= 7.125.0; uuidv7 only on <= 7.124.x) ahead of the method branch; only PUT had this declared.
|
|
2368
2476
|
$ref: '#/components/responses/BadRequest'
|
|
2369
2477
|
'401': { $ref: '#/components/responses/Unauthorized' }
|
|
2370
2478
|
'404': { $ref: '#/components/responses/NotFound' }
|
|
@@ -2845,8 +2953,8 @@ paths:
|
|
|
2845
2953
|
lacks. Owner-scoped (non-owner → 404, no oracle); 400 on an id that is not uuid-SHAPED — 🔴 the sync family's
|
|
2846
2954
|
gate is `isUuidShape` (ANY uuid version), NOT `isUuidV7`: server `routes/session-sync.ts` deliberately
|
|
2847
2955
|
keeps a v4-keyed legacy shell session pushable (it was creatable via the lenient submit face), while
|
|
2848
|
-
crafted-id classes stay shut out. `fork
|
|
2849
|
-
501 without a durable session store.
|
|
2956
|
+
crafted-id classes stay shut out. Since server >= 7.125.0 `fork` / `delete` / `policy` read the SAME one gate (any canonical uuid version);
|
|
2957
|
+
<= 7.124.x those three used `isUuidV7` and only this family was lenient. 501 without a durable session store.
|
|
2850
2958
|
responses:
|
|
2851
2959
|
'200':
|
|
2852
2960
|
description: The manifest, wrapped as `{ manifest }`.
|
|
@@ -3111,6 +3219,14 @@ paths:
|
|
|
3111
3219
|
description: 'server >= 7.52: the file-history envelope (replaces `snapshots`); `null` / absent = none. Every blob it references must already be PUT.'
|
|
3112
3220
|
policy: { type: array, items: { $ref: '#/components/schemas/SessionRulesRecord' } }
|
|
3113
3221
|
anchors: { type: array, items: { $ref: '#/components/schemas/SyncAnchor' } }
|
|
3222
|
+
title:
|
|
3223
|
+
type: [string, 'null']
|
|
3224
|
+
description: >
|
|
3225
|
+
server >= 7.125.0 (contract section 20): the session row's title from the manifest. Judged by the SAME reader as `PATCH /v1/sessions/{sessionId}` cell by cell
|
|
3226
|
+
(a string 1..120 characters with no leading / trailing whitespace; `""` is `400 request.body_shape`; a non-string-non-null / too long / whitespace-edged value is
|
|
3227
|
+
`400 request.field_invalid`) BEFORE any staging is opened, so a refused push leaves nothing behind. `null` CLEARS the destination back to the automatic title. After the
|
|
3228
|
+
commit (and on the `identical` short-circuit arm) the keys that were sent are replayed through the store's one write door. KEY NOT SENT = the destination's two bits stay (older callers).
|
|
3229
|
+
pinned: { type: boolean, description: 'server >= 7.125.0 (contract section 20): the session row''s pin from the manifest; judged by the PATCH reader (non-boolean is `400 request.field_invalid`); not sent = the destination''s pin stays.' }
|
|
3114
3230
|
resolution: { type: string, enum: [overwrite-dst], description: Override a fork/stale loss-of-history conflict. }
|
|
3115
3231
|
logDigest:
|
|
3116
3232
|
type: string
|
|
@@ -4779,6 +4895,45 @@ paths:
|
|
|
4779
4895
|
content:
|
|
4780
4896
|
application/json:
|
|
4781
4897
|
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
|
4898
|
+
get:
|
|
4899
|
+
tags: [leader]
|
|
4900
|
+
operationId: leaderGetByIdempotencyKey
|
|
4901
|
+
x-status: gated
|
|
4902
|
+
summary: Read the leader run an `Idempotency-Key` built (look before you re-submit).
|
|
4903
|
+
description: >
|
|
4904
|
+
server >= 7.125.0 (contract §0.7). Same shape as `GET /v1/runs?idempotencyKey=` for family `leader`: `idempotencyKey` is the RAW key the
|
|
4905
|
+
`POST /v1/leader` carried; the server scopes it by your verified identity exactly as the submit did. 200 = the same read face as
|
|
4906
|
+
`GET /v1/leader/{leaderRunId}` (`LeaderRecord`). A key that built no leader run, and a run that is not yours, are the SAME 404 (no existence
|
|
4907
|
+
oracle). A leader key is a different key from a run key even with the same raw text (resource families do not cross). Not a submit: no billing, no admission gates.
|
|
4908
|
+
The leader face being absent on the deployment is the same 501 `capability.leader_endpoint_required` as the other leader verbs.
|
|
4909
|
+
parameters:
|
|
4910
|
+
- in: query
|
|
4911
|
+
name: idempotencyKey
|
|
4912
|
+
required: true
|
|
4913
|
+
schema: { type: string, minLength: 1, maxLength: 255 }
|
|
4914
|
+
description: 'The RAW `Idempotency-Key` the `POST /v1/leader` carried (1–255 characters, no leading or trailing whitespace — the submit-header rule). Missing, or a malformed shape ⇒ 400 `request.query_invalid`.'
|
|
4915
|
+
responses:
|
|
4916
|
+
'200':
|
|
4917
|
+
description: The leader run's read face — what `GET /v1/leader/{leaderRunId}` returns for that run.
|
|
4918
|
+
content:
|
|
4919
|
+
application/json:
|
|
4920
|
+
schema: { $ref: '#/components/schemas/LeaderRecord' }
|
|
4921
|
+
'400':
|
|
4922
|
+
description: 'errorCode `request.query_invalid` — `idempotencyKey` is missing, or not a well-formed key (empty / over 255 characters / leading or trailing whitespace).'
|
|
4923
|
+
content:
|
|
4924
|
+
application/json:
|
|
4925
|
+
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
|
4926
|
+
'401': { $ref: '#/components/responses/Unauthorized' }
|
|
4927
|
+
'404':
|
|
4928
|
+
description: 'errorCode `not_found.leader_run` — this key (under your identity) built no leader run, or the run is not yours. Same code and sentence for both.'
|
|
4929
|
+
content:
|
|
4930
|
+
application/json:
|
|
4931
|
+
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
|
4932
|
+
'501':
|
|
4933
|
+
description: 'errorCode `capability.leader_endpoint_required` — no leader face on this deployment (the same refusal the other leader verbs give).'
|
|
4934
|
+
content:
|
|
4935
|
+
application/json:
|
|
4936
|
+
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
|
4782
4937
|
|
|
4783
4938
|
/v1/leader/{leaderRunId}:
|
|
4784
4939
|
parameters:
|
|
@@ -6669,8 +6824,11 @@ components:
|
|
|
6669
6824
|
required: false
|
|
6670
6825
|
schema: { type: string }
|
|
6671
6826
|
description: >
|
|
6672
|
-
Submit-class dedupe key (the SDK auto-generates a UUID → retries are safe by default).
|
|
6673
|
-
= 15 min (per-instance).
|
|
6827
|
+
Submit-class dedupe key (the SDK auto-generates a UUID → retries are safe by default). server <= 7.124.x: in-process window
|
|
6828
|
+
= 15 min (per-instance). server >= 7.125.0: the key is a unique key on the run row (scope = identity bit (the verified principal, else the credential family) × resource family × raw key,
|
|
6829
|
+
families `run` / `leader` / `bake`; the endpoint is NOT part of the scope, so `POST /v1/tasks`, `/v1/tasks/stream`, `/v1/runs` and A2A
|
|
6830
|
+
`message/send` share family `run`), valid across replicas and restarts for the run's retention; only concurrent in-flight duplicates are
|
|
6831
|
+
merged in process. A submit is retried by the SDK ONLY when a key exists.
|
|
6674
6832
|
LastEventId:
|
|
6675
6833
|
in: header
|
|
6676
6834
|
name: Last-Event-ID
|
|
@@ -6772,6 +6930,9 @@ components:
|
|
|
6772
6930
|
does NOT silently resolve it to that run: `activeTaskId` may be the caller's own first attempt OR an
|
|
6773
6931
|
unrelated concurrent run on the same session — 409 alone cannot tell (the pinned wire contract Q6). Only triggers when
|
|
6774
6932
|
a `sessionId` is present.
|
|
6933
|
+
server >= 7.125.0 also: `conflict.run_exists` — on a submit (`POST /v1/runs`, `/v1/tasks`, `/v1/tasks/stream`), the `Idempotency-Key` /
|
|
6934
|
+
`body.taskId` points at a run the caller cannot read, or the two keys of one submit point at different rows. The sentence is
|
|
6935
|
+
`a run already exists for this submission's taskId or Idempotency-Key` (code unchanged, sentence new; nothing echoed). Submit again under a new key.
|
|
6775
6936
|
content:
|
|
6776
6937
|
application/json:
|
|
6777
6938
|
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
|
@@ -7027,13 +7188,15 @@ components:
|
|
|
7027
7188
|
type: string
|
|
7028
7189
|
description: >-
|
|
7029
7190
|
[2400] TR-16 (declared 2026-08-02) — CALLER-MINTED uuidv7 task id: the DURABLE second tier of
|
|
7030
|
-
idempotency on POST /v1/runs (`src/http/routes/runs.ts`).
|
|
7191
|
+
idempotency on POST /v1/runs (`src/http/routes/runs.ts`). On server <= 7.124.x the `Idempotency-Key` header's
|
|
7031
7192
|
cache is in-memory and PER-POD, so a dispatch gateway that re-routes a retry to ANOTHER instance
|
|
7032
7193
|
after a network error would start a SECOND run; THIS replay reads the durable run store, so it
|
|
7033
7194
|
survives the re-route and a restart — it is the prerequisite for gateway failover. A hit replays the
|
|
7034
7195
|
original 202 receipt (`{taskId, sessionId, status}`); nothing re-runs, nothing re-bills.
|
|
7035
7196
|
Shape-gated: a non-uuidv7 value is 400 `request.id_invalid`. Owner-gated: a taskId owned by another
|
|
7036
7197
|
principal answers 409 `conflict.run_exists` (no cross-tenant id oracle). Omit ⇒ the server mints one.
|
|
7198
|
+
server >= 7.125.0: `Idempotency-Key` is ALSO a unique key on the run row (contract §0.7), so the two are two keys to one row. Sent together
|
|
7199
|
+
they must point at the same row; a key that points at a row this `taskId` does not name is 409 `conflict.run_exists`.
|
|
7037
7200
|
scenario: { $ref: '#/components/schemas/Scenario' }
|
|
7038
7201
|
sessionId:
|
|
7039
7202
|
type: string
|
|
@@ -8221,6 +8384,37 @@ components:
|
|
|
8221
8384
|
toolCallId: { type: string }
|
|
8222
8385
|
retryAfterMs: { type: integer }
|
|
8223
8386
|
|
|
8387
|
+
RunReplayRecord:
|
|
8388
|
+
description: >
|
|
8389
|
+
server >= 7.125.0 (contract §0.7). The idempotent-REPLAY body of a submit whose `Idempotency-Key` had already built a run row: `RunRecord` (the
|
|
8390
|
+
`GET /v1/runs/{taskId}` read face, same projection) plus `replay: true`. It answers `POST /v1/tasks` (200) and is the `done.result` of
|
|
8391
|
+
`POST /v1/tasks/stream`. 🔴 IT IS NOT A `TaskResult`: a synchronous first answer carries the engine result at the top level (`terminal`, `stats`, `result` as a
|
|
8392
|
+
string); a replay has only the row, so `result` is the NESTED `TaskResult` (or absent while the run has no result), there is no top-level `terminal` / `stats`, and
|
|
8393
|
+
`wiringManifest` / `notice` are absent (the wiring receipt is a live capture that is not stored on the row). `taskId` is the row's id — on the stream leg, when the
|
|
8394
|
+
replay is discovered only after the stream opened (another replica created the row first), the `x-task-id` header and `meta` frame carried an id this leg minted
|
|
8395
|
+
and never created: the id in this body is the real one. Narrow on `replay === true` (the two shapes cannot both match). A same-key duplicate still IN FLIGHT in the same
|
|
8396
|
+
process is merged instead and gets the first answer's bytes with no `replay` key.
|
|
8397
|
+
allOf:
|
|
8398
|
+
- $ref: '#/components/schemas/RunRecord'
|
|
8399
|
+
- type: object
|
|
8400
|
+
required: [replay]
|
|
8401
|
+
|
|
8402
|
+
SubmitRefusalDoneResult:
|
|
8403
|
+
type: object
|
|
8404
|
+
description: >
|
|
8405
|
+
The `done.result` of a submit REFUSED after the SSE stream was already open (`POST /v1/tasks/stream`): the status line is gone, so the refusal travels as the
|
|
8406
|
+
stream's terminal frame in the triple `{status:"failed", errorCode, errorMessage}` — the same code and sentence the JSON refusal body would have carried
|
|
8407
|
+
(server `src/http/routes/tasks.ts`). Two producers: (1) server >= 7.125.0 — the submit's key (or `body.taskId`) turned out, only at row-creation time, to point at a run row
|
|
8408
|
+
the caller cannot read or that is not the one this submit names: `errorCode: "conflict.run_exists"` (contract §0.7; the frame carries `replay: true`); (2) a device-placement
|
|
8409
|
+
refusal (the placement error body's code and sentence). It is NOT an engine result and NOT `ActiveRunConflictDoneResult` (that one is `conflict.session_active_run` and requires
|
|
8410
|
+
`activeTaskId`). Branch on `errorCode`; the sentence is for people.
|
|
8411
|
+
additionalProperties: false
|
|
8412
|
+
required: [status, errorCode, errorMessage]
|
|
8413
|
+
properties:
|
|
8414
|
+
status: { const: failed }
|
|
8415
|
+
errorCode: { type: string, description: 'Open string: `conflict.run_exists` (server >= 7.125.0) or the device-placement refusal code.' }
|
|
8416
|
+
errorMessage: { type: string, description: 'The refusal sentence (display only — never match on it).' }
|
|
8417
|
+
|
|
8224
8418
|
RunReceipt:
|
|
8225
8419
|
type: object
|
|
8226
8420
|
# 封闭(census 轴一,2026-07-30):producer 铸造点 routes/runs.ts 与 idem 重放臂各自逐字写死
|
|
@@ -8249,6 +8443,25 @@ components:
|
|
|
8249
8443
|
description: 'v1 sole value, the same word as settings-schema `SEALED_BOX_ALG`; another alg is a loud per-model refusal.'
|
|
8250
8444
|
publicKeyId: { type: string, description: 'Goes into `sealedApiKey.publicKeyId` (server default mint `exec-<first 12 hex of sha256(raw public key)>`; treat as opaque); the engine opens with EXACTLY this key.' }
|
|
8251
8445
|
publicKey: { type: string, description: 'STANDARD base64 of the 32-byte X25519 public key (44 chars). Public — safe to log.' }
|
|
8446
|
+
WorkspaceSyncCapability:
|
|
8447
|
+
type: object
|
|
8448
|
+
description: >
|
|
8449
|
+
server >= 7.125.0 (contract appendix B.1 / B.1a) — the `GET /v1/capabilities` `workspaceSync` segment: EXACTLY two keys, key-for-key the server's `src/workspace-space.ts`
|
|
8450
|
+
`WorkspaceSyncCapability`. Both are ALWAYS-PRESENT members of the object (B.1a); the object itself is present-iff (see `Capabilities.workspaceSync`).
|
|
8451
|
+
additionalProperties: false
|
|
8452
|
+
required: [persist, keep]
|
|
8453
|
+
properties:
|
|
8454
|
+
persist:
|
|
8455
|
+
type: string
|
|
8456
|
+
enum: [space, off]
|
|
8457
|
+
description: >
|
|
8458
|
+
The operator knob `WORKSPACE_PERSIST` (default `off` — persistence is opt-in), a TWO-WORD closed set. `space` = the operator switched it on: the k8s `/workspace` persists across runs per session occupant. `off` = the snapshot
|
|
8459
|
+
store IS wired but persistence is not switched on (the default, or the operator switched it off): a fresh workspace every run — NOT the same as the whole object being absent (absent = no such facility on this deployment).
|
|
8460
|
+
keep:
|
|
8461
|
+
type: integer
|
|
8462
|
+
minimum: 1
|
|
8463
|
+
maximum: 50
|
|
8464
|
+
description: 'Snapshots kept per space (the session occupant) (`WORKSPACE_SNAPSHOT_KEEP`, an integer in [1, 50], default 3); older ones are deleted after each successful save.'
|
|
8252
8465
|
StoreCapability:
|
|
8253
8466
|
type: object
|
|
8254
8467
|
description: >
|
|
@@ -8413,6 +8626,13 @@ components:
|
|
|
8413
8626
|
taskId: { type: string }
|
|
8414
8627
|
sessionId: { type: string }
|
|
8415
8628
|
status: { $ref: '#/components/schemas/RunStatus' }
|
|
8629
|
+
replay:
|
|
8630
|
+
const: true
|
|
8631
|
+
description: >
|
|
8632
|
+
server >= 7.125.0 (additive; contract §0.7). Present ONLY on the IDEMPOTENT-REPLAY body of a submit — `POST /v1/tasks` 200, and the same body as
|
|
8633
|
+
`done.result` on `POST /v1/tasks/stream` (see `RunReplayRecord`) — where it is always `true`: the submit's key had already built this run row, so the
|
|
8634
|
+
answer is the row's read face plus this marker, NOT the first answer's engine result. ABSENT on `GET /v1/runs/{taskId}` and
|
|
8635
|
+
`GET /v1/runs?idempotencyKey=` (those are the read face itself) and never `false`. Branch on `replay === true`; the outcome is in `result`.
|
|
8416
8636
|
msSinceLastActivity:
|
|
8417
8637
|
type: integer
|
|
8418
8638
|
description: >
|
|
@@ -8741,6 +8961,13 @@ components:
|
|
|
8741
8961
|
usedMicroUsd: { type: integer }
|
|
8742
8962
|
limitMicroUsd: { type: integer }
|
|
8743
8963
|
retryAfterSec: { type: integer }
|
|
8964
|
+
windowResetsAt:
|
|
8965
|
+
type: string
|
|
8966
|
+
format: date-time
|
|
8967
|
+
description: >
|
|
8968
|
+
server >= 7.125.0 (additive). The end of the current quota window (ISO 8601) — the same instant `GET /v1/usage` reports as
|
|
8969
|
+
`UsageInfo.windowResetsAt`. `retryAfterSec` and the `Retry-After` header stay as they were and share its clock read.
|
|
8970
|
+
Absent = an older server.
|
|
8744
8971
|
|
|
8745
8972
|
TraceTurn:
|
|
8746
8973
|
type: object
|
|
@@ -9909,6 +10136,10 @@ components:
|
|
|
9909
10136
|
(`DB_BACKEND=memory`, multi-tenant bare boot) this bit is now false and the card and the RPC both answer 404 (7.115.x:
|
|
9910
10137
|
true, card 200, every `message/send` / `tasks/get` a JSON-RPC `-32004`); the startup log carries a warn
|
|
9911
10138
|
`a2a_serve_not_served`. The old "the server deliberately does not fold the run store into this bit" note is retired.
|
|
10139
|
+
server >= 7.125.0 (contract §0.7): an A2A `message/send` shares the submit idempotency scope with the HTTP submits (family `run`; the key is derived from the
|
|
10140
|
+
message's `messageId`, its content, `contextId` and skill id, and scoped by the caller identity) — a repeat that finds the existing run answers that task's protocol object immediately (it never enters the
|
|
10141
|
+
`blocking` wait), and a hit the caller cannot read, or that names a different run, is a JSON-RPC `-32602` (Invalid params) error, the same refusal the HTTP
|
|
10142
|
+
faces give as 409 `conflict.run_exists`.
|
|
9912
10143
|
agentRoster:
|
|
9913
10144
|
type: boolean
|
|
9914
10145
|
description: >
|
|
@@ -10075,6 +10306,20 @@ components:
|
|
|
10075
10306
|
do NOT seal — use `apiKeyEnv`, or fix the custody directory and restart. Absent is a missing key, never `null`.
|
|
10076
10307
|
🔴 ONE STORE PER HOST (`<data root>/sealed-key/`): replicas that do not share that directory answer different
|
|
10077
10308
|
keys, and a ciphertext sealed against replica A's key is `stale_ciphertext` on replica B.
|
|
10309
|
+
workspaceSync:
|
|
10310
|
+
allOf: [{ $ref: '#/components/schemas/WorkspaceSyncCapability' }]
|
|
10311
|
+
description: >
|
|
10312
|
+
server >= 7.125.0 (contract appendix B.1 / B.1a) — WORKSPACE KEPT ACROSS RUNS: the k8s lane's `/workspace` is stored in object storage per session OCCUPANT (the session incarnation: delete the session and re-register the same id = a NEW space, starting from an empty directory). Each run's end
|
|
10313
|
+
snapshots the workspace into `<prefix>/<incarnation>/space/` (a sub-prefix this lane owns; the park-suspend snapshots live outside it and are never pruned), moves the `space/latest` pointer and keeps only
|
|
10314
|
+
the newest `keep`; the next run's start (when it connects the sandbox) restores from `latest`. A replica that cannot read the session occupant neither restores nor saves for that run (an operator line) and does NOT fall back to the session id.
|
|
10315
|
+
A shell / center renders "work files kept across turns" only when `persist` is `"space"` (the default is `off`; persistence is opt-in).
|
|
10316
|
+
🔴 PRESENT-IFF — one of the three per-key exceptions on this surface (with `sealedKey` and `centerWiring`, contract appendix B.1): the facility = the k8s lane AND a snapshot store (the three
|
|
10317
|
+
`MINIO_*` keys). ABSENT is a missing key, never `false` / `null` / `{}`, and means one of TWO things with the SAME action: an older server (< 7.125.0, does not know this bit), or a deployment that
|
|
10318
|
+
is not the k8s lane / has no snapshot store (the same body's `version` tells them apart) — either way do NOT promise "last run's files are still there"; treat each run's workspace as an empty directory.
|
|
10319
|
+
`persist: "off"` means the store is present and the capability is present but nothing is retained today (the default, or the operator switched it off) — different from absent. Covers `/workspace` only (process / memory state does not cross runs).
|
|
10320
|
+
FAILURE IS LOUD: failing to read `latest` or the commit marker, a `latest` that points at a missing snapshot object, or a restore that fails midway fails THAT run's sandbox connect (never a silent empty
|
|
10321
|
+
directory; a partial directory is not handed to tools and is not snapshotted back); a failed end-of-run snapshot puts the residual key `workspaceSnapshot: "failed"` on the fleet terminal `task` frame
|
|
10322
|
+
(`FleetTaskRow.workspaceSnapshot`), and the next run restores the PREVIOUS `latest`.
|
|
10078
10323
|
background:
|
|
10079
10324
|
type: object
|
|
10080
10325
|
additionalProperties: true
|
|
@@ -11322,15 +11567,23 @@ components:
|
|
|
11322
11567
|
Per-principal cost/quota snapshot (GET /v1/usage; shape audited from source). Computed from
|
|
11323
11568
|
the REQUEST principal. When no quota window is configured → enabled=false + ONLY windowSec/maxTask*
|
|
11324
11569
|
(the amount/limit fields are ABSENT) — render must tolerate the gap.
|
|
11570
|
+
server >= 7.125.0: an `enabled:true` body ALWAYS carries `windowResetsAt` (the end of the current quota window),
|
|
11571
|
+
zero spend and the limit-0 switched-off sentinel included; an `enabled:false` body is unchanged and has no such key.
|
|
11325
11572
|
# 🔴 封闭 + required 补铸造点(census 批2 第二段,2026-07-30)。两条腿都是逐字字面量
|
|
11326
11573
|
# (`routes/observability.ts` —— 无配额臂 4 键 / 有配额臂 11 键),11 个属性就是并集。
|
|
11574
|
+
# server 7.125.0:有配额臂 +`windowResetsAt` = 12 键,并集 12 个属性(无配额臂 4 键不变)。
|
|
11327
11575
|
# required 从 `[enabled]` 抬到四键:`windowSec`/`maxTaskCostUsd`/`maxTaskTokens` 在**两条腿上都**
|
|
11328
11576
|
# 无条件发送(config 直读),只列 `enabled` 是把恒在键写成了可缺 —— 消费方因此得写永远为真的判空。
|
|
11329
11577
|
required: [enabled, windowSec, maxTaskCostUsd, maxTaskTokens]
|
|
11330
11578
|
additionalProperties: false
|
|
11331
11579
|
properties:
|
|
11332
11580
|
enabled: { type: boolean, description: 'A cost-quota WINDOW is configured (false ⇒ single-task ceilings only).' }
|
|
11333
|
-
windowSec:
|
|
11581
|
+
windowSec:
|
|
11582
|
+
type: integer
|
|
11583
|
+
description: >
|
|
11584
|
+
Quota window length in seconds (e.g. 86400 = daily). server >= 7.125.0: on EVERY deployment the window is a
|
|
11585
|
+
clock-aligned bucket (bucket number = `floor(now / windowSec)`, a daily window rolls at UTC midnight) — not a window that
|
|
11586
|
+
starts at the principal's first spend and not a sliding one; read the roll instant from `windowResetsAt`.
|
|
11334
11587
|
usedMicroUsd: { type: integer, description: 'Spent in the current window (micro-USD = 1e-6 USD).' }
|
|
11335
11588
|
usedUsd: { type: number, description: 'Spent in the current window (USD; convenience projection).' }
|
|
11336
11589
|
limitMicroUsd: { type: integer, description: 'Window quota ceiling (micro-USD).' }
|
|
@@ -11338,6 +11591,15 @@ components:
|
|
|
11338
11591
|
remainingMicroUsd: { type: integer, description: 'Window remaining (limit - used, floored at 0).' }
|
|
11339
11592
|
overLimit: { type: boolean, description: 'Window quota exceeded (new tasks blocked until retryAfterSec).' }
|
|
11340
11593
|
retryAfterSec: { type: integer, description: 'Suggested wait when overLimit.' }
|
|
11594
|
+
windowResetsAt:
|
|
11595
|
+
type: string
|
|
11596
|
+
format: date-time
|
|
11597
|
+
description: >
|
|
11598
|
+
server >= 7.125.0 (additive). The END of the current quota window (ISO 8601) — the instant the clock-aligned bucket rolls
|
|
11599
|
+
over and `usedMicroUsd` starts again from zero. ALWAYS present when `enabled` is true (the window is a clock-aligned bucket,
|
|
11600
|
+
so a zero-spend principal and the limit-0 switched-off sentinel report it too — the window length is a configured fact); ABSENT
|
|
11601
|
+
when `enabled` is false. ABSENT on an `enabled:true` body = an older server, NOT "the window does not roll". The same instant rides
|
|
11602
|
+
the 429 `limit.cost_quota_exceeded` body (`QuotaError.windowResetsAt`), derived from the same clock read as `retryAfterSec`.
|
|
11341
11603
|
maxTaskCostUsd: { type: number, description: 'Single-task cost hard ceiling (USD; independent of the window, always present).' }
|
|
11342
11604
|
maxTaskTokens: { type: integer, description: 'Single-task token hard ceiling (independent of the window).' }
|
|
11343
11605
|
|
|
@@ -12007,6 +12269,17 @@ components:
|
|
|
12007
12269
|
workflow `wa*` agent, and the TOP-LEVEL run row — has no generation concept at all. Absent means "no
|
|
12008
12270
|
such concept", NOT "generation 1"; a consumer that reads absence as 1 will misread a late first frame
|
|
12009
12271
|
as a revive.
|
|
12272
|
+
workspaceSnapshot:
|
|
12273
|
+
type: string
|
|
12274
|
+
enum: [failed]
|
|
12275
|
+
description: >
|
|
12276
|
+
server >= 7.125.0 (contract appendix H.3b), ADDITIVE / tolerate-absent, a TERMINAL top-level run frame's residual key (`completed` / `failed` / `killed`; a parked frame NEVER carries it).
|
|
12277
|
+
Has a source only while the k8s lane keeps the workspace across runs (`capabilities.workspaceSync.persist: "space"`). A SINGLE-MEMBER CLOSED SET, the one word `failed`:
|
|
12278
|
+
the workspace snapshot at the end of THIS run was NOT saved (the upload failed, or the `latest` pointer write failed). The pod is deleted anyway; `latest` still points at the previous snapshot, so the next
|
|
12279
|
+
run of the same session restores the PREVIOUS one and this run's changes to `/workspace` are lost. Render "this turn's work files were not saved" — NOT as a run failure (`status` is still the run's own
|
|
12280
|
+
terminal state). ABSENT = saved / persistence off / not the k8s lane / the run never touched a sandbox / an older server / OR the save took longer than the engine's 15-second teardown bound and its outcome
|
|
12281
|
+
arrived after the terminal frame — so absence must NEVER be read as "definitely saved": it only says "no failure to report". Source: the save outcome the server notes before the engine hands back its result
|
|
12282
|
+
(keyed by the run's session id), reported once. ONLY the outcome that settled before THIS run's terminal frame is carried: one that settles after the teardown bound stays an operator log line and is never attached to the NEXT run's terminal frame of the same session. The `?session=` scoped (unattributed-row) projection keeps this key (a structural fact, zero conversation content).
|
|
12010
12283
|
retiredBy:
|
|
12011
12284
|
# 🔴 NO `enum` (codex R1-F3, verified): the description below REQUIRES open-on-read, and the SDK type is
|
|
12012
12285
|
# open (`"reconcile" | (string & {})`). A closed enum would make a strict generated client reject a
|
|
@@ -13154,8 +13427,36 @@ components:
|
|
|
13154
13427
|
taskId: { type: string, description: The later run on whose first beat the notice was emitted. }
|
|
13155
13428
|
messages:
|
|
13156
13429
|
type: array
|
|
13430
|
+
description: >
|
|
13431
|
+
The engine's message rows (opaque here; their shapes are the engine's). server >= 7.125.0 (contract section 20): the ONE key the server adds itself is the
|
|
13432
|
+
rewind handle `taskId` on a `role: "user"` row — the `taskId` of the run that submitted that prompt, to be handed to `resumeAt` (rewind =
|
|
13433
|
+
the existing `POST /v1/runs` / `POST /v1/tasks` with `{ sessionId, resumeAt: <taskId> }`, no new verb). It rides EVERY way of fetching the rows (whole read /
|
|
13434
|
+
`tail` / `before` / `blobCap` / `?message=N`). PRESENT-IFF the anchor store holds EXACTLY ONE row for that user entry whose owner is the reader (the same
|
|
13435
|
+
predicate `resumeAt` resolves with, so a handle on a row always resolves). ABSENT = no such anchor: an older entry (anchors are written only from the point
|
|
13436
|
+
they were introduced; no backfill), an anchor owned by someone else (mixed-owner session), the same entry named by two anchor rows (not picked), or a prompt
|
|
13437
|
+
submitted through the non-streaming `POST /v1/tasks` (only the durable legs — `POST /v1/runs`, `POST /v1/tasks/stream`, the resume leg — write user-message
|
|
13438
|
+
anchors; resuming at that run's `taskId` is 404 `resume_at.unknown_event`, the same fact as the key being absent). A later user message (steer) inside one run
|
|
13439
|
+
moves that run's anchor to the last-submitted one, so the handle appears on that row. assistant / toolResult / other-role rows never carry it. The value is a
|
|
13440
|
+
server-minted identifier, verbatim (not redacted). Older servers (<= 7.124.x) never emit it.
|
|
13157
13441
|
items: {}
|
|
13158
13442
|
|
|
13443
|
+
SessionUserMessageRow:
|
|
13444
|
+
type: object
|
|
13445
|
+
description: >
|
|
13446
|
+
The `role: "user"` arm of a `SessionRecord.messages` row — the NARROWING TARGET for reading the rewind handle (server >= 7.125.0, contract section 20). `messages` rows are the
|
|
13447
|
+
engine's own shapes and stay open/opaque; the only key the server itself adds is `taskId` on a user row. OPEN object: every other key is the engine's.
|
|
13448
|
+
required: [role]
|
|
13449
|
+
additionalProperties: true
|
|
13450
|
+
properties:
|
|
13451
|
+
role: { const: user }
|
|
13452
|
+
taskId:
|
|
13453
|
+
type: string
|
|
13454
|
+
description: >
|
|
13455
|
+
The rewind handle: the `taskId` of the run that submitted this prompt, handed to `resumeAt` (`POST /v1/runs` / `POST /v1/tasks` with `{ sessionId, resumeAt: <taskId> }`).
|
|
13456
|
+
PRESENT-IFF the anchor store holds EXACTLY ONE row for this user entry owned by the reader — the same predicate `resumeAt` resolves with, so a handle on a row always
|
|
13457
|
+
resolves. ABSENT = an older entry (no backfill) / an anchor owned by someone else / the same entry named by two anchor rows / a prompt submitted through the non-streaming
|
|
13458
|
+
`POST /v1/tasks` (no anchor is written there; resuming at that run's `taskId` is 404 `resume_at.unknown_event`) / an older server (<= 7.124.x). A server-minted identifier, verbatim.
|
|
13459
|
+
|
|
13159
13460
|
SessionSummary:
|
|
13160
13461
|
type: object
|
|
13161
13462
|
description: >
|
|
@@ -13186,6 +13487,9 @@ components:
|
|
|
13186
13487
|
Auto-generated session summary title (service-side `session-titler`: one cheap model call kicked off in
|
|
13187
13488
|
the background after the first submit, idempotent via an IS-NULL gate). Genuinely `null` until it lands,
|
|
13188
13489
|
so render a fallback rather than waiting on it.
|
|
13490
|
+
server >= 7.125.0 (contract section 20): ONE column with TWO writers — the user's string (`PATCH /v1/sessions/{sessionId}`, unconditional;
|
|
13491
|
+
`null` clears it back) wins, otherwise the auto-titler's (fills ONLY a NULL column), otherwise `null`. So read it as "the user's title, else the
|
|
13492
|
+
automatic one, else null" — a reader that took it as "always the automatic title" must change. <= 7.124.x: only the auto-titler writes it.
|
|
13189
13493
|
tempo:
|
|
13190
13494
|
type: string
|
|
13191
13495
|
enum: [active, idle, blocked]
|
|
@@ -13193,6 +13497,32 @@ components:
|
|
|
13193
13497
|
server ≥7.91.0 (additive, absent on older servers): the session's activity word, derived from its most
|
|
13194
13498
|
recent run's status by the ONE mapping table the peer directory also reads (running → active;
|
|
13195
13499
|
suspended / needs_review → blocked; no non-terminal run → idle).
|
|
13500
|
+
pinned:
|
|
13501
|
+
type: boolean
|
|
13502
|
+
description: >
|
|
13503
|
+
server >= 7.125.0 (contract section 20): the user's pin (written by `PATCH /v1/sessions/{sessionId}`), default `false`. ALWAYS present on
|
|
13504
|
+
>= 7.125.0 (a session's state, not a capability — NOT present-iff); a lister face with no session metadata (the in-memory run-store
|
|
13505
|
+
aggregate) always answers `false`. ABSENT = an older server (<= 7.124.x) — read as "not pinned". Not in `required` for that reason only.
|
|
13506
|
+
The list order is unchanged (recency by `lastActivityAt`); the client groups the pinned section. Renaming / pinning does not move `lastActivityAt`.
|
|
13507
|
+
|
|
13508
|
+
SessionPatchBody:
|
|
13509
|
+
type: object
|
|
13510
|
+
description: >
|
|
13511
|
+
`PATCH /v1/sessions/{sessionId}` body (server >= 7.125.0, contract section 20) — a CLOSED set of two keys, at least one: any other key, an empty body or a
|
|
13512
|
+
non-object is `400 request.body_shape`. `title`: a string renames (1..120 CHARACTERS counted as Unicode characters = the storage column width, no leading /
|
|
13513
|
+
trailing whitespace, stored verbatim — not rewritten, not redacted); `null` CLEARS it back to the automatic title (the next submit lets the auto-titler fill
|
|
13514
|
+
it again). `""` is NOT a clear — it is `400 request.body_shape`. A string of the wrong form / a non-string-non-null `title` / a non-boolean `pinned` is
|
|
13515
|
+
`400 request.field_invalid`.
|
|
13516
|
+
additionalProperties: false
|
|
13517
|
+
minProperties: 1
|
|
13518
|
+
properties:
|
|
13519
|
+
title:
|
|
13520
|
+
type: [string, 'null']
|
|
13521
|
+
minLength: 1
|
|
13522
|
+
maxLength: 120
|
|
13523
|
+
pattern: '^\S([\s\S]*\S)?$'
|
|
13524
|
+
description: 'A string = rename (1..120 characters, no leading / trailing whitespace); `null` = clear back to the automatic title; `""` is a 400 (the only spelling of "clear" is `null`).'
|
|
13525
|
+
pinned: { type: boolean, description: 'The pin (default `false` on a row that never set it).' }
|
|
13196
13526
|
|
|
13197
13527
|
SessionWindow:
|
|
13198
13528
|
type: object
|
|
@@ -13220,7 +13550,7 @@ components:
|
|
|
13220
13550
|
type: ['string', 'null']
|
|
13221
13551
|
description: 'the compaction floor this index is relative to, when one applies.'
|
|
13222
13552
|
index: { type: integer, description: 'the requested message index (echoed).' }
|
|
13223
|
-
message: { description: 'OPEN — the message payload as stored; shape belongs to the transcript format, not to this envelope.' }
|
|
13553
|
+
message: { description: 'OPEN — the message payload as stored; shape belongs to the transcript format, not to this envelope. server >= 7.125.0: a `role: "user"` message carries the same present-iff rewind handle `taskId` as on the whole-record read (see `SessionRecord.messages`).' }
|
|
13224
13554
|
|
|
13225
13555
|
PromptEpoch:
|
|
13226
13556
|
type: object
|
|
@@ -13645,13 +13975,15 @@ components:
|
|
|
13645
13975
|
snapshot: entry IDS (oldest-first, NOT payloads) + the file-history envelope + policy + anchors + leaf.
|
|
13646
13976
|
🔴 server >= 7.52 (S-15, design/381; re-read byte for byte against 7.116.0): the key `snapshots` (per-snapshot
|
|
13647
13977
|
relPath→blobHash, the retired E19 whole-tree snapshot epoch) is NO LONGER SENT — the server's `exportSessionManifest` mints
|
|
13648
|
-
`{ sessionId, entryIds, entryCount, leafId, fileHistory, policy, anchors }`
|
|
13978
|
+
`{ sessionId, entryIds, entryCount, leafId, fileHistory, policy, anchors }` (server >= 7.125.0 adds the session row's two writable bits `title` / `pinned`,
|
|
13979
|
+
present-iff the session is a row of the list face — see the two properties) and `fileHistory` (core's versioned
|
|
13649
13980
|
`FileHistoryExport` envelope; `null` = no history / the source store cannot export) replaces it. <= 7.51 servers still send
|
|
13650
13981
|
`snapshots` and no `fileHistory`. The blob BYTES never ride the manifest: content-addressed, via the history blob faces
|
|
13651
13982
|
(`GET …/sync/history/blobs/{hash}` / `PUT …/sync/blobs/{hash}`; probe `capabilities.sessionSyncFileHistory` on BOTH ends).
|
|
13652
13983
|
The SDK's `manifest()` fills `snapshots: []` when the wire omits it.
|
|
13653
13984
|
# 封闭(census 批2 第三段,2026-07-30;16.2.0 按 7.116.0 发布字节重核):铸造点 `session-sync.ts`(exportSessionManifest 尾部
|
|
13654
13985
|
# 字面量)恰是 `{ sessionId, entryIds, entryCount, leafId, fileHistory, policy, anchors }` 7 键(≥7.52),无 `snapshots`;
|
|
13986
|
+
# server ≥7.125.0 另有 `title` / `pinned` 两键(会话行可写位,present-iff 会话是列表面上的行;条件展开,不进 required);
|
|
13655
13987
|
# ≤7.51 老 server 发 `snapshots`、无 `fileHistory` ⇒ 两键都不进 required(支持底线 3.0.0)。
|
|
13656
13988
|
additionalProperties: false
|
|
13657
13989
|
required: [sessionId, entryIds, entryCount, leafId, policy, anchors]
|
|
@@ -13668,6 +14000,15 @@ components:
|
|
|
13668
14000
|
description: 'server >= 7.52: the per-edited-file rewind history envelope; `null` = no history / the source store cannot export; key ABSENT = a <= 7.51 server (that generation sends `snapshots`).'
|
|
13669
14001
|
policy: { type: array, items: { $ref: '#/components/schemas/SessionRulesRecord' } }
|
|
13670
14002
|
anchors: { type: array, items: { $ref: '#/components/schemas/SyncAnchor' } }
|
|
14003
|
+
title:
|
|
14004
|
+
type: [string, 'null']
|
|
14005
|
+
description: >
|
|
14006
|
+
server >= 7.125.0 (contract section 20): the session row's title, read through the LIST ROW's one projection (the same value as `SessionSummary.title`: the
|
|
14007
|
+
user's string, else the auto-titler's). `null` = no title — and a PUSH that replays it (`sync/import` Phase A `title: null`) CLEARS the destination back to the
|
|
14008
|
+
automatic title, the same law as `PATCH`. PRESENT-IFF the session is a row of the list face (every ordinary session; a placed delegation-transcript
|
|
14009
|
+
session has no writable row bits, so BOTH keys are absent there). ABSENT also = an older server (<= 7.124.x). Not part of the `basis` / `payloadVerified` judgement
|
|
14010
|
+
(those two speak only for the conversation log).
|
|
14011
|
+
pinned: { type: boolean, description: 'server >= 7.125.0 (contract section 20): the session row''s pin (same value as `SessionSummary.pinned`; present-iff exactly as `title` above).' }
|
|
13671
14012
|
|
|
13672
14013
|
FileHistoryExport:
|
|
13673
14014
|
type: object
|
|
@@ -17892,12 +18233,18 @@ components:
|
|
|
17892
18233
|
Event_done:
|
|
17893
18234
|
type: object
|
|
17894
18235
|
description: >
|
|
17895
|
-
The stream's terminal frame. `result` is a DISCRIMINATED UNION of
|
|
18236
|
+
The stream's terminal frame. `result` is a DISCRIMINATED UNION of real shapes (契约调和车1 E,
|
|
17896
18237
|
2026-08-02) — do NOT assume the full TaskResult:
|
|
17897
18238
|
· `TaskResult` — the normal terminal (output string at `.result`, stats at `.stats`), service Drift 4;
|
|
17898
18239
|
· `ActiveRunConflictDoneResult` — the SSE lane's REJECTION terminal: when the session already has an
|
|
17899
18240
|
active run, the submit's 409 is encoded as this done frame (`src/http/routes/tasks.ts`), and it
|
|
17900
|
-
carries NO `taskId` / `sessionId` / `stats
|
|
18241
|
+
carries NO `taskId` / `sessionId` / `stats`;
|
|
18242
|
+
· `RunReplayRecord` (server >= 7.125.0, contract §0.7) — the IDEMPOTENT-REPLAY terminal of `POST /v1/tasks/stream`: the submit's key had already built a run row, so
|
|
18243
|
+
`result` is that row's read face plus `replay: true` — NOT an engine result (no top-level `terminal` / `stats`; the outcome is the nested `result`). The frame carries
|
|
18244
|
+
`replay: true` as well. (A same-key duplicate still in flight in the same process is merged instead: the frame has `replay: true` but `result` is the first answer's
|
|
18245
|
+
`TaskResult` verbatim — so the FRAME's `replay` does not tell the shapes apart; `result.replay` does.)
|
|
18246
|
+
· `SubmitRefusalDoneResult` — a submit refused after the stream was already open, as `{status:"failed", errorCode, errorMessage}` (server >= 7.125.0: `conflict.run_exists` when
|
|
18247
|
+
the replay is discovered at row-creation time and the caller cannot read that row; also the device-placement refusal). Not an engine result; carries no `taskId` / `stats`.
|
|
17901
18248
|
Narrow in TWO steps: `stats` present (an engine result) vs absent (the conflict envelope) — or match
|
|
17902
18249
|
`errorCode === "conflict.session_active_run"` — and then, among the engine results, `"terminal" in result`
|
|
17903
18250
|
(the current cause form) vs absent (`LegacyPlaneTaskResult`, the flat bytes an older generation left in
|
|
@@ -17916,14 +18263,17 @@ components:
|
|
|
17916
18263
|
- $ref: '#/components/schemas/TaskResult'
|
|
17917
18264
|
- $ref: '#/components/schemas/ActiveRunConflictDoneResult'
|
|
17918
18265
|
- $ref: '#/components/schemas/LegacyPlaneTaskResult'
|
|
18266
|
+
- $ref: '#/components/schemas/RunReplayRecord'
|
|
18267
|
+
- $ref: '#/components/schemas/SubmitRefusalDoneResult'
|
|
17919
18268
|
replay:
|
|
17920
18269
|
type: boolean
|
|
17921
18270
|
description: >
|
|
17922
18271
|
契约调和车1 F(2026-08-02;server `src/http/routes/tasks.ts` 的两处铸造点)— `true`
|
|
17923
18272
|
marks the IDEMPOTENT-REPLAY leg: this terminal was NOT produced by a live run of your submit. Either
|
|
17924
|
-
|
|
17925
|
-
|
|
17926
|
-
|
|
18273
|
+
your call was deduplicated into a concurrent identical submit still in flight in the same process (then `result` is the original
|
|
18274
|
+
run's terminal, verbatim, and you received no live events at all), or — server >= 7.125.0 — the `Idempotency-Key` had already built a run
|
|
18275
|
+
row and `result` is that row's read face plus `replay: true` (`RunReplayRecord`; NOT an engine result). The frame's flag does not
|
|
18276
|
+
tell the two apart; `result.replay === true` does. ABSENT on every live leg (never `false`) — a UI can use it to say "replayed"
|
|
17927
18277
|
instead of implying the work just ran twice.
|
|
17928
18278
|
|
|
17929
18279
|
ActiveRunConflictDoneResult:
|