@sema-agent/sdk 9.0.0 → 9.2.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/openapi.yaml CHANGED
@@ -918,6 +918,104 @@ paths:
918
918
  schema: { $ref: '#/components/schemas/ErrorResponse' }
919
919
  '501': { $ref: '#/components/responses/NotImplemented' }
920
920
 
921
+ /v1/runs/{taskId}/memory/capture-optout:
922
+ parameters:
923
+ - $ref: '#/components/parameters/PrincipalHeader'
924
+ - $ref: '#/components/parameters/TaskIdPath'
925
+ post:
926
+ tags: [runs]
927
+ operationId: runsMemoryCaptureOptOut
928
+ x-status: live # server >= 7.70.0 — the mid-run flip verb for the session memory-capture opt-out.
929
+ summary: Opt this SESSION out of long-term memory capture, mid-run.
930
+ description: >
931
+ "We got into something that should not be remembered." From this moment the session commits NOTHING to
932
+ long-term memory, already-committed contributions leave the consolidation pool, and write-root residue
933
+ is swept. ONE-WAY: there is no reverse verb, repeat calls are idempotent (`outcome:"existed"`), and
934
+ getting capture back needs a NEW session.
935
+ 🔴 The SAME record as the `memoryCapture:"off"` submit-time declaration, reached through a second
936
+ ingress — so "declare it on the next submit" is the fallback for MOST refusals. It is not the answer to
937
+ all of them: read the per-errorCode recovery notes on the 409/503 arms below (one 409 is a PARTIAL
938
+ SUCCESS whose retry lane is this very verb, one names another replica, and one is a deployment-shape
939
+ fact the declaration hits too).
940
+ Owner-gated on the VERIFIED principal (a bare sessionId relay would let anyone switch off anyone's
941
+ memory); a non-owner gets the SAME 404 string as an unknown run (no existence oracle). The engine
942
+ re-adjudicates the per-principal entitlement at flip time — the service does not replicate that ruling.
943
+ Runs no model (not billable, no drain/roster gate); rate-limited as a mutating verb. Gate the UI entry
944
+ on `capabilities.runMemoryCaptureOptOut`.
945
+ requestBody:
946
+ required: false
947
+ content:
948
+ application/json:
949
+ schema:
950
+ type: object
951
+ additionalProperties: false # STRICT server-side — a mistyped key is a 400, not "no reason given".
952
+ properties:
953
+ reason: { type: string, minLength: 1, maxLength: 500, description: 'Optional one-line audit note. Does not affect the ruling.' }
954
+ responses:
955
+ '200':
956
+ description: Flipped — `created` on the first call, `existed` on an idempotent repeat.
957
+ content:
958
+ application/json:
959
+ schema: { $ref: '#/components/schemas/MemoryCaptureOptOutResult' }
960
+ '400':
961
+ description: >-
962
+ errorCode "request.body_shape" — the body must be `{}` or `{ reason }` (non-empty, at most 500
963
+ chars); this verb takes no other keys.
964
+ content:
965
+ application/json:
966
+ schema: { $ref: '#/components/schemas/ErrorResponse' }
967
+ '401': { $ref: '#/components/responses/Unauthorized' }
968
+ '403':
969
+ description: >-
970
+ errorCode "memory.capture_optout_denied" — this principal's entitlement does not permit the opt-out.
971
+ TERMINAL: the verdict is a deployment/center fact, so retrying changes nothing.
972
+ content:
973
+ application/json:
974
+ schema: { $ref: '#/components/schemas/ErrorResponse' }
975
+ '404': { $ref: '#/components/responses/NotFound' }
976
+ '409':
977
+ description: >-
978
+ Four DIFFERENT situations share this status; the recovery action is per errorCode, not per status.
979
+ (1) "steering.not_running" — this verb needs a LIVE turn and there is none. THREE of its four causes
980
+ (terminal / parked / no steerable face on this replica) are fixed the same way: declare
981
+ `memoryCapture: "off"` on this session's next NEW submit. ⚠️ For a PARKED run that matters twice
982
+ over — a resumed continuation rebuilds its spec from the PERSISTED body and cannot take a new
983
+ declaration, so content captured until that next submit stays captured. The FOURTH cause is
984
+ different: when the run is live on ANOTHER replica the service says so explicitly, and the recovery
985
+ is to RESEND this verb to the owning replica (the next-submit declaration also works).
986
+ (2) "memory.capture_optout_sweep_failed" — 🔴 PARTIAL SUCCESS, not a rejection: the one-way opt-out
987
+ record STANDS (capture is already off) and only the residue sweep failed on named paths. A repeat
988
+ call IS the retry lane; leaving it un-retried leaves that named residue on the authoritative plane.
989
+ (3) "memory.capture_optout_unavailable" — this run mounted no memory session, so there is nothing to
990
+ opt out of; no action is owed.
991
+ (4) "config.memory_capture_unsupported" — a DEPLOYMENT-SHAPE fact (no capture record store on this
992
+ worker, so the record would not survive to a resume replica). 🔴 The submit-time declaration hits the
993
+ SAME wall on this shape — do not advertise it as the workaround; the fix is on the deployment.
994
+ content:
995
+ application/json:
996
+ schema: { $ref: '#/components/schemas/ErrorResponse' }
997
+ '422':
998
+ description: 'errorCode "steering.invalid_content" — the engine refused the reason payload.'
999
+ content:
1000
+ application/json:
1001
+ schema: { $ref: '#/components/schemas/ErrorResponse' }
1002
+ '429':
1003
+ description: >-
1004
+ errorCode "limit.rate_exceeded" — mutating verb, rate-limited only (no quota/lease gate; it runs no
1005
+ model). `Retry-After` (seconds) set.
1006
+ content:
1007
+ application/json:
1008
+ schema: { $ref: '#/components/schemas/ErrorResponse' }
1009
+ '501': { $ref: '#/components/responses/NotImplemented' }
1010
+ '503':
1011
+ description: >-
1012
+ errorCode "memory.capture_optout_unpersisted" — the record could not be durably written (a store-mark
1013
+ failure, or a failing consolidation-epoch bump). The engine REFUSES rather than holding the flip
1014
+ in-process, so capture is still ON. RETRYABLE (the verb is idempotent).
1015
+ content:
1016
+ application/json:
1017
+ schema: { $ref: '#/components/schemas/ErrorResponse' }
1018
+
921
1019
  /v1/runs/{taskId}/subagents/{target}/output:
922
1020
  parameters:
923
1021
  - $ref: '#/components/parameters/PrincipalHeader'
@@ -2554,16 +2652,20 @@ paths:
2554
2652
  `POST /v1/assistant/tasks/{taskId}/resume`. Retrying the same body never succeeds.
2555
2653
  SEVENTH group (server >= 7.69.0 / S-185, the PARKED WORKFLOW-CHILD lane): `decide.workflow_host_unknown`
2556
2654
  and `decide.workflow_host_not_parked`. The pending belongs to a workflow child that parked on an
2557
- approval gate, and the deployment could not hand the decision to that workflow's HOST session (the
2558
- engine's only deployment-facing channel for a parked workflow ordinal is a resume of the host, which
2559
- then re-invokes `Workflow({resumeFromRunId})`). Both bodies carry `runId` (the WORKFLOW run) and NO
2560
- `taskId`. NOTHING was consumed: the child's checkpoint stays PENDING and the card is still listable
2561
- and re-decidable. `_host_not_parked` = the host is not currently parked awaiting this run (it may
2562
- have been interrupted, taking an already-delivered decision with it — the decision is not persisted
2563
- anywhere) → retry once it parks again, or resume that `runId` yourself. `_host_unknown` = the run
2564
- carries no originating session at all (a directly started `runWorkflow`, not a Workflow tool call) →
2565
- NO retry value, a person resumes that `runId`. SDK → DecideWorkflowHostError (`.errorCode` splits the
2566
- two recovery verbs, `.runId` is the handle).
2655
+ approval gate, and the deployment could not hand the decision to that workflow's HOST session. Both
2656
+ bodies carry `runId` (the WORKFLOW run) and NO `taskId`. NOTHING was consumed: the child's checkpoint
2657
+ stays PENDING and the card is still listable and re-decidable. SDK → DecideWorkflowHostError (`.runId`
2658
+ is the handle). `_host_unknown` = the run carries no originating session at all (a directly started
2659
+ `runWorkflow`, not a Workflow tool call) → NO retry value, a person resumes that `runId`.
2660
+ `_host_not_parked` = the host is not currently parked awaiting this run. ⚰️ RETIRED at server 7.72.0,
2661
+ no alias, but this SDK still maps it: `SUPPORTED_SERVER_FLOOR` is 3.0.0 and this code was introduced
2662
+ at 7.69.0, so a still-supported server in the [7.69.0, 7.72.0) window can genuinely send it, and for
2663
+ that window the old contract holds — retry once it parks again, or resume that `runId` yourself. A
2664
+ server >= 7.72.0 will never send this code again: that version replaced the dead end with a SECOND
2665
+ leg that instead mints a fresh run on the same host session, so against a current server the SAME
2666
+ decide either resumes the parked host or starts that new run (both land on the byte-identical
2667
+ acceptance shape) — a host session already busy with another run instead answers the ordinary 409
2668
+ `conflict.session_active_run`.
2567
2669
  SIXTH group (A-075.10, TIME-BASED retry-later pair): `resume.usage_window_exhausted` (#449 G1,
2568
2670
  core 5.60.1 — the deployment governance window on this run's ledger key is full; NOTHING was
2569
2671
  consumed or unpinned, the SAME token with the SAME decision redeems once the window slides) and
@@ -5232,7 +5334,9 @@ components:
5232
5334
  type: object
5233
5335
  description: >
5234
5336
  Common request body. `[additionalProperties]` mirrors the service passthrough — scenario-specific fields
5235
- (code-review's `repo`/`council`/`debate`/`rounds`, etc.) ride here.
5337
+ (code-review's `repo`/`lenses`/`rounds`, etc.) ride here. NOTE (sdk 9.1.0): `council`/`debate` used to be
5338
+ named here as passthrough examples, but the service declares them as TOP-LEVEL typed keys and reads them
5339
+ on two seams outside the scenario builder, so they are declared properties below.
5236
5340
  required: [objective]
5237
5341
  additionalProperties: true
5238
5342
  properties:
@@ -5318,6 +5422,40 @@ components:
5318
5422
  (mcpInjectionHonored = requirePrincipal!==true); multi-tenant ignores it (fail-closed). Merge:
5319
5423
  deployment/center baseline wins on name collision (caller can ADD, never SHADOW).
5320
5424
  items: { $ref: '#/components/schemas/McpServerSpec' }
5425
+ a2aPeers:
5426
+ type: array
5427
+ maxItems: 32
5428
+ description: >
5429
+ Per-request A2A peers — remote agents this task may call. HONORED only on a SINGLE-USER deployment
5430
+ that has neither locked the `a2a` key nor had `a2a_peers` denied by its compliance posture (the same
5431
+ three-veto predicate `capabilities.a2aInjection` advertises); multi-tenant ignores the field, and a
5432
+ LOCKED deployment refuses the whole request with 400 `config.locked_key`.
5433
+ 🔴 Declaring a peer declares an OUTBOUND WRITE channel: every skill it advertises mounts with
5434
+ `egress:true` + `effect:"write"` (core's ruling); `toolAxes` is the caller's explicit override face.
5435
+ 🔴 A malformed entry drops the WHOLE peer (named in a warn), never "drop the bad key and keep the
5436
+ peer": `allowSkills` is a NARROWING field whose absence means "no restriction", so a half-applied
5437
+ entry would turn a narrowing intent into a widening. The request itself is not rejected.
5438
+ The center/config baseline wins on a name clash (the peer name IS the tool namespace segment). Over
5439
+ the 32-entry cap the surplus entries are DROPPED and named too — same disposition as a malformed
5440
+ entry, not a 400.
5441
+ items: { $ref: '#/components/schemas/A2aServerSpec' }
5442
+ council:
5443
+ type: boolean
5444
+ description: >
5445
+ The `code-review` scenario's EXPENSIVE tier — fan the review out to N parallel lenses plus an
5446
+ arbiter (`run_council`) instead of the lead reviewing in-line. Read on TWO seams and both matter:
5447
+ the scenario builder mounts the council tool, and spec resolution treats it as an EXPLICIT team
5448
+ declaration (widening the task's wall-clock tenancy budget and suppressing the value router's
5449
+ auto-escalation). Only a literal `true` counts on both seams. On other scenarios the first seam is
5450
+ inert while the second still applies.
5451
+ debate:
5452
+ type: boolean
5453
+ description: >
5454
+ Run the L2 peer-debate rounds on top of `council`. 🔴 The two seams are ASYMMETRIC — this is not a
5455
+ standalone switch: the scenario builder reads it only INSIDE the `council === true` branch (sending
5456
+ `debate:true` alone produces no debate rounds), while the explicit-team predicate is
5457
+ `council || debate` (sending it alone still widens the budget and suppresses auto-escalation).
5458
+ Literal `true` only, same as `council`.
5321
5459
  resumeAt:
5322
5460
  type: string
5323
5461
  description: >
@@ -5503,6 +5641,22 @@ components:
5503
5641
  items: { type: string }
5504
5642
  description: Capability names the caller requires; the worker refuses up front rather than 501-ing mid-run.
5505
5643
  memoryWrite: { type: boolean, description: Allow this task to WRITE user memory (read is governed separately). }
5644
+ memoryCapture:
5645
+ type: string
5646
+ enum: [off]
5647
+ description: >
5648
+ server >= 7.70.0 — the SESSION-level memory-capture OPT-OUT declaration: "off" means "this session
5649
+ does not enter long-term memory". A ONE-MEMBER closed set: there is NO "on" spelling, absence is the
5650
+ only way to say "capture as usual".
5651
+ 🔴 A DIFFERENT AXIS from `memoryWrite`: that one pauses writes for THIS run and can be flipped back
5652
+ per run; this one is a ONE-WAY, one-shot session record (later resumes never capture, repeat
5653
+ declarations are idempotent, and getting capture back needs a NEW session).
5654
+ 🔴 The spelling gate is loud on BOTH legs (no resume downgrade): "OFF" / a boolean / anything else
5655
+ is a 400 `request.field_invalid` — silently dropping a privacy request as a typo is the banned
5656
+ direction. A deployment policy that refuses this principal's opt-out answers 403
5657
+ `memory.capture_optout_denied` (terminal). A worker older than 7.70.0 IGNORES the key silently, so
5658
+ it is not a capability probe. Rides the persisted body onto resume legs.
5659
+ Mid-run flip: POST /v1/runs/{taskId}/memory/capture-optout (the same record, a second ingress).
5506
5660
  settings:
5507
5661
  type: object
5508
5662
  description: >
@@ -7283,6 +7437,71 @@ components:
7283
7437
  face drops the whole server.
7284
7438
  additionalProperties: { $ref: '#/components/schemas/McpToolFace' }
7285
7439
 
7440
+ A2aServerSpec:
7441
+ type: object
7442
+ description: >
7443
+ One per-request A2A peer (`TaskRequest.a2aPeers[]`). These six keys are exactly what the service's
7444
+ normalizer copies through — anything else on the entry never reaches the engine. `name`/`url` are
7445
+ required; if ANY present key is malformed the WHOLE peer is dropped (and named in a warn), because a
7446
+ half-applied entry can turn a narrowing intent into a widening.
7447
+ required: [name, url]
7448
+ additionalProperties: false
7449
+ properties:
7450
+ name:
7451
+ type: string
7452
+ minLength: 1
7453
+ maxLength: 128
7454
+ description: 'Stable local name; also the tool-namespace segment (`a2a__<peer>__<skill>`).'
7455
+ url:
7456
+ type: string
7457
+ description: 'The peer service URL; doubles as the origin the well-known agent-card path resolves against. http(s) ONLY.'
7458
+ cardUrl:
7459
+ type: string
7460
+ description: >
7461
+ Explicit agent-card location. When set it is used ALONE (no well-known probing behind the operator's
7462
+ back), so a malformed value drops the peer rather than falling back.
7463
+ headers:
7464
+ type: object
7465
+ additionalProperties: { type: string }
7466
+ description: 'Static headers sent on every request to this peer.'
7467
+ principalHeader:
7468
+ type: string
7469
+ description: >
7470
+ Header name the Runner injects this run's AUTHENTICATED principal into (the model can neither read
7471
+ nor set it). Absent principal => the header is not sent; the peer must then default to deny/public.
7472
+ allowSkills:
7473
+ type: array
7474
+ items: { type: string }
7475
+ description: >
7476
+ Allowlist of remote skill ids to mount. ⚠️ ABSENCE means "no restriction" (core's semantic), so this
7477
+ is a NARROWING key — never express narrowing by omitting it.
7478
+ toolAxes:
7479
+ type: object
7480
+ description: >
7481
+ Per-skill safety-axis overrides keyed by the peer's REMOTE skill id. The caller is the trust root,
7482
+ so this may LOWER the fail-closed default (`egress:true` + `effect:"write"`) as well as raise it.
7483
+ ⚠️ Lowering `effect` WITHOUT clearing `egress` is a combination the engine refuses by design.
7484
+ additionalProperties:
7485
+ type: object
7486
+ additionalProperties: false
7487
+ properties:
7488
+ effect: { type: string, enum: [read, write, idempotent] }
7489
+ egress: { type: boolean }
7490
+ irreversibility: { type: string, enum: [always, never] }
7491
+
7492
+ MemoryCaptureOptOutResult:
7493
+ type: object
7494
+ description: >
7495
+ 200 body of POST /v1/runs/{taskId}/memory/capture-optout. `outcome` is a closed two-word set:
7496
+ `created` = this call wrote the one-way session record; `existed` = the session had already opted out
7497
+ (idempotent replay).
7498
+ required: [taskId, sessionId, outcome]
7499
+ additionalProperties: false
7500
+ properties:
7501
+ taskId: { type: string }
7502
+ sessionId: { type: string }
7503
+ outcome: { type: string, enum: [created, existed] }
7504
+
7286
7505
  MemoryEntryFrontmatter:
7287
7506
  type: object
7288
7507
  description: >
@@ -8597,9 +8816,16 @@ components:
8597
8816
  lane's 200 DO mean the engine committed the decision — one sentence ("approved and in effect") cannot be
8598
8817
  used for both lanes.
8599
8818
  (2) delivery can still be LOST: if the host run is interrupted before it re-invokes `Workflow`, the
8600
- decision goes with that invocation (it is not persisted anywhere) and a retry answers 409
8601
- `decide.workflow_host_not_parked`. Because the card was pending throughout, nothing is forged and the
8602
- approval can be re-decided once the host parks again.
8819
+ decision goes with that invocation (it is not persisted anywhere); because the card was pending
8820
+ throughout, nothing is forged. Against a server >= 7.72.0 a retry of the SAME decide succeeds — that
8821
+ version gave this lane a second leg that mints a fresh run on the same host session when the host is not
8822
+ currently parked, so the retry either resumes the parked host or starts that new run (byte-identical
8823
+ acceptance shape either way; a host session already busy with another run instead answers the existing
8824
+ `conflict.session_active_run` 409). Against a still-supported server in the [7.69.0, 7.72.0) window
8825
+ (`SUPPORTED_SERVER_FLOOR` is 3.0.0) the retry instead answers 409 `decide.workflow_host_not_parked` —
8826
+ retry once the host parks again, or resume that `runId` yourself. ⚰️ That code is RETIRED at 7.72.0, no
8827
+ alias, and a current server will not send it again — but it remains a live, supported response from an
8828
+ older server in that window, so the SDK still maps it (see `DecideWorkflowHostError`).
8603
8829
  The durable approval-queue projection is UNCHANGED — no key marks the workflow origin on any read face.
8604
8830
  required: []
8605
8831
  additionalProperties: false
@@ -8940,6 +9166,21 @@ components:
8940
9166
  one-off question. The value comes WHOLE from core (`summarizeCheckpoint` echoes it only when the
8941
9167
  cause is a member of the engine's set), so the server neither recomputes nor re-screens it. See
8942
9168
  ClassifierUnavailable for the open-`cause` rule and the three shapes absence covers.
9169
+ RETIRED: server >= 7.72.0 (core 7.14.0 #688) never mints this key on any of the three faces; its
9170
+ successor is `disposition.cause` on the gate record (`unavailable` / `parse_error` — a different
9171
+ question, so an old switch does not port). Kept for servers in [7.69.0, 7.72.0) only (sdk 9.2.0).
9172
+ ruleStoreUnreadable:
9173
+ type: string
9174
+ enum: [store, call]
9175
+ description: >-
9176
+ server >= 7.72.0 (core 7.14.0 #688 C3), ADDITIVE — WHICH HALF of the rule lane could not be read.
9177
+ Present iff `origin === "rule_store_unavailable"`: `store` = the wired rule store could not be read
9178
+ (look at the store / network); `call` = the store was read but THIS command could not be read against
9179
+ the person's deny/ask rows (look at the command spelling). One `origin` word covers both facts, and
9180
+ their recovery verbs live in different places (ops vs prompt). Carried on the live `tool_approval`
9181
+ frame and the inbox row; the `card_json` card deliberately does NOT carry it (it travels with `origin`,
9182
+ which is not on the card). Absence = `origin` is not that word — NOT an assertion that the rule store
9183
+ is healthy.
8943
9184
 
8944
9185
  InboxList:
8945
9186
  type: object
@@ -9681,6 +9922,13 @@ components:
9681
9922
  - $ref: '#/components/schemas/Event_tool_approval_complete'
9682
9923
  - $ref: '#/components/schemas/Event_approval_request'
9683
9924
  - $ref: '#/components/schemas/Event_approval_revoke'
9925
+ # sdk 9.1.0: two LIVE-leg arms the service has been emitting while neither this union nor the
9926
+ # SDK's declared them — `text_end` since server 7.66.0 (segment boundary; retires the idle-flush
9927
+ # heuristic) and `tool_roster_delta` since server 7.70.0 (run-time roster change). A generated client
9928
+ # had no arm to switch on and dropped both. Found by the mechanical frame-arm reconciliation gate
9929
+ # (server `routes/tasks.ts` mint points x this union), which now runs on every build.
9930
+ - $ref: '#/components/schemas/Event_text_end'
9931
+ - $ref: '#/components/schemas/Event_tool_roster_delta'
9684
9932
  discriminator:
9685
9933
  propertyName: type
9686
9934
  mapping:
@@ -9724,6 +9972,8 @@ components:
9724
9972
  tool_approval_complete: '#/components/schemas/Event_tool_approval_complete'
9725
9973
  approval_request: '#/components/schemas/Event_approval_request'
9726
9974
  approval_revoke: '#/components/schemas/Event_approval_revoke'
9975
+ text_end: '#/components/schemas/Event_text_end'
9976
+ tool_roster_delta: '#/components/schemas/Event_tool_roster_delta'
9727
9977
 
9728
9978
  # design/99 §E2 (SHIPPED, core TaskEventIdentity) — additive per-event identity on every CONTENT event (NOT on
9729
9979
  # the lifecycle arms turn_end/compacted/suspended/done/failed). eventId = uuidv7 dedup/resume handle (the durable
@@ -9813,6 +10063,27 @@ components:
9813
10063
  parentToolCallId: { type: string }
9814
10064
  sourceTaskId: { type: string }
9815
10065
  bgAgentId: { type: string }
10066
+ Event_text_end:
10067
+ type: object
10068
+ description: >
10069
+ LIVE stream only (server >= 7.66.0, core 5.62.0) — the END-OF-SEGMENT signal for one assistant text
10070
+ segment. `content` is the segment's AUTHORITATIVE full text (same trust class as `text_delta`, same
10071
+ zero content transformation). It retires the "no delta for N ms => the segment must be over" idle-flush
10072
+ heuristic: the boundary is now stated by the engine, not guessed by a timer.
10073
+ 🔴 A TRUNCATED segment gets NO such frame (core deliberately withholds it) — "no text_end" does NOT
10074
+ mean "the segment is still going". 🔴 The durable leg does not append it (the batched `text` event
10075
+ carries the same final prose), so GET /v1/runs/{id}/events never replays it. Both mint points are in
10076
+ `routes/tasks.ts`: the top stream (all four identity keys) and the forwarded sub-agent leg (first two).
10077
+ allOf: [{ $ref: '#/components/schemas/EventIdentity' }]
10078
+ additionalProperties: false # 封闭 + identity 本地镜像:见 Event_reasoning 处的长注
10079
+ required: [type, content]
10080
+ properties:
10081
+ type: { const: text_end }
10082
+ content: { type: string }
10083
+ eventId: { type: string }
10084
+ parentToolCallId: { type: string }
10085
+ sourceTaskId: { type: string }
10086
+ bgAgentId: { type: string }
9816
10087
  Event_tool_start:
9817
10088
  type: object
9818
10089
  allOf: [{ $ref: '#/components/schemas/EventIdentity' }]
@@ -9982,6 +10253,18 @@ components:
9982
10253
  properties:
9983
10254
  kind: { const: denied }
9984
10255
  deniedBy: { $ref: '#/components/schemas/DeniedBy' }
10256
+ cause:
10257
+ type: string
10258
+ description: >-
10259
+ server >= 7.72.0 (core 7.14.0 #688 C-a) — the SHAPE of this deny when the auto-mode classifier was
10260
+ the refusing layer: `unavailable` (that classifier round could not run: threw / refused / over cap)
10261
+ or `parse_error` (it ran but answered outside the verdict contract, so the call was stopped to be
10262
+ safe). Successor seat of the retired `classifierUnavailable` key (minted once by the engine, carried
10263
+ on both `tool_end.gate` and `permissionDenied.gate`) — but NOT the same question as the old `cause`
10264
+ (`error` / `timeout` answered WHY it was unavailable), so an old switch does not port. The
10265
+ vocabulary's owner is the engine (`CLASSIFIER_DENY_CAUSES`); the server passes it through verbatim
10266
+ — branch the two known words and keep a default arm. Absent = this deny was not a classifier-fault
10267
+ shape (policy / person / rule refused); never read absence as "classifier healthy".
9985
10268
 
9986
10269
  GateOutcome:
9987
10270
  type: object
@@ -10020,7 +10303,7 @@ components:
10020
10303
  # 服务对缺陷记录是整条不上帧 ⇒ 词表外的值在这条 wire 上到不了消费端。审批帧那一面不判成员,
10021
10304
  # 所以 `AskOrigin` 本体保持真开(见该 schema 的长注)。这张 enum 是本 spec 里该词表的**唯一**
10022
10305
  # 执法点;core 加词时改这一处。
10023
- - enum: [content_question, unresolvable, org_unavailable, org_rule, rule_store_unavailable, hook, ask_rule, denial_limit_fallback, shell_gate_tighten, safety_tighten, policy]
10306
+ - enum: [content_question, ancestor_marked, org_unavailable, org_rule, rule_store_unavailable, hook, ask_rule, denial_limit_fallback, shell_gate_tighten, safety_tighten, policy]
10024
10307
  description: >
10025
10308
  WHO ASKED. Same-present/same-absent as `settlement` (I1). CLOSED here (and only here) — see the
10026
10309
  enum note above.
@@ -10585,6 +10868,59 @@ components:
10585
10868
  parentToolCallId: { type: string }
10586
10869
  sourceTaskId: { type: string }
10587
10870
  bgAgentId: { type: string }
10871
+ Event_tool_roster_delta:
10872
+ type: object
10873
+ description: >
10874
+ A RUN-TIME roster change (server >= 7.70.0, core 7.8.0; live-only until 7.71.0, see below).
10875
+ `wiring_manifest.tools` is the snapshot taken when this leg started; this frame is the increment after
10876
+ it: one refresh that actually changed the roster emits EXACTLY one frame (ordered after the manifest and
10877
+ before that refresh's own `tool_end`).
10878
+ 🔴 A `delta.fromDigest` that does not match what the consumer holds is NOT a rejection: `delta.roster`
10879
+ is the new state regardless — record a skew and re-sync from the whole roster (only `delta.summary`
10880
+ becomes unusable). 🔴 Half a roster never reaches the wire: the service drops the WHOLE frame when
10881
+ core's own typebox check rejects the payload. 🔴 Durable legs (server >= 7.71.0, S-212): BOTH durable legs
10882
+ append it too (the three legs share one mint point), so the raw replay `GET /v1/runs/{id}/events`
10883
+ (`runs.events()`, `AgentEvent`) DOES carry it after a reconnect. What still never carries it is the
10884
+ projected trace stream `GET /v1/tasks/{id}/stream` (`trace.stream()`, `TraceStreamEvent`, `mapTraceEvent`
10885
+ drops it with `wiring_manifest` / `human_input`) — consumers on THAT leg keep re-syncing from the
10886
+ `wiring_manifest.tools` snapshot. (Before 7.71.0 the frame was live-only.)
10887
+ allOf: [{ $ref: '#/components/schemas/EventIdentity' }]
10888
+ additionalProperties: false # 封闭 + identity 本地镜像:见 Event_reasoning 处的长注
10889
+ required: [type, delta]
10890
+ properties:
10891
+ type: { const: tool_roster_delta }
10892
+ delta: { $ref: '#/components/schemas/ToolRosterDelta' }
10893
+ eventId: { type: string }
10894
+ parentToolCallId: { type: string }
10895
+ sourceTaskId: { type: string }
10896
+ bgAgentId: { type: string }
10897
+ ToolRosterDelta:
10898
+ type: object
10899
+ description: >
10900
+ core `ToolRosterDelta` (core 7.8.0) — the payload of `tool_roster_delta`. `roster` is the WHOLE
10901
+ post-change roster and positions come from IT, never from `summary`; the three `summary` lists are
10902
+ pairwise disjoint and a `changed` name's new row lives in `roster`.
10903
+ 🔴 OPEN on purpose (`additionalProperties: true`), unlike the frame that carries it. The rule: a schema is
10904
+ CLOSED where the SERVICE builds the object key by key (every `Event_*` arm), and OPEN where the service
10905
+ forwards a CORE-owned payload VERBATIM and keeps no key table of its own. This payload is the latter (the
10906
+ service passes `delta` through after core's own typebox check), so closing it would turn a future core key
10907
+ into "every legitimate frame is a violation". `ToolRoster` is closed despite the same provenance because
10908
+ the service DOES keep a compile-time key table for it.
10909
+ required: [fromDigest, roster, summary]
10910
+ additionalProperties: true
10911
+ properties:
10912
+ fromDigest:
10913
+ type: string
10914
+ description: 'The digest the consumer is expected to hold. A mismatch is a SKEW (re-sync from `roster`), never a rejection.'
10915
+ roster: { $ref: '#/components/schemas/ToolRoster' }
10916
+ summary:
10917
+ type: object
10918
+ required: [added, removed, changed]
10919
+ additionalProperties: true # same rule as the parent — core owns this payload's key table.
10920
+ properties:
10921
+ added: { type: array, items: { type: string } }
10922
+ removed: { type: array, items: { type: string } }
10923
+ changed: { type: array, items: { type: string } }
10588
10924
  ToolRoster:
10589
10925
  type: object
10590
10926
  description: >
@@ -10625,7 +10961,13 @@ components:
10625
10961
  capabilityId: { type: string }
10626
10962
  effect: { type: string, enum: [read, write, idempotent] }
10627
10963
  egress: { type: boolean }
10628
- irreversibility: { type: string, enum: [always, never] }
10964
+ irreversibility:
10965
+ type: string
10966
+ enum: [always, maybe, never]
10967
+ description: >
10968
+ core's THREE-word set (`never` / `maybe` / `always`). `maybe` is a real emitted value (e.g. a shell
10969
+ tool under a classifying gate), so a two-word mirror rejects an entire legitimate roster and leaves
10970
+ the consumer on stale state.
10629
10971
  contentOrigin: { type: string, enum: [local, execution, external] }
10630
10972
  family: { type: string, enum: [shell, file-read, file-write, search, web, delegate, plan, scaffold, memory, protocol, other] }
10631
10973
  pathTarget: { $ref: '#/components/schemas/McpToolPathTarget' }
@@ -11400,6 +11742,21 @@ components:
11400
11742
  server >= 7.69.0 (core 7.10.0 #616), ADDITIVE, present only when the classifier was consulted and
11401
11743
  could not run — WHY this ask reached a human. See ClassifierUnavailable for the open-`cause` rule and
11402
11744
  the three shapes absence covers.
11745
+ RETIRED: server >= 7.72.0 (core 7.14.0 #688) never mints this key on any of the three faces; its
11746
+ successor is `disposition.cause` on the gate record (`unavailable` / `parse_error` — a different
11747
+ question, so an old switch does not port). Kept for servers in [7.69.0, 7.72.0) only (sdk 9.2.0).
11748
+ ruleStoreUnreadable:
11749
+ type: string
11750
+ enum: [store, call]
11751
+ description: >-
11752
+ server >= 7.72.0 (core 7.14.0 #688 C3), ADDITIVE — WHICH HALF of the rule lane could not be read.
11753
+ Present iff `origin === "rule_store_unavailable"`: `store` = the wired rule store could not be read
11754
+ (look at the store / network); `call` = the store was read but THIS command could not be read against
11755
+ the person's deny/ask rows (look at the command spelling). One `origin` word covers both facts, and
11756
+ their recovery verbs live in different places (ops vs prompt). Carried on the live `tool_approval`
11757
+ frame and the inbox row; the `card_json` card deliberately does NOT carry it (it travels with `origin`,
11758
+ which is not on the card). Absence = `origin` is not that word — NOT an assertion that the rule store
11759
+ is healthy.
11403
11760
  ApprovalRequestFrame:
11404
11761
  type: object
11405
11762
  description: >
@@ -11538,6 +11895,9 @@ components:
11538
11895
  server >= 7.69.0 (core 7.10.0 #616), ADDITIVE — WHY this ask reached a human. Frame and card carry
11539
11896
  the SAME value (one narrow-read function server-side). See ClassifierUnavailable for the open-`cause`
11540
11897
  rule and the three shapes absence covers.
11898
+ RETIRED: server >= 7.72.0 (core 7.14.0 #688) never mints this key on any of the three faces; its
11899
+ successor is `disposition.cause` on the gate record (`unavailable` / `parse_error` — a different
11900
+ question, so an old switch does not port). Kept for servers in [7.69.0, 7.72.0) only (sdk 9.2.0).
11541
11901
 
11542
11902
  RuleSuggestion:
11543
11903
  type: object
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/sdk",
3
- "version": "9.0.0",
3
+ "version": "9.2.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",