@sema-agent/sdk 12.0.0 โ†’ 13.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.
Files changed (79) hide show
  1. package/README.md +110 -0
  2. package/dist/control-types.d.ts +2 -1
  3. package/dist/control-types.d.ts.map +1 -1
  4. package/dist/control-types.js.map +1 -1
  5. package/dist/errors.d.ts +121 -9
  6. package/dist/errors.d.ts.map +1 -1
  7. package/dist/errors.js +112 -14
  8. package/dist/errors.js.map +1 -1
  9. package/dist/events.d.ts +21 -1
  10. package/dist/events.d.ts.map +1 -1
  11. package/dist/events.js.map +1 -1
  12. package/dist/health.d.ts +3 -1
  13. package/dist/health.d.ts.map +1 -1
  14. package/dist/health.js +7 -0
  15. package/dist/health.js.map +1 -1
  16. package/dist/index.d.ts +2 -2
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +1 -1
  19. package/dist/index.js.map +1 -1
  20. package/dist/registry/client.d.ts +1 -0
  21. package/dist/registry/client.d.ts.map +1 -1
  22. package/dist/registry/client.js +3 -3
  23. package/dist/registry/client.js.map +1 -1
  24. package/dist/registry/errors.d.ts +27 -4
  25. package/dist/registry/errors.d.ts.map +1 -1
  26. package/dist/registry/errors.js +41 -1
  27. package/dist/registry/errors.js.map +1 -1
  28. package/dist/registry/index.d.ts +7 -1
  29. package/dist/registry/index.d.ts.map +1 -1
  30. package/dist/registry/index.js +7 -1
  31. package/dist/registry/index.js.map +1 -1
  32. package/dist/registry/me-config.d.ts +5 -2
  33. package/dist/registry/me-config.d.ts.map +1 -1
  34. package/dist/registry/me-config.js +21 -14
  35. package/dist/registry/me-config.js.map +1 -1
  36. package/dist/registry/scope-config-feedback.d.ts +6 -2
  37. package/dist/registry/scope-config-feedback.d.ts.map +1 -1
  38. package/dist/registry/scope-config-feedback.js +18 -9
  39. package/dist/registry/scope-config-feedback.js.map +1 -1
  40. package/dist/registry/scopes-effective.d.ts.map +1 -1
  41. package/dist/registry/scopes-effective.js +2 -3
  42. package/dist/registry/scopes-effective.js.map +1 -1
  43. package/dist/registry-conflict.d.ts +32 -0
  44. package/dist/registry-conflict.d.ts.map +1 -0
  45. package/dist/registry-conflict.js +37 -0
  46. package/dist/registry-conflict.js.map +1 -0
  47. package/dist/resources/control/config.d.ts +12 -6
  48. package/dist/resources/control/config.d.ts.map +1 -1
  49. package/dist/resources/control/config.js +10 -4
  50. package/dist/resources/control/config.js.map +1 -1
  51. package/dist/resources/control/versioning.d.ts +6 -2
  52. package/dist/resources/control/versioning.d.ts.map +1 -1
  53. package/dist/resources/control/versioning.js +4 -1
  54. package/dist/resources/control/versioning.js.map +1 -1
  55. package/dist/resources/session-sync.d.ts +9 -0
  56. package/dist/resources/session-sync.d.ts.map +1 -1
  57. package/dist/resources/session-sync.js.map +1 -1
  58. package/dist/resources/sessions.d.ts +11 -1
  59. package/dist/resources/sessions.d.ts.map +1 -1
  60. package/dist/resources/sessions.js +11 -1
  61. package/dist/resources/sessions.js.map +1 -1
  62. package/dist/resources/tasks.d.ts +18 -3
  63. package/dist/resources/tasks.d.ts.map +1 -1
  64. package/dist/resources/tasks.js +16 -1
  65. package/dist/resources/tasks.js.map +1 -1
  66. package/dist/resources/tool-approvals.d.ts +24 -2
  67. package/dist/resources/tool-approvals.d.ts.map +1 -1
  68. package/dist/resources/tool-approvals.js.map +1 -1
  69. package/dist/transport.d.ts +20 -0
  70. package/dist/transport.d.ts.map +1 -1
  71. package/dist/transport.js +35 -3
  72. package/dist/transport.js.map +1 -1
  73. package/dist/types.d.ts +159 -32
  74. package/dist/types.d.ts.map +1 -1
  75. package/dist/types.js +22 -1
  76. package/dist/types.js.map +1 -1
  77. package/openapi.yaml +293 -31
  78. package/package.json +2 -2
  79. package/registry-openapi.yaml +17 -4
package/openapi.yaml CHANGED
@@ -413,6 +413,15 @@ paths:
413
413
  description: >
414
414
  Synchronous run-to-completion. Auto `Idempotency-Key` (โ†’ retry-safe). Supports `verify`/`cascade`.
415
415
  For anything that may take minutes or hit a HITL gate, use `/v1/runs` instead (this call blocks).
416
+ ๐Ÿ”ด PREPARE-TIME REFUSALS ARE TRIAGED ON WORK DONE (server >= 7.104.0 / S-271, one rule for the whole table): a
417
+ prepare-time refusal code that HAS a status in the server's per-code triage table (`resume_at.*`, governance codes,
418
+ `rewind.store_unconfigured` / `rewind.capture_disabled` / `rewind.child_scope_unsupported`, the registered `config.*`
419
+ codes incl. the three roster-bound ones, `corrupt_session`; codes outside the table, e.g. `rewind.invalid_spec`, stay
420
+ 200 with a failed body) answers its 4xx / 5xx ERROR BODY only when the whole submission did ZERO WORK (cumulative
421
+ `stats.turns === 0` AND `stats.tokens === 0` โ€” the common case: nothing billed, not idempotency-cached). When a
422
+ `verify` / `cascade` run's earlier tier already ran turns and only the last tier is refused, the SAME code arrives
423
+ as 200 with a failed body (`terminal.code`; billed; a same-key resend replays). Multi-tier callers must read both
424
+ forms. On a park the 200 body is the result itself plus the three poll-receipt keys (`SyncSubmitResult`, S-173).
416
425
  parameters:
417
426
  - $ref: '#/components/parameters/IdempotencyKey'
418
427
  requestBody:
@@ -422,15 +431,49 @@ paths:
422
431
  schema: { $ref: '#/components/schemas/TaskRequest' }
423
432
  responses:
424
433
  '200':
425
- description: Task result.
434
+ description: >-
435
+ The task result (`SyncSubmitResult`): the sealed + redacted engine result; on a park (`terminal.kind:"paused"`)
436
+ it additionally carries the poll receipt `{taskId, sessionId, status}` with `status` โˆˆ {suspended, needs_review}
437
+ (server >= 7.104.0 / S-173 โ€” before that a park answered ONLY those three keys). Terminal bodies carry no `status`
438
+ on >= 7.64; a server <= 7.63 answers the flat form instead (terminal word in `status`, no `terminal`) โ€” see SyncSubmitResult.
426
439
  content:
427
440
  application/json:
428
- schema: { $ref: '#/components/schemas/TaskResult' }
441
+ schema: { $ref: '#/components/schemas/SyncSubmitResult' }
429
442
  '400': { $ref: '#/components/responses/BadRequest' }
430
443
  '401': { $ref: '#/components/responses/Unauthorized' }
431
444
  '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 ้‡Œ(**้‚ฃๆฎตๆ˜ฏ่ขซ่งฃๆž็š„**,ๆœฌ่กŒๆณจ้‡Šๅชๆ˜ฏๆŒ‡่ทฏ,ไธๅค่ฟฐไปฅๅ…ไธคๅค„ๆผ‚็งป)ใ€‚
432
- '409': { $ref: '#/components/responses/Conflict' }
445
+ '409':
446
+ description: >-
447
+ Conflict (ConflictError). 409 โ€” session already has an active run (`conflict.session_active_run`, `activeTaskId`,
448
+ `pendingGate`), OR an idempotency/session-CAS conflict. โš ๏ธ The SDK surfaces it as a TYPED ConflictError and does
449
+ NOT silently resolve it to that run: `activeTaskId` may be the caller's own first attempt OR an unrelated
450
+ concurrent run on the same session โ€” 409 alone cannot tell (same text as the `Conflict` response component).
451
+ K-24 (server >= 7.104.0 / S-724, zero-work submissions only): `rewind.store_unconfigured` โ€” this
452
+ deployment has NO file-history store but the request asks to restore files; the request is well-formed, the
453
+ deployment shape conflicts with it (change the deployment, not the request; `capabilities.rewindFiles` is false
454
+ there). Before 7.104.0 it arrived as 200 with a failed body.
455
+ content:
456
+ application/json:
457
+ schema: { $ref: '#/components/schemas/ErrorResponse' }
458
+ '422':
459
+ description: >-
460
+ Semantically unprocessable. K-24 (server >= 7.104.0 / S-724): `rewind.capture_disabled` โ€” the run turned file
461
+ history off (`fileHistory: "disabled"`) and asks to restore in the same breath; this server's request face
462
+ refuses that combination earlier (400 `request.field_conflict`), so only a host building a TaskSpec directly can
463
+ reach this code. `rewind.child_scope_unsupported` / `resume_at.*` also land here.
464
+ content:
465
+ application/json:
466
+ schema: { $ref: '#/components/schemas/ErrorResponse' }
433
467
  '429': { $ref: '#/components/responses/RateLimited' }
468
+ '500':
469
+ description: >-
470
+ K-24 (server >= 7.104.0 / S-612): `corrupt_session` โ€” this session's saved log cannot be read at all (file backend:
471
+ no meta header; SQL backends: one `session_event.payload` row is not a readable entry). Nothing ran, nothing
472
+ billed; `error` is the engine / store sentence (no on-disk path). DETERMINISTIC: a retry cannot change it (the SDK
473
+ does not auto-retry this code); the repair is the operator's. Also `internal.error` (unclassified).
474
+ content:
475
+ application/json:
476
+ schema: { $ref: '#/components/schemas/ErrorResponse' }
434
477
  '503': { $ref: '#/components/responses/Unauthorized' } # โš ๏ธ (declared 2026-08-05,server >=7.0.0)ๆœฌๆ“ไฝœ็š„ 503 producer ไธๆญข auth ้—จ โ€”โ€” producer ๆธ…ๅ•ๅœจ `Unauthorized` ็ป„ไปถ็š„ descriptionใ€็ญ‰ๅพ…ๆ็คบ็š„ๅœจๅœบ็Ÿฉ้˜ตๅœจ่ฏฅ็ป„ไปถ็š„ `headers.Retry-After`(**ไธคๆฎต้ƒฝๆ˜ฏ่ขซ่งฃๆž็š„**;ๆœฌ่กŒๅชๆŒ‡่ทฏ,ไธๅค่ฟฐไปฅๅ…ๆผ‚็งป)ใ€‚
435
478
  get:
436
479
  tags: [trace]
@@ -1649,6 +1692,13 @@ paths:
1649
1692
  rows. Owner-scoped (non-owner โ†’ 404). Idempotent (`{deleted:false}` when nothing existed). ๐Ÿ”ด 409 while a run
1650
1693
  is LIVE on the session (`activeTaskId` in the body) โ€” cancel it first so the session lock isn't orphaned.
1651
1694
  501 without a durable session store.
1695
+ ๐Ÿ”ด K-24 / S-790 (server >= 7.104.0): the delete FIRST ends the background children rooted at this session (parked
1696
+ ones are retired โ€” their cards leave the pending list and a later decide on such a card answers 409
1697
+ `decide.not_parked`; running ones on this replica are stopped) and only then deletes the record. A child it cannot
1698
+ stop (typically running on ANOTHER replica) โ‡’ the delete is refused and the session record stays (contract: nothing
1699
+ deleted; int-7104 `4a7f72e4` moves that check before every destructive step): 409 `conflict.session_children_alive`
1700
+ with `remaining`. A log that cannot be read at all, when the ownership probe has to open it (cold owner table after
1701
+ a restart) โ‡’ 500 `corrupt_session` (S-612; the bad log stays on disk).
1652
1702
  responses:
1653
1703
  '200':
1654
1704
  description: Delete result (idempotent).
@@ -1669,7 +1719,21 @@ paths:
1669
1719
  $ref: '#/components/responses/BadRequest'
1670
1720
  '401': { $ref: '#/components/responses/Unauthorized' }
1671
1721
  '404': { $ref: '#/components/responses/NotFound' }
1672
- '409': { $ref: '#/components/responses/Conflict' }
1722
+ '409':
1723
+ description: >-
1724
+ Two conflicts, told apart by `errorCode` (both ConflictError): `conflict.session_active_run` โ€” a run is LIVE on the
1725
+ session (`activeTaskId`), cancel it first; `conflict.session_children_alive` (server >= 7.104.0 / S-790) โ€” some
1726
+ background children rooted at this session could not be stopped from here, NOTHING was deleted, and `remaining`
1727
+ lists their ids (the same strings as `GET /v1/agents/roster`'s `handle`): wait for them to finish or stop them
1728
+ where they run, then retry the delete verbatim (idempotent). Not a server fault โ€” do not back off as for a 5xx.
1729
+ content:
1730
+ application/json:
1731
+ schema: { $ref: '#/components/schemas/ErrorResponse' }
1732
+ '500':
1733
+ description: '`corrupt_session` (server >= 7.104.0 / S-612): the session log cannot be read at all and the ownership probe had to open it (cold owner table); the delete is refused and the log stays as it is. Deterministic โ€” not auto-retried. Also `internal.error` (int-7104 `4a7f72e4`: children this replica could stop but did not stop cleanly โ€” the session record stays, a retry converges).'
1734
+ content:
1735
+ application/json:
1736
+ schema: { $ref: '#/components/schemas/ErrorResponse' }
1673
1737
  '501': { $ref: '#/components/responses/NotImplemented' }
1674
1738
 
1675
1739
  /v1/sessions/{sessionId}/fork:
@@ -3114,6 +3178,12 @@ paths:
3114
3178
  `not_found.approval` (it used to 409 โ€” a single-bit cross-tenant "who is parked on an approval"
3115
3179
  oracle); a legitimately-owned old token (a shell retrying its own already-decided row) keeps the
3116
3180
  attributable 409 + `terminal:"resolved"` verbatim.
3181
+ PARKED-CHILD lane, K-24 (server >= 7.104.0 / S-790) โ€” two codes gain a trigger, same code, same body keys:
3182
+ `decide.not_parked` (DecideParkMovedError) now also answers when the child's ROOT SESSION was deleted, or deleted
3183
+ and the same id re-registered as a new session (the decide leg reads the root session's occupant before and after
3184
+ the claim and rolls back when it is gone or different) โ€” a 7.104.0 session delete retires such cards, so this form
3185
+ mostly meets cards left by a pre-7.104.0 delete; `decide.row_unrevivable` (DecideError) now also answers when the
3186
+ parked row carries no root-session anchor (refused before the claim, nothing consumed).
3117
3187
  content:
3118
3188
  application/json:
3119
3189
  schema: { $ref: '#/components/schemas/ErrorResponse' }
@@ -3140,6 +3210,13 @@ paths:
3140
3210
  (grandchild) row, or one whose root session was deleted / changed owner, still does (core #1112 pending) โ€”
3141
3211
  render it as "this card can only be denied / wait for a server upgrade". Not a regression: 7.31.x children
3142
3212
  never had those tools.
3213
+ ๐Ÿ”ด THE TWO CAUSES ARE TOLD APART BY `cause` (server S-818, riding core #1161; ADDITIVE, optional): the body may
3214
+ carry `cause` โ€” core's CAUSE WORD for this startup failure, relayed VERBATIM from the engine receipt's `details`
3215
+ (no rewrite, no default; absent when the engine did not say, i.e. every server before S-818 and every core before
3216
+ #1161). The closed set is core's (#1161 exports it as a constant, named in core 7.34.1's release notes); this
3217
+ spec copies no word list, so treat it as an OPEN string with a default arm. Absent โ‡’ "cannot tell": offer both
3218
+ ways out. SDK โ†’ `ParkedResumeStartupError.startupCause` (`undefined` when absent or not a string; the wire key stays
3219
+ `cause` โ€” the SDK does not put it on the standard `Error.cause`, which means a chained error).
3143
3220
  content:
3144
3221
  application/json:
3145
3222
  schema: { $ref: '#/components/schemas/ErrorResponse' }
@@ -4034,7 +4111,7 @@ paths:
4034
4111
  application/json:
4035
4112
  schema: { $ref: '#/components/schemas/AskDecisionAck' }
4036
4113
  '400':
4037
- description: 'Malformed body โ€” `request.invalid_json` (unparseable) or `request.body_shape` (schema reject, e.g. an `actor` carrying anything but `label`).'
4114
+ description: 'Malformed body โ€” `request.invalid_json` (unparseable) or `request.body_shape` (schema reject, e.g. an `actor` carrying anything but `label`, or โ€” on a server < 7.104.0 โ€” a `settledBy` key). Server >= 7.104.0 (S-843) adds the `settledBy` refusals shared with the durable `/decide` leg: `request.field_conflict` (with a non-deny decision), `settled_by_not_in_proof` (direct-door worker), `request.field_invalid` (non-string, or outside the 1..16-character column) โ€” see AskDecisionBody.settledBy.'
4038
4115
  content:
4039
4116
  application/json:
4040
4117
  schema: { $ref: '#/components/schemas/ErrorResponse' }
@@ -6267,6 +6344,11 @@ components:
6267
6344
  `state.model_roster_pending` (roster not loaded yet; retry -> ServiceStateError), and (server >=7.0.0,
6268
6345
  synchronous `POST /v1/tasks` only) `memory.admission_required` โ€” the org-memory admission directory is
6269
6346
  transiently undecidable, refused fail-closed BEFORE any model call, mapped to MemoryAdmissionError.
6347
+ K-24 (server >= 7.104.0 / S-271, synchronous `POST /v1/tasks` only, and only when the submission did zero work):
6348
+ `config.tool_name_too_long` / `config.tool_contract_too_long` / `config.tool_roster_bound` โ€” a tool the DEPLOYMENT
6349
+ declared exceeds the roster bound (name 128, contract id / revision 64, `modelGate` 64); the caller cannot route
6350
+ around it and a verbatim retry never changes it (DETERMINISTIC โ€” the SDK does not auto-retry these codes; they land
6351
+ on ServiceStateError via the 503 tail, with no wait hint); only the deployment fixing its declaration helps.
6270
6352
  Which of them carry a wait hint is stated ONCE, under `headers.Retry-After` below (kept in one place so
6271
6353
  the two statements cannot drift apart).
6272
6354
  headers:
@@ -6369,8 +6451,16 @@ components:
6369
6451
  Scenario:
6370
6452
  type: string
6371
6453
  description: >
6372
- Primary routing axis (one image serves many; unknown โ†’ "default"). Selects model/tools/skills/prompt.
6373
- examples: ['default', 'oa', 'code-review', 'team']
6454
+ Primary routing axis (one image serves many). Selects model/tools/skills/prompt. OPEN: a stock server's builtin
6455
+ table is `code` (factory default) / `default` / `code-review` / `scan` (SDK `KNOWN_SCENARIOS`, checked against the
6456
+ published server's `buildScenarios()` table) plus `discuss` โ€” still accepted today, deliberately not in
6457
+ `KNOWN_SCENARIOS` because it sits inside core #1056's (7.35.0) retirement sweep (server S-630 โ‘ก) โ€” and a deployment may
6458
+ register more through the registry. A name
6459
+ the caller SENDS that the deployment does not serve is a loud 400 `scenario_unknown` (server >= 5.1.0, the body
6460
+ carries the live vocabulary) โ€” the silent fold to `default` exists only on the server-side ASSIGNMENT chain.
6461
+ Retired, no alias: `oa` (5.0.0), `autonomous` ([2400] CAPS-OPS-11), `team` (renamed `discuss`, server #273 / C-R14;
6462
+ alias withdrawn at #342 โ€” the discuss scenario's body key `team`, which picks the roster template, kept its name).
6463
+ examples: ['code', 'default', 'code-review', 'scan']
6374
6464
 
6375
6465
  ScenarioDetail:
6376
6466
  # ๆ–ฐๅขž(2026-07-25 ๅ›žๅกซๆ‰น):`GET /v1/capabilities/scenarios/{name}` ๆญคๅ‰**ๆ•ดๆก่ทฏๅพ„**้ƒฝไธๅœจๆœฌๆ–‡ไปถ้‡Œ
@@ -6702,8 +6792,11 @@ components:
6702
6792
  `resumeAnchorStore.resolve(sessionId, handle, principal)` โ†’ entry id; unknown handle โ‡’ 404
6703
6793
  `rewind_files_to.unknown_event`; no session โ‡’ 422 `rewind_files_to.no_session`; no anchor store / no getLeafId โ‡’
6704
6794
  501. โš ๏ธ The 501 is NOT the capability's whole predicate: `capabilities.rewindFilesTo` also requires the
6705
- file-history store (capture face); without it the server does not 501 โ€” core refuses in the run with
6706
- `rewind.store_unconfigured` โ€” so probe the bit first. Honored ONLY when `resumeAt` is absent. One-shot at submit
6795
+ file-history store; without it the server does not 501 โ€” core refuses at prepare with
6796
+ `rewind.store_unconfigured` (server >= 7.104.0 / S-724: a 409 error body on the synchronous `POST /v1/tasks` when
6797
+ the submission did zero work; the async / live legs keep a terminal run row with the same code; and
6798
+ `FILE_HISTORY_CAPTURE=false` no longer triggers it) โ€” so probe the bit first. Riding together with
6799
+ `fileHistory: "disabled"` โ‡’ 400 `request.field_conflict`. Honored ONLY when `resumeAt` is absent. One-shot at submit
6707
6800
  (resume legs strip it, server >= 7.72.0).
6708
6801
  restoreFiles:
6709
6802
  type: boolean
@@ -6726,6 +6819,19 @@ components:
6726
6819
  ๐Ÿ”ด Applies to BOTH restore legs (codex R2 correction): `resumeAt`+`restoreFiles` AND the
6727
6820
  code-only `rewindFilesTo` โ€” a code-only caller omitting it gets the loud terminal too. Recovery
6728
6821
  either way: re-run the same restore (per-file idempotent, converges).
6822
+ fileHistory:
6823
+ type: string
6824
+ enum: [disabled]
6825
+ description: >-
6826
+ K-24 (server >= 7.104.0 / S-724; core 7.31.0 #1048 `TaskSpec.fileHistory`) โ€” the REQUEST-LEVEL file-history off
6827
+ word. ONE word, `disabled`: this run takes no first-touch backup of an edited file and mints no turn boundaries
6828
+ (delegates inherit it); absent = the deployment's setting (there is no `enabled` spelling). Any other value โ‡’ 400
6829
+ `request.field_invalid` ("fileHistory must be the string 'disabled' when present โ€ฆ"), fresh and resume legs alike.
6830
+ ๐Ÿ”ด Riding together with ANY restore key (`restoreFiles` / `rewindFilesTo` / the retired `rewindFiles`) โ‡’ 400
6831
+ `request.field_conflict` AT ADMISSION ("fileHistory: 'disabled' cannot ride with a file restore โ€ฆ"), session and
6832
+ files untouched โ€” drop it to restore. The leg's own verdict is `wiring_manifest.fileHistory`; whether to render a
6833
+ "don't capture this session" switch is `capabilities.fileHistoryCapture === "on-optional"` (the switch = send this
6834
+ key on every submit of that session). โš ๏ธ A server < 7.104.0 answers 400 `request.body_shape` (unknown key).
6729
6835
  permissionMode:
6730
6836
  type: string
6731
6837
  enum: [default, plan, acceptEdits, bypassPermissions, auto]
@@ -7436,6 +7542,54 @@ components:
7436
7542
  sessionId: { type: string }
7437
7543
  status: { $ref: '#/components/schemas/RunStatus' }
7438
7544
 
7545
+ SealedKeyCapability:
7546
+ type: object
7547
+ description: >
7548
+ K-24 (server >= 7.104.0 / S-737) โ€” the `GET /v1/capabilities` `sealedKey` segment: EXACTLY three keys, key-for-key
7549
+ the server's `src/sealed-key.ts` `SealedKeyCapability`. Private keys, the custody path, the held key-id list and
7550
+ `createdAt` never reach the wire.
7551
+ additionalProperties: false
7552
+ required: [alg, publicKeyId, publicKey]
7553
+ properties:
7554
+ alg:
7555
+ type: string
7556
+ enum: [libsodium-sealedbox-x25519]
7557
+ description: 'v1 sole value, the same word as settings-schema `SEALED_BOX_ALG`; another alg is a loud per-model refusal.'
7558
+ 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.' }
7559
+ publicKey: { type: string, description: 'STANDARD base64 of the 32-byte X25519 public key (44 chars). Public โ€” safe to log.' }
7560
+ SyncParkReceipt:
7561
+ type: object
7562
+ description: >
7563
+ K-24 (server >= 7.104.0 / S-173) โ€” the three POLL-RECEIPT keys a synchronous `POST /v1/tasks` adds on a PARK (the
7564
+ whole park body before 7.104.0, values byte-identical), key-for-key the server's `http/sync-submit-body.ts`
7565
+ `SyncParkReceipt`. `status` is the word on the `GET /v1/runs/{taskId}` row โ€” the server derives the two words from
7566
+ its `RUN_STATUS_CLASS` table (the `parked` class).
7567
+ additionalProperties: false
7568
+ required: [taskId, sessionId, status]
7569
+ properties:
7570
+ taskId: { type: string, description: 'The durable run id minted at submit โ€” the `GET /v1/runs/{taskId}` poll key.' }
7571
+ sessionId: { type: string }
7572
+ status: { type: string, enum: [suspended, needs_review] }
7573
+ SyncSubmitResult:
7574
+ description: >
7575
+ K-24 โ€” the synchronous `POST /v1/tasks` 200 body (server name `SyncSubmitBody`, one mint `buildSyncSubmitBody`).
7576
+ ONE RULE (contract ยง5.0c): it is ALWAYS the result itself (the sealed + redacted engine result โ€” `TaskResult`); when
7577
+ the run is parked (`terminal.kind === "paused"`) it additionally carries the `SyncParkReceipt` keys, so `status`
7578
+ appears ONLY on a park โ€” terminal bodies carry none (since 7.64.0). A park here is byte-identical to the same park
7579
+ on `/v1/tasks/stream`'s `done.result` except for the identity keys; the resume credential is never on it.
7580
+ Read "is it parked" off `terminal.kind === "paused"`; the old reading `status โˆˆ {suspended, needs_review}` still
7581
+ holds. โš ๏ธ THREE GENERATIONS behind one type (the SDK's support floor is 3.0.0): server <= 7.63 answers the FLAT form
7582
+ (`LegacyPlaneTaskResult`: `status` is the terminal word โ€” completed / failed / blocked or a park word โ€” and there is
7583
+ no `terminal`); 7.64โ€“7.103 answers the current `terminal` form on terminal bodies but a park with ONLY the three receipt
7584
+ keys (no `terminal`, no `stats`); >= 7.104 follows the rule above. Read `status` for "parked" first (all three carry
7585
+ a park word on a park), then tell the generations apart by whether `terminal` is present.
7586
+ allOf:
7587
+ - $ref: '#/components/schemas/TaskResult'
7588
+ - type: object
7589
+ properties:
7590
+ status:
7591
+ allOf: [{ $ref: '#/components/schemas/RunStatus' }]
7592
+ description: 'Poll-receipt word on a park (>= 7.64: suspended / needs_review only), or the flat terminal word of a server <= 7.63 (LegacyPlaneTaskResult.status).'
7439
7593
  RunRecord:
7440
7594
  type: object
7441
7595
  description: >
@@ -7492,12 +7646,18 @@ components:
7492
7646
  current cause form on anything server >= 7.64.0 wrote, and the retired flat form on rows written
7493
7647
  before the upgrade. The service passes the stored blob through verbatim (redaction only) and does
7494
7648
  NOT back-fill `terminal`. Narrow on `"terminal" in result`.
7649
+ ๐Ÿ”ด K-24 / S-173 (server >= 7.104.0, contract ยง5.0c): the key always describes the CURRENT state โ€” a parked row
7650
+ (`suspended` / `needs_review`) carries the result it stopped with (`terminal.kind === "paused"`, credential
7651
+ stripped; = the synchronous 200 body minus `status`); a `running` row (incl. one claimed for resume) has none;
7652
+ a terminal row replaces it whole; an approval-expiry / stale reap clears it. READ `status` FIRST โ€” `result`
7653
+ present does not mean finished.
7495
7654
  errorCode:
7496
7655
  type: string
7497
7656
  description: >
7498
7657
  Result-level terminal code (e.g. `budget.exceeded`, `cancelled`); NOT an HTTP error. This ROW COLUMN
7499
7658
  is unchanged at 7.64.0 โ€” only its source moved (projected from `result.terminal`, or read straight
7500
- off a legacy flat blob).
7659
+ off a legacy flat blob). โš ๏ธ K-24 / S-173 (server >= 7.104.0): on a `needs_review` PARK row it is core's canonical
7660
+ `review.pending` (absent before) โ€” never read "errorCode present" as "failed"; read `status` first.
7501
7661
  error: { type: string }
7502
7662
  jobId:
7503
7663
  type: string
@@ -8331,10 +8491,12 @@ components:
8331
8491
  Code-only rewind (`TaskRequest.rewindFilesTo`) is wired โ€” mint `routes/capabilities.ts:839` =
8332
8492
  `Boolean(deps.fileHistoryStore && deps.resumeAnchorStore && deps.sessionStorage?.getLeafId)`: it needs the
8333
8493
  file-history store (to restore) AND the anchor map (to resolve the target HANDLE). Corrected in sdk 11.4.0
8334
- (board [8437] U3): the store is the FILE-HISTORY store (not the pre-S-15 whole-tree snapshot store), and since
8335
- server 7.97.0 (S-589) that seat is the CAPTURE face = backend AND the `FILE_HISTORY_CAPTURE` knob โ€” a deployment
8336
- that turns capture off reports this bit false together with `rewindFiles` / `restoreFiles` / `sessionSync`
8337
- (and `fileHistoryCapture: "off"`).
8494
+ (board [8437] U3): the store is the FILE-HISTORY store (not the pre-S-15 whole-tree snapshot store).
8495
+ ๐Ÿ”ด K-24: on server 7.97.0โ€“7.103.x that seat was the CAPTURE face (backend AND the `FILE_HISTORY_CAPTURE` knob), so a
8496
+ deployment that turned capture off reported this bit false together with `rewindFiles` / `restoreFiles` /
8497
+ `sessionSync`. Since server 7.104.0 (S-724) the knob ONLY turns capture off (the store stays wired, stored history
8498
+ still restores and syncs) โ€” this bit and those three no longer depend on it; they read "is there a file-history
8499
+ store" (= a storage backend). Gate rewind entries on this bit / `rewindFiles`, never on `fileHistoryCapture`.
8338
8500
  restoreFiles:
8339
8501
  type: boolean
8340
8502
  description: >-
@@ -8926,22 +9088,40 @@ components:
8926
9088
  ABSENT = an older worker (<= 7.94) โ‡’ read as `true` (contract appendix B.1: it passed through back then).
8927
9089
  fileHistoryCapture:
8928
9090
  type: string
8929
- enum: ['off', on-always]
8930
- description: >
8931
- server >= 7.97.0 (S-589; mint `routes/capabilities.ts:847` = `fileHistoryCaptureOf(deps.fileHistoryStore)`,
8932
- closed word set owned by the server's `src/file-history-capture.ts` `FILE_HISTORY_CAPTURE_WORD_SET`) โ€”
8933
- CAN FILE-HISTORY CAPTURE BE TURNED OFF. Derived from the ASSEMBLY RESULT (capture face = backend AND the
8934
- `FILE_HISTORY_CAPTURE` deployment knob), NOT an echo of the knob โ€” a deployment without the backend is
8935
- `off` too; the SAME judgement as `rewindFiles` / `restoreFiles` / `rewindFilesTo` / `sessionSync`.
8936
- `off` = this deployment captures no first-touch snapshots and has no rewind / restore / code-only rewind /
8937
- session-sync โ‡’ render neither a "turn capture off" switch nor a rewind entry. `on-always` = capturing, and
8938
- it CANNOT be turned off per session (core's first-touch capture is process-wide) โ‡’ render one read-only true
8939
- sentence, never a switch that cannot switch.
8940
- ๐Ÿ”ด A third word `on-optional` ("this session may skip capture") is DELIBERATELY UNDECLARED by the server (it
8941
- waits for a per-session no-capture seat in core); when it lands it is an additive server change and this
8942
- enum grows with it. Keep a default arm (unknown word โ‡’ treat as unknown, render no switch). Turning capture
8943
- off does NOT delete what was already captured.
8944
- ABSENT = an older worker (<= 7.96) โ‡’ fall back to `rewindFiles` (`true` โ‡’ `on-always`, `false` โ‡’ `off`).
9091
+ enum: ['off', on-optional, on-always]
9092
+ description: >
9093
+ server >= 7.97.0 (S-589), WORDS CHANGED at 7.104.0 (S-724; mint int-7104 `routes/capabilities.ts:846` =
9094
+ `fileHistoryCaptureOf(deps.fileHistoryStore, deps.config.fileHistoryCapture)`, closed word set owned by the
9095
+ server's `src/file-history-capture.ts` `FILE_HISTORY_CAPTURE_WORD_SET`) โ€” IS FILE-HISTORY CAPTURE ON, AND CAN A RUN
9096
+ TURN IT OFF. Derived from the ASSEMBLY RESULT (store present AND the `FILE_HISTORY_CAPTURE` knob not off), NOT an
9097
+ echo of the knob โ€” a deployment without the backend is `off` too. Read it EXHAUSTIVELY:
9098
+ `off` = this deployment captures no first-touch snapshots โ‡’ render no "turn capture off" switch. ๐Ÿ”ด Since 7.104.0
9099
+ it no longer implies "no rewind": the knob only stops capture, stored history still restores / syncs โ€” gate rewind
9100
+ entries on `rewindFiles` / `rewindFilesTo`, not on this bit.
9101
+ `on-optional` = capturing, and a run CAN turn it off: send `fileHistory: "disabled"` on the submit โ‡’ render the
9102
+ switch (off = send it on every submit of that session; the resume legs replay the same body).
9103
+ โšฐ๏ธ `on-always` is a RETIRED word: server 7.104.0 no longer sends it (once the request word is wired "capturing and
9104
+ cannot be turned off" is structurally unreachable), but a 7.97.0โ€“7.103.x worker still does = capturing and it
9105
+ CANNOT be turned off per session โ‡’ render one read-only true sentence, never a switch that cannot switch (contract
9106
+ appendix B.1). It stays in this enum while the SDK's support floor (3.0.0) is below 7.104.0 โ€” removing it would make
9107
+ a valid 7.103 capabilities response fail this schema. An unknown word is treated as unknown (never guess by
9108
+ prefix). Turning capture off does NOT delete what was already captured.
9109
+ ABSENT = an older worker (<= 7.96, no knob at all) โ‡’ fall back to `rewindFiles` (`true` โ‡’ `on-always`, `false` โ‡’ `off`).
9110
+ sealedKey:
9111
+ allOf: [{ $ref: '#/components/schemas/SealedKeyCapability' }]
9112
+ description: >
9113
+ K-24 (server >= 7.104.0 / S-737; mint int-7104 `routes/capabilities.ts:969`, a plain member
9114
+ `sealedKey: projectSealedKeyCapability(deps.sealedKeyPublic)`) โ€” the PUBLIC-KEY DISCOVERY face for sealed model
9115
+ keys: the newest X25519 public key in this process's model-key custody store, exactly three keys. Seal a model key
9116
+ (its UTF-8 bytes) with `crypto_box_seal` against `publicKey`, encode the ciphertext as STANDARD base64 and write it
9117
+ with `alg` / `publicKeyId` into `ModelEntry.sealedApiKey` (local lane: `config.d/models.json`; server USAGE ยง9.10).
9118
+ Sealed wins over `apiKeyEnv`; a key that cannot be opened fails that model on every call (no fallback to
9119
+ `apiKeyEnv`, none to the gateway key).
9120
+ ๐Ÿ”ด PRESENT-IFF โ€” the one per-key exception on this surface (contract appendix B.1): ABSENT means an older server
9121
+ (< 7.104.0) OR this engine built no custody store today (the same body's `version` tells them apart); either way
9122
+ do NOT seal โ€” use `apiKeyEnv`, or fix the custody directory and restart. Absent is a missing key, never `null`.
9123
+ ๐Ÿ”ด ONE STORE PER HOST (`<data root>/sealed-key/`): replicas that do not share that directory answer different
9124
+ keys, and a ciphertext sealed against replica A's key is `stale_ciphertext` on replica B.
8945
9125
  background:
8946
9126
  type: object
8947
9127
  additionalProperties: true
@@ -12421,6 +12601,13 @@ components:
12421
12601
  catch and answered 500 `internal.error` โ€” a caller-side mistake reported as a server fault, which
12422
12602
  also made it a retry-worthy-looking code. A consumer that branched on 500 for those paths must move
12423
12603
  the branch to the 400 (the fix is the caller's either way: re-encode the id).
12604
+ ๐Ÿ”ด DETERMINISTIC failure codes (K-24): a few codes arrive on a status that normally means "retry may help" (500 /
12605
+ 503) while the server contract says a verbatim retry NEVER changes the verdict. The SDK's retry arm exempts them
12606
+ by code: `corrupt_session` (500, server >= 7.104.0 / S-612 โ€” the session's saved log cannot be read),
12607
+ `config.tool_name_too_long` / `config.tool_contract_too_long` / `config.tool_roster_bound` (503, server >=
12608
+ 7.104.0 / S-271 โ€” a deployment-declared tool exceeds the roster bound), `corrupt` (500 โ€” the session rule store
12609
+ cannot read its own bytes) and `internal.memory_control_plane_corrupt` (500 โ€” the memory control plane refuses
12610
+ fail-closed). Every other 5xx code keeps the status-based retry.
12424
12611
  error: { type: string }
12425
12612
  errorMessage: { type: string }
12426
12613
  activeTaskId:
@@ -12441,6 +12628,30 @@ components:
12441
12628
  ๐Ÿ”ด NOT interchangeable with `taskId`: the workflow-child park lane mints no taskId at all (this
12442
12629
  decision never landed on any run, so minting one would be a lie), and the other two decide lanes
12443
12630
  mint no runId. Branch by `errorCode`, never fall back from one handle to the other.
12631
+ cause:
12632
+ type: string
12633
+ description: >-
12634
+ server S-818 (riding core #1161), on `parked_resume.startup_failed` (422) ONLY: core's CAUSE WORD for the parked
12635
+ resume's startup failure, relayed verbatim (never rewritten, never defaulted; absent when the engine did not say).
12636
+ An OPEN string here โ€” the closed set is core's (#1161 exports it; the constant's name is in core 7.34.1's release
12637
+ notes). Absent โ‡’ cannot tell which of the two causes (see the `/v1/approvals/{sessionId}/decide` 422 text).
12638
+ SDK reads it as `ParkedResumeStartupError.startupCause`.
12639
+ remaining:
12640
+ type: array
12641
+ items: { type: string }
12642
+ description: >-
12643
+ K-24 (server >= 7.104.0 / S-790), on 409 `conflict.session_children_alive` ONLY (`DELETE /v1/sessions/{sessionId}`):
12644
+ the ids of the background children rooted at this session that are still alive (count = length) โ€” the SAME
12645
+ strings as the `handle` on `GET /v1/agents/roster` rows. Nothing was deleted; wait for them to finish (or stop
12646
+ them where they run) and retry the delete verbatim. SDK: `err.extra.remaining` on the ConflictError.
12647
+ blockedBy:
12648
+ type: string
12649
+ description: >-
12650
+ On 409 `resume_blocked_by_policy` ONLY: the `errorCode` the folded refusal would have carried had it gone out
12651
+ directly โ€” the original code when it had one, else the status-derived coarse code (`request.rejected` /
12652
+ `not_found.resource` / `request.unprocessable` / `capability.unavailable` โ€ฆ). ALWAYS PRESENT on server >= 7.104.0
12653
+ (S-607); before that it was absent when the original refusal carried no code. SDK:
12654
+ `ResumeBlockedByPolicyError.blockedBy` (`undefined` when the key is absent or not a string โ€” never defaulted).
12444
12655
 
12445
12656
  # โ”€โ”€ Streaming event taxonomy (the UX ๅ‘ฝ่„‰). Each SSE `data:` line is one JSON-serialized AgentEvent. โ”€โ”€
12446
12657
  # TWO vocabularies (the pinned wire contract Drift 3). โš ๏ธ The two lines below are the SHAPE-DEFINING
@@ -13687,6 +13898,17 @@ components:
13687
13898
  server >= 7.66.0 (core 7.8.0 design/388 B-4) โ€” this leg's WHOLE tool roster. The service passes it
13688
13899
  through verbatim only after core's own typebox check accepted it; a roster that fails that check is
13689
13900
  ABSENT as a whole (never half a roster on the wire). Effective half only (never on the static half).
13901
+ fileHistory:
13902
+ type: string
13903
+ description: >
13904
+ K-24 (server >= 7.104.0 / S-724; core 7.31.0 #1048 / #1050; contract appendix G.20) โ€” TENANT and OPERATOR faces
13905
+ alike: does THIS LEG capture file history. `on` = edited files are backed up before the first touch and turn
13906
+ boundaries are minted; `disabled` = no capture, whatever the cause (the request word `fileHistory:"disabled"`,
13907
+ the deployment knob `FILE_HISTORY_CAPTURE=false`, no history store, no filesystem on this leg). Effective half only.
13908
+ ๐Ÿ”ด `disabled` โ‰  "cannot restore": with the deployment knob off stored history still restores
13909
+ (`capabilities.rewindFiles`); only a run the REQUEST word turned off cannot restore itself.
13910
+ OPEN on read: the server does not enumerate the word (a present non-string is dropped), a third word passes
13911
+ through โ€” treat it as unknown. ABSENT = an older engine / external derivation, never "capturing".
13690
13912
  eventId: { type: string }
13691
13913
  parentToolCallId: { type: string }
13692
13914
  sourceTaskId: { type: string }
@@ -13992,6 +14214,21 @@ components:
13992
14214
  was armed but declined/failed (check the store, check authorisation), `false` = there was no lane at all
13993
14215
  (wire one up). The two recovery verbs differ, so a consumer MUST render them apart โ€” NEVER fold them into
13994
14216
  one "nobody approved it".
14217
+ `session.records_skipped` (K-24; server >= 7.104.0 / S-612 form B; contract ยง17.4 / appendix D.3) โ€” ๐Ÿ”ด SERVER-MINTED
14218
+ (not in core's register; owner, audience `user` and mint all live in the server). The session's saved log was
14219
+ OPENED and some records could not be read and were skipped (file session backend only โ€” both SQL backends refuse a
14220
+ bad row with `corrupt_session`); the session woke up and THIS TURN RUNS, but the model sees a shorter history.
14221
+ `{ dropped, entriesKept, sessionId }`: `dropped` = records skipped by this replay, `entriesKept` = surviving entries
14222
+ counted after the open succeeded (the meta header not counted); both are observed counts โ€” do NO arithmetic (there
14223
+ is no "total"); `entriesKept === 0` = header present, no entry left (empty context, the turn still runs). NO `runId`
14224
+ (minted inside the session store's open; attribution = whichever run's stream / ledger it lands on). Deduplicated
14225
+ ONCE PER OPEN: until the log is repaired every real open emits again; a turn served from the server's session cache
14226
+ (`SESSION_CACHE_TTL_SEC`, default 300 s) does not reopen and emits nothing โ€” an event, not a standing state. Only an
14227
+ open "for this session itself" mints it (a list / export / head / ownership probe reading another session's bad log
14228
+ does not). โš ๏ธ When a skipped record is an ancestor of the current leaf the turn then fails `invalid_session` in
14229
+ prepare (core #1077) โ€” the frame still arrives first. โš ๏ธ The non-streaming synchronous `POST /v1/tasks` registers no
14230
+ notice delivery (every `engine_notice` code alike), so there it reaches the operator log only. A log that cannot be
14231
+ read AT ALL (no meta header) emits no frame โ€” that turn is refused with 500 `corrupt_session`.
13995
14232
  Starter whitelist (server 7.36): `memory.session_polluted` `{reason, sessionId?}` ยท
13996
14233
  `memory.harvest_quarantined` `{count, moved, escalated, reason?}` โ€” `moved` and `escalated` MUST NOT be
13997
14234
  subtracted from each other (an in-place tombstone counts as both) ยท `memory.delegation_static_mark_waived`
@@ -15629,6 +15866,25 @@ components:
15629
15866
  additionalProperties: false
15630
15867
  properties:
15631
15868
  label: { type: string, maxLength: 256 }
15869
+ settledBy:
15870
+ type: string
15871
+ enum: [policy]
15872
+ description: >-
15873
+ server S-843 (>= 7.104.0), ADDITIVE โ€” the SETTLEMENT SOURCE of this decision. Same word, same source, same
15874
+ sentence as the durable decide leg's key (`POST /v1/approvals/{sessionId}/decide`, ApprovalDecision.settledBy โ€”
15875
+ see that schema for the full account; the server judges this door and `/decide` with one function, and the live
15876
+ respond leg reads the same two refusal sentences under its own `request.body_shape` family):
15877
+ `"policy"` = the deployment's own policy answered this card, not a person; ABSENT = today's shape.
15878
+ `decision:"deny"` only (an approve carrying it โ‡’ 400 `request.field_conflict`); REFUSED on a direct-door worker
15879
+ (400 `settled_by_not_in_proof`); a non-string โ‡’ 400 `request.field_invalid`. ๐Ÿ”ด An UNKNOWN word is the OPPOSITE of
15880
+ `/decide` here: this door does not judge the word, so it answers 200 and the word is stored with the decision (the
15881
+ card is settled as `deny`; its source cannot be corrected afterwards, and an idempotent replay replays it), and the
15882
+ engine refuses the word fail-closed at settlement โ€” NOT `/decide`'s 409 `resume_outcome_invalid` with the card left
15883
+ pending for a corrected re-decide. THIS leg only: the value lands in a fixed-width
15884
+ `settled_by` column โ€” outside 1..16 characters โ‡’ 400 `request.field_invalid` before the CAS; none of the three
15885
+ response bodies echoes it. OLDER SERVERS (< 7.104.0): this body schema is strict, so the key is an unknown key โ‡’
15886
+ 400 `request.body_shape` and nothing is decided โ€” a loud refusal, not a silent drop. No capability bit; gate on
15887
+ the server version.
15632
15888
 
15633
15889
  AskDecisionAck:
15634
15890
  type: object
@@ -16710,7 +16966,7 @@ components:
16710
16966
  ๐Ÿ”ด ONE RULE FOR EVERY "EFFECTIVE HALF ONLY" KEY (sdk 9.7.0, after a codex adversarial-review [high]):
16711
16967
  this schema's only consumer today is `GET /v1/diagnostics/wiring`'s `static` half, and every key marked
16712
16968
  "effective half only" (`leg`, `ask.effective`, `configFingerprint`, `tools`, `hooks`, `lsp`,
16713
- `modelGate`, `autoMode`, `writeProtection`, `readDeny`, `autoConsolidation`) is ALWAYS ABSENT on that
16969
+ `modelGate`, `autoMode`, `writeProtection`, `readDeny`, `autoConsolidation`, `fileHistory`) is ALWAYS ABSENT on that
16714
16970
  wire โ‡’ THEIR ABSENCE HERE CARRIES NO INFORMATION. The per-key absence readings ("absent = not armed",
16715
16971
  "absent = a third state", โ€ฆ) hold for the LIVE FRAME (`Event_wiring_manifest`) and must not be carried
16716
16972
  over to this response. For the facts behind those keys: a tenant reads
@@ -16885,6 +17141,12 @@ components:
16885
17141
  the operator LIVE projection; for "is this deployment armed", read
16886
17142
  `GET /v1/sessions/{sessionId}/memory-status`'s `autoConsolidationArmed` (always present, a real
16887
17143
  boolean). Full per-key contract: see Event_wiring_manifest.autoConsolidation.
17144
+ fileHistory:
17145
+ type: string
17146
+ description: >
17147
+ K-24 (server >= 7.104.0 / S-724) โ€” does this leg capture file history (`on` / `disabled`, OPEN word). EFFECTIVE
17148
+ HALF ONLY โ‡’ ALWAYS ABSENT ON THIS ENDPOINT; the deployment-level reading is `capabilities.fileHistoryCapture`.
17149
+ Full per-key contract: see Event_wiring_manifest.fileHistory.
16888
17150
  WiringManifestWriteProtection:
16889
17151
  # sdk 9.7.0:ไธŽ `WiringManifestMcpEntry` / `WiringManifestLspSeam` ๅŒไธ€ๆก่ฃๅฎš โ€”โ€” ๅŒไธ€ๆฎตๅฝข่ขซ live ๅธงไธŽ
16890
17152
  # ้™ๆ€่ฏŠๆ–ญ้ขไธคๅค„ๅผ•็”จ,ๅ†…่”ๅฐฑๆ˜ฏไธคไปฝไผšๅ„่‡ชๆผ‚็š„้•œๅƒ,ๆ‰€ไปฅๅ…ทๅๅ•ๆบใ€‚
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/sdk",
3
- "version": "12.0.0",
3
+ "version": "13.0.0",
4
4
  "description": "Typed, zero-runtime-dependency SDK for the Sema agent fleet usage plane. The shared substrate for all doors (CC/Codex MCP faรงade + web). Server-side only โ€” tokens never enter the browser.",
5
5
  "type": "module",
6
6
  "license": "BUSL-1.1",
@@ -52,6 +52,6 @@
52
52
  "prepublishOnly": "rm -f tsconfig.tsbuildinfo && npm run build && npm run typecheck --workspaces=false --prefix ../.. && npm test --workspaces=false --prefix ../.."
53
53
  },
54
54
  "devDependencies": {
55
- "@sema-agent/settings-schema": "^5.0.0"
55
+ "@sema-agent/settings-schema": "^6.3.0"
56
56
  }
57
57
  }
@@ -129,9 +129,12 @@ paths:
129
129
  ๐Ÿ”ด CAS axis = the `updatedAt` STRING (verbatim echo), NOT a version number โ€” the scopes/config
130
130
  face below uses a NUMBER `version` axis; the two axes must never be mixed (pinned). 409 carries
131
131
  `currentUpdatedAt` (typed ConfigCasConflictError; retryOn409 merge-callback retries ONCE).
132
+ 409 SCOPE-GONE (web-admin W41F, 2026-09-27): the scope this token is bound to no longer exists โ‡’
133
+ `409 {error:"scope_gone", error_description}` โ€” NO `currentUpdatedAt`, nothing written, NOT a CAS conflict:
134
+ never refetch+merge+retry it (the SDK throws `ScopeGoneError` and skips the merge leg; `classifyRegistryConflict`).
132
135
  responses:
133
136
  '200': { description: Written. }
134
- '409': { description: '{currentUpdatedAt} โ€” concurrent write; refetch+merge+retry once.' }
137
+ '409': { description: '{error:"conflict", currentUpdatedAt} โ€” concurrent write; refetch+merge+retry once. OR {error:"scope_gone"} โ€” the token''s scope no longer exists; nothing written; do NOT retry.' }
135
138
  /api/v1/users/{u}/config/{domain}:
136
139
  parameters:
137
140
  - { in: path, name: u, required: true, schema: { type: string } }
@@ -145,7 +148,7 @@ paths:
145
148
  summary: Managed CAS-write (same updatedAt axis + 409 semantics as me/config).
146
149
  responses:
147
150
  '200': { description: Written. }
148
- '409': { description: '{currentUpdatedAt}.' }
151
+ '409': { description: '{error:"conflict", currentUpdatedAt} โ€” CAS conflict. OR {error:"scope_gone"} โ€” the token''s scope no longer exists; nothing written; do NOT retry (SDK ScopeGoneError).' }
149
152
 
150
153
  # โ”€โ”€ scope config draft / publish (CAS on the version NUMBER axis) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
151
154
  /api/v1/scopes/{s}/config/{domain}:
@@ -174,7 +177,12 @@ paths:
174
177
  summary: CAS-write the draft (putScopeConfigDraft; version NUMBER axis โ€” not updatedAt).
175
178
  responses:
176
179
  '200': { description: Written. }
177
- '409': { description: 'Version conflict.' }
180
+ '409':
181
+ description: >
182
+ `{error:"conflict", currentVersion?}` โ€” version conflict (SDK ScopeConfigCasConflictError: re-read, re-base, retry).
183
+ OR `{error:"scope_gone", error_description}` (web-admin W41F / KL-24) โ€” the scope was deleted, or deleted and re-created
184
+ under the same name, while this write was in flight (or the optional `baseIncarnation` no longer matches): NO
185
+ `currentVersion`, nothing written, NOT retryable โ€” a re-read reads the REPLACEMENT scope (SDK ScopeGoneError).
178
186
  /api/v1/scopes/{s}/publish:
179
187
  parameters:
180
188
  - { in: path, name: s, required: true, schema: { type: string } }
@@ -274,7 +282,12 @@ paths:
274
282
  get: { operationId: adminAuthProvidersShow, summary: 'ADMIN: one auth provider.', responses: { '200': { description: OK } } }
275
283
  /api/v1/scopes/{s}/rollback:
276
284
  parameters: [{ in: path, name: s, required: true, schema: { type: string } }]
277
- post: { operationId: adminScopeRollback, summary: 'ADMIN: rollback a scope''s published config.', responses: { '200': { description: OK } } }
285
+ post:
286
+ operationId: adminScopeRollback
287
+ summary: 'ADMIN: rollback a scope''s published config.'
288
+ responses:
289
+ '200': { description: OK }
290
+ '409': { description: '{error:"scope_gone"} โ€” the scope was deleted / re-created under the same name while the rollback was in flight; nothing written; do NOT retry (SDK ScopeGoneError, still a RegistryApiError). Other 409s stay a plain RegistryApiError.' }
278
291
  /api/v1/usage/me:
279
292
  get: { operationId: adminUsageMe, summary: 'Usage of the caller.', responses: { '200': { description: OK } } }
280
293
  /api/v1/usage/summary: