@sema-agent/sdk 6.6.0 → 6.7.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 +68 -0
- package/dist/errors.d.ts +105 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +195 -9
- package/dist/errors.js.map +1 -1
- package/dist/events.d.ts +94 -0
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js.map +1 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -2
- package/dist/index.js.map +1 -1
- package/dist/resources/tool-approvals.d.ts +262 -1
- package/dist/resources/tool-approvals.d.ts.map +1 -1
- package/dist/resources/tool-approvals.js +139 -1
- package/dist/resources/tool-approvals.js.map +1 -1
- package/dist/settings.d.ts +30 -7
- package/dist/settings.d.ts.map +1 -1
- package/dist/settings.js +1 -1
- package/dist/types.d.ts +68 -9
- package/dist/types.d.ts.map +1 -1
- package/openapi.yaml +628 -14
- package/package.json +2 -2
package/openapi.yaml
CHANGED
|
@@ -210,10 +210,10 @@ paths:
|
|
|
210
210
|
schema: { $ref: '#/components/schemas/TaskResult' }
|
|
211
211
|
'400': { $ref: '#/components/responses/BadRequest' }
|
|
212
212
|
'401': { $ref: '#/components/responses/Unauthorized' }
|
|
213
|
-
'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 起默认内存店自带该能力 ⇒ 该触发条件在默认部署上不再出现。
|
|
213
|
+
'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 里(**那段是被解析的**,本行注释只是指路,不复述以免两处漂移)。
|
|
214
214
|
'409': { $ref: '#/components/responses/Conflict' }
|
|
215
215
|
'429': { $ref: '#/components/responses/RateLimited' }
|
|
216
|
-
'503': { $ref: '#/components/responses/Unauthorized' }
|
|
216
|
+
'503': { $ref: '#/components/responses/Unauthorized' } # ⚠️ (declared 2026-08-05,server >=7.0.0)本操作的 503 producer 不止 auth 门 —— producer 清单在 `Unauthorized` 组件的 description、等待提示的在场矩阵在该组件的 `headers.Retry-After`(**两段都是被解析的**;本行只指路,不复述以免漂移)。
|
|
217
217
|
get:
|
|
218
218
|
tags: [trace]
|
|
219
219
|
operationId: traceList
|
|
@@ -515,6 +515,15 @@ paths:
|
|
|
515
515
|
errorCode "steering.not_running" — the run is terminal, is running on ANOTHER replica
|
|
516
516
|
(cross-replica live-steer is not wired), or the durable park lost its CAS (the checkpoint
|
|
517
517
|
resolved/expired in between). NOT the ordinary suspended case — that one parks and 202s.
|
|
518
|
+
errorCode "steering.queue_full" (server >=7.3.0 / core 5.14.0) — the run IS parked, but the
|
|
519
|
+
steering queue on its checkpoint is full. The queue is bounded and FAIL-LOUD: the engine never
|
|
520
|
+
evicts an already-accepted instruction to make room. Opposite handling from not_running: the
|
|
521
|
+
text is still valid — let the run resume (which drains the queue) or reduce concurrent steers,
|
|
522
|
+
then resend.
|
|
523
|
+
errorCode "steering.duplicate_input_id" (server >=7.3.0 / core 5.14.0) — the Idempotency-Key you
|
|
524
|
+
sent is already parked with DIFFERENT steering content (same key + same content is an idempotent
|
|
525
|
+
no-op and 202s). The engine refuses rather than silently picking a side. Reissue with a fresh
|
|
526
|
+
key — this is a new instruction, not a retry.
|
|
518
527
|
content:
|
|
519
528
|
application/json:
|
|
520
529
|
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
|
@@ -2800,6 +2809,97 @@ paths:
|
|
|
2800
2809
|
'404': { description: Unknown/settled/expired/foreign approval (no existence oracle). }
|
|
2801
2810
|
'501': { description: Live tool-approval HITL not enabled (TOOL_APPROVAL_ENABLED). }
|
|
2802
2811
|
|
|
2812
|
+
/v1/tasks/{taskId}/asks/{askId}/decision:
|
|
2813
|
+
parameters:
|
|
2814
|
+
- $ref: '#/components/parameters/PrincipalHeader'
|
|
2815
|
+
- $ref: '#/components/parameters/TaskIdPath'
|
|
2816
|
+
- { name: askId, in: path, required: true, schema: { type: string }, description: 'From ApprovalRequestFrame.askId (the durable identity, NOT approvalId).' }
|
|
2817
|
+
post:
|
|
2818
|
+
tags: [questions]
|
|
2819
|
+
operationId: askDecision
|
|
2820
|
+
x-status: planned # design/172 §3.2 (#151 车4) — UNRELEASED; ships with server 7.3.0, gated OFF by STREAM_APPROVAL_ENABLED. SDK toolApprovals.decideAsk() 消费。
|
|
2821
|
+
summary: Settle an IN-STREAM approval card (design/172 streaming approval protocol).
|
|
2822
|
+
description: >
|
|
2823
|
+
The in-stream sibling of POST /v1/tool-approvals/{id}/respond: the approver is live on the SSE stream, so
|
|
2824
|
+
the ask does a stream round-trip and the run never parks. Keyed on `askId` (the DURABLE identity derived
|
|
2825
|
+
from a five-tuple), so the decision is accepted by ANY replica (the CAS lands in the persistent store).
|
|
2826
|
+
|
|
2827
|
+
Decision is a TWO-word set (`approve`/`deny`) — `allow_session` is a live-bridge-only semantic and is NOT
|
|
2828
|
+
on this endpoint. `actor` accepts ONLY `label`: `id`/`verified` are always server-minted and an actor
|
|
2829
|
+
NEVER participates in the permission decision (design/171 §5.1).
|
|
2830
|
+
|
|
2831
|
+
Response doctrine (design/172 §3.2, four rules): ① same `idempotencyKey` retried → the ORIGINAL 200
|
|
2832
|
+
replayed (no second CAS); ② already DECIDED with a DIFFERENT decision → 409 `conflict.ask_decided`
|
|
2833
|
+
carrying the first decision echo (`decision`/`decidedAtMs`/`actor`; `note` is NEVER echoed across
|
|
2834
|
+
permission boundaries); ③ PARKED/DENIED/VOID → 410 `gone.ask_parked` (with the gate reconciliation
|
|
2835
|
+
coordinates `{sessionId, gateBoundCallId, gateBoundInputHash}` — NEVER the checkpoint token) or
|
|
2836
|
+
`gone.ask`; ④ PARKING → 425 `parking.ask_retry` — retrying fetches the FINAL ROUTING INFO, it does NOT
|
|
2837
|
+
retry acceptance of the decision (the window is already closed).
|
|
2838
|
+
|
|
2839
|
+
Attribution split: DENIED is produced ONLY by routing failure (`routing_failure_fail_closed`); an
|
|
2840
|
+
operator refusal is DECIDED(decision=deny). A timeout NEVER produces a deny.
|
|
2841
|
+
requestBody:
|
|
2842
|
+
required: true
|
|
2843
|
+
content:
|
|
2844
|
+
application/json:
|
|
2845
|
+
schema: { $ref: '#/components/schemas/AskDecisionBody' }
|
|
2846
|
+
responses:
|
|
2847
|
+
'200':
|
|
2848
|
+
description: The decision was accepted (or replayed for the same idempotencyKey).
|
|
2849
|
+
content:
|
|
2850
|
+
application/json:
|
|
2851
|
+
schema: { $ref: '#/components/schemas/AskDecisionAck' }
|
|
2852
|
+
'400':
|
|
2853
|
+
description: 'Malformed body — `request.invalid_json` (unparseable) or `request.body_shape` (schema reject, e.g. an `actor` carrying anything but `label`).'
|
|
2854
|
+
content:
|
|
2855
|
+
application/json:
|
|
2856
|
+
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
|
2857
|
+
'401': { $ref: '#/components/responses/Unauthorized' }
|
|
2858
|
+
'404':
|
|
2859
|
+
description: >
|
|
2860
|
+
Unknown askId / taskId mismatch / non-owner — ONE shape, NO existence oracle (`not_found.ask`).
|
|
2861
|
+
NOT the cross-leg case: a park+resume mints a NEW askId, and the OLD row survives in its terminal
|
|
2862
|
+
state, so deciding against the old askId gets that row's terminal answer (200 idempotent replay /
|
|
2863
|
+
409 first-decision echo / 410) — the "no consent is ever reused across legs" guarantee comes from
|
|
2864
|
+
the new leg having its own independent row, not from the old one 404-ing.
|
|
2865
|
+
content:
|
|
2866
|
+
application/json:
|
|
2867
|
+
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
|
2868
|
+
'409':
|
|
2869
|
+
description: >
|
|
2870
|
+
Already settled with a DIFFERENT decision (`conflict.ask_decided`, first-decision echo in the body),
|
|
2871
|
+
or the idempotencyKey belongs to another ask (`conflict.ask_idempotency`).
|
|
2872
|
+
content:
|
|
2873
|
+
application/json:
|
|
2874
|
+
schema: { $ref: '#/components/schemas/AskDecidedConflictBody' }
|
|
2875
|
+
'410':
|
|
2876
|
+
description: >
|
|
2877
|
+
The window is closed and the outcome is already cast: `gone.ask_parked` (gate coordinates in the
|
|
2878
|
+
body — route to the durable gate flow) or `gone.ask` (`{state, deniedReason?}`). The two legs carry
|
|
2879
|
+
DISJOINT bodies.
|
|
2880
|
+
content:
|
|
2881
|
+
application/json:
|
|
2882
|
+
schema: { $ref: '#/components/schemas/AskGoneBody' }
|
|
2883
|
+
'425':
|
|
2884
|
+
description: >
|
|
2885
|
+
PARKING — transferring to the delivery plane. `parking.ask_retry`; the retry fetches FINAL ROUTING
|
|
2886
|
+
INFO, never acceptance. The body carries `retryAfterSec` and NO gate coordinates (they are not
|
|
2887
|
+
written until bind).
|
|
2888
|
+
headers:
|
|
2889
|
+
Retry-After:
|
|
2890
|
+
schema: { type: integer }
|
|
2891
|
+
description: 'Seconds to wait before polling again for the final routing info. Paired with the body''s `retryAfterSec`.'
|
|
2892
|
+
content:
|
|
2893
|
+
application/json:
|
|
2894
|
+
schema: { $ref: '#/components/schemas/AskParkingBody' }
|
|
2895
|
+
'501':
|
|
2896
|
+
description: >
|
|
2897
|
+
In-stream approval protocol not on this worker (`feature.approval_ask_disabled`) — no ask store, or
|
|
2898
|
+
STREAM_APPROVAL_ENABLED is off. Probe the capability, do not trial-by-501.
|
|
2899
|
+
content:
|
|
2900
|
+
application/json:
|
|
2901
|
+
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
|
2902
|
+
|
|
2803
2903
|
/v1/side-query:
|
|
2804
2904
|
parameters:
|
|
2805
2905
|
- $ref: '#/components/parameters/PrincipalHeader'
|
|
@@ -3794,7 +3894,26 @@ components:
|
|
|
3794
3894
|
application/json:
|
|
3795
3895
|
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
|
3796
3896
|
Unauthorized:
|
|
3797
|
-
description:
|
|
3897
|
+
description: >
|
|
3898
|
+
Fail-closed auth — 401 (bad token) / 503 (missing token); those two map to AuthError.
|
|
3899
|
+
⚠️ On the billable submit set (every POST that starts a model turn — the server's
|
|
3900
|
+
`isBillableSubmitPath`, which is wider than the three task/run endpoints) this
|
|
3901
|
+
503 slot is SHARED with producers that are not auth at all, so `errorCode` — not the status — is the
|
|
3902
|
+
branch key: `draining` (this replica is shutting down; retry against a replacement -> DrainingError),
|
|
3903
|
+
`state.model_roster_pending` (roster not loaded yet; retry -> ServiceStateError), and (server >=7.0.0,
|
|
3904
|
+
synchronous `POST /v1/tasks` only) `memory.admission_required` — the org-memory admission directory is
|
|
3905
|
+
transiently undecidable, refused fail-closed BEFORE any model call, mapped to MemoryAdmissionError.
|
|
3906
|
+
Which of them carry a wait hint is stated ONCE, under `headers.Retry-After` below (kept in one place so
|
|
3907
|
+
the two statements cannot drift apart).
|
|
3908
|
+
headers:
|
|
3909
|
+
Retry-After:
|
|
3910
|
+
schema: { type: integer }
|
|
3911
|
+
description: >
|
|
3912
|
+
Seconds to wait before retrying. Presence is per producer, not per status:
|
|
3913
|
+
`draining` always sends it (15), `state.model_roster_pending` always sends it (5),
|
|
3914
|
+
`memory.admission_required` sends it ONLY when the engine supplied a hint — a steady-state refusal
|
|
3915
|
+
(no resolver wired) honestly carries neither this header nor the body's `retryAfterSec`.
|
|
3916
|
+
The auth legs (401 bad token / 503 missing token) never send it.
|
|
3798
3917
|
content:
|
|
3799
3918
|
application/json:
|
|
3800
3919
|
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
|
@@ -3802,8 +3921,19 @@ components:
|
|
|
3802
3921
|
description: >
|
|
3803
3922
|
Authorization denied on a resource the caller is authenticated for — 403. Distinct from Unauthorized
|
|
3804
3923
|
(401/503 = who are you / this worker refuses to serve): here the identity is established and rejected.
|
|
3805
|
-
|
|
3806
|
-
|
|
3924
|
+
SEVERAL producers share this status, so `errorCode` — not the status — is the branch key. Known today:
|
|
3925
|
+
(a) session-ownership — reusing a `sessionId` that belongs to another principal; mapped to AuthError.
|
|
3926
|
+
(b) `execution_lane_not_allowed` — this worker's execution lane is outside the principal's policy
|
|
3927
|
+
allowlist; the body carries `allowedLanes` + `lane`. Mapped to plain APIError (no typed class yet);
|
|
3928
|
+
raised while PREPARING a task, so it can appear on any submit endpoint, not only this one.
|
|
3929
|
+
(c) `memory.admission_denied` (server >=7.0.0, synchronous `POST /v1/tasks`) — the principal holds no
|
|
3930
|
+
grant on the requested org-memory tenant plane, or the verdict tried to widen the request, or the
|
|
3931
|
+
request asked for an org scope with NO principal at all (that last one is the one case on this status
|
|
3932
|
+
where the caller is not authenticated — the engine refuses a principal-less request to a tenant plane
|
|
3933
|
+
rather than reading it as anonymous-allowed); refused
|
|
3934
|
+
before any model call, nothing billed, and a retry cannot change the verdict (change the scope or have
|
|
3935
|
+
an operator grant it). Mapped to MemoryAdmissionError, NOT AuthError.
|
|
3936
|
+
Treat the list as OPEN: an unrecognized 403 code means "authenticated and refused", nothing finer.
|
|
3807
3937
|
content:
|
|
3808
3938
|
application/json:
|
|
3809
3939
|
schema: { $ref: '#/components/schemas/ErrorResponse' }
|
|
@@ -4143,14 +4273,30 @@ components:
|
|
|
4143
4273
|
type: string
|
|
4144
4274
|
enum: [default, plan, acceptEdits, bypassPermissions, auto]
|
|
4145
4275
|
description: >
|
|
4146
|
-
CC permission-mode INTENT
|
|
4147
|
-
|
|
4276
|
+
CC-parity permission-mode INTENT — the five values THIS wire's closed submit-gate accepts (post-[816]/
|
|
4277
|
+
[820]/[822]; not a claim these are CC's/sema-cli's complete mode vocabulary, which is wider, e.g.
|
|
4278
|
+
`dontAsk`). The frontend (TUI shell / web portal) carries the RAW mode; the SERVICE interprets it TIGHTEN-ONLY vs the deployment baseline:
|
|
4148
4279
|
`plan` ⇒ mount present_plan (core enablePlanMode) + read-only hands (CC EnterPlanMode research);
|
|
4149
|
-
`default` adds the manual ask gate
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4280
|
+
`default` adds the manual ask gate on non-exempt writes (scratchpad dirs / remembered session
|
|
4281
|
+
exemptions still auto-allow — not literally every Write/Edit); the server emits the SAME gate-policy
|
|
4282
|
+
for `auto` as for `default` — sending `auto` is NOT itself what arms core's auto-mode classifier; that
|
|
4283
|
+
classifier is armed purely by the principal's entitlement (`runtimeCaps.autoMode===true` plus a
|
|
4284
|
+
deployment `deps.autoMode` wiring), independent of which mode word the request sends: an entitled
|
|
4285
|
+
principal's `default` request is screened too, and a non-entitled principal's `auto` request behaves
|
|
4286
|
+
as plain `default`; `acceptEdits` adds the SAME gate narrowed to auto-allow inside the working
|
|
4287
|
+
directory — but only when the service has a trusted working directory to scope it to; on a sandbox
|
|
4288
|
+
execution lane with none wired, `acceptEdits` degrades to asking on every non-exempt write, same
|
|
4289
|
+
as `default`; `bypassPermissions` adds no mode-derived gate at all. None of the five can ever subtract
|
|
4290
|
+
from the deployment/operator policy (deny-wins). Omitting this field is NOT itself "no mode-derived
|
|
4291
|
+
gate" — this field wins over a `settings.permissions.defaultMode` bundle when both are sent, but when
|
|
4292
|
+
THIS field is omitted the bundled mode (if any) still supplies one; only when neither carrier supplies
|
|
4293
|
+
a mode is there no mode-derived gate (the same posture as `bypassPermissions`). Sending this field is
|
|
4294
|
+
also NOT the same as leaving it unset — an explicit `default` actively installs the ask gate. A fresh
|
|
4295
|
+
submit with an unknown string is rejected fail-loud with 400 (sibling of `promptProfile`); the old
|
|
4296
|
+
silent most-asking-mode fallback no longer applies to fresh submits — a lenient version of that
|
|
4297
|
+
fallback survives only internally, for RESUME replay of a pre-gate persisted
|
|
4298
|
+
body. Carrying the intent (not pre-interpreted core fields) = one interpretation across frontends +
|
|
4299
|
+
service-owned governance.
|
|
4154
4300
|
attachmentIds:
|
|
4155
4301
|
type: array
|
|
4156
4302
|
maxItems: 16
|
|
@@ -4229,6 +4375,17 @@ components:
|
|
|
4229
4375
|
type: string
|
|
4230
4376
|
enum: [simple, classic]
|
|
4231
4377
|
description: Which prompt-assembly profile to use.
|
|
4378
|
+
toolMaterializeStrategy:
|
|
4379
|
+
type: string
|
|
4380
|
+
enum: [static, swap]
|
|
4381
|
+
description: >
|
|
4382
|
+
server >=7.3.0 (core 5.14.0, board [2856]2) — how a DEFERRED tool (see `deferTools`) supplies its
|
|
4383
|
+
schema AFTER ToolSearch activates it. `static` keeps the placeholder (activation only flips
|
|
4384
|
+
callability; the advertised schema stays the empty object); `swap` advertises the REAL schema on the
|
|
4385
|
+
next request. A provider whose decoding is CONSTRAINED by the advertised schema loops UNBOUNDED under
|
|
4386
|
+
`static` (every round emits `{}` and the next round still advertises an empty schema), which is why
|
|
4387
|
+
this is a per-task knob. OMIT to inherit the engine chain (`spec ?? env ?? static`) — sending an
|
|
4388
|
+
explicit value takes that decision away from the deployment env lane. Unknown value => 400 fail-loud.
|
|
4232
4389
|
clientContext:
|
|
4233
4390
|
type: object
|
|
4234
4391
|
description: Non-authoritative client hints (never trusted for authz).
|
|
@@ -4246,8 +4403,11 @@ components:
|
|
|
4246
4403
|
description: >
|
|
4247
4404
|
TOC `settings.json` contract carried to the worker (CC-parity v1: permissions/hooks/env/model/
|
|
4248
4405
|
outputStyle). The SDK owns the schema in `src/settings.ts` (`SemaSettings`) — it is deliberately NOT
|
|
4249
|
-
duplicated here as a component
|
|
4250
|
-
|
|
4406
|
+
duplicated here as a component to keep ONE schema authority instead of two drifting copies. The
|
|
4407
|
+
service ACTIVELY interprets this bundle (task-settings.ts: permissions + permissions.defaultMode +
|
|
4408
|
+
model + outputStyle + env shipped in the 1.26.0 v1 scope; hooks was deferred at that point and wired
|
|
4409
|
+
in a later batch — both are live and interpreted today, not a raw uninterpreted passthrough);
|
|
4410
|
+
`permissions.defaultMode`'s specific fold is documented on `TaskRequest.permissionMode`. OPEN object.
|
|
4251
4411
|
additionalProperties: true
|
|
4252
4412
|
cwd: { type: string, description: Working directory for the execution env. }
|
|
4253
4413
|
selfOrchestration: { type: boolean, description: Permit S8 self-orchestration (run_workflow) inside this task. }
|
|
@@ -4455,7 +4615,27 @@ components:
|
|
|
4455
4615
|
description: >
|
|
4456
4616
|
Result-level terminal code (NOT an HTTP error). Includes `budget.precall` / `budget.exceeded`
|
|
4457
4617
|
(cost gate, non-retryable) — branch on this, not on a 4xx (pinned wire contract).
|
|
4618
|
+
Governance codes also arrive HERE, and WHICH leg renders them as an HTTP status differs per code —
|
|
4619
|
+
do not generalize from one to the other:
|
|
4620
|
+
(a) `memory.admission_denied` / `memory.admission_required` (server >=7.0.0) are remapped to 403/503
|
|
4621
|
+
ONLY on the synchronous `POST /v1/tasks`; on `POST /v1/runs` -> `GET /v1/runs/{taskId}` and on the
|
|
4622
|
+
SSE `done` frame they ride a 200 result (those legs have no second chance at a status line).
|
|
4623
|
+
(b) `usage.window_exhausted` is a 429 only when the PRE-ADMISSION gate catches it (all three submit
|
|
4624
|
+
endpoints). When the window is exhausted in the race after admission, the engine's refusal is NOT
|
|
4625
|
+
remapped on any leg — it lands in the result, including a 200 from the synchronous `POST /v1/tasks`.
|
|
4626
|
+
Branch on this field, never on the status alone.
|
|
4458
4627
|
errorMessage: { type: string }
|
|
4628
|
+
retryAfterMs:
|
|
4629
|
+
type: integer
|
|
4630
|
+
description: >
|
|
4631
|
+
Wait hint on the WAIT-BEARING governance results — MILLISECONDS, verbatim from the engine. NOT the
|
|
4632
|
+
same unit as the error body's `retryAfterSec` (seconds) / the `Retry-After` header.
|
|
4633
|
+
"Wait-bearing" is not the same as "transient": the engine classifies `memory.admission_required` as
|
|
4634
|
+
TRANSIENT (the same submission may succeed once the directory answers) and `usage.window_exhausted`
|
|
4635
|
+
as TERMINAL for that run while still retryable by a LATER submission once the window frees — both
|
|
4636
|
+
carry this field WHEN the engine has a concrete wait to report. It is absent when there is none to
|
|
4637
|
+
report (a steady-state `memory.admission_required` with no resolver wired), on
|
|
4638
|
+
`memory.admission_denied` (a retry cannot change the verdict), and on every non-governance outcome.
|
|
4459
4639
|
stats: { $ref: '#/components/schemas/TaskStats' }
|
|
4460
4640
|
verification:
|
|
4461
4641
|
type: object
|
|
@@ -6960,6 +7140,9 @@ components:
|
|
|
6960
7140
|
- $ref: '#/components/schemas/Event_elicitation'
|
|
6961
7141
|
- $ref: '#/components/schemas/Event_elicitation_complete'
|
|
6962
7142
|
- $ref: '#/components/schemas/Event_workflow_complete'
|
|
7143
|
+
# [2854] core 5.14.0 TaskEvent 16->18 (server 7.3.0 pickup): both new arms, projected.
|
|
7144
|
+
- $ref: '#/components/schemas/Event_human_input'
|
|
7145
|
+
- $ref: '#/components/schemas/Event_wiring_manifest'
|
|
6963
7146
|
discriminator:
|
|
6964
7147
|
propertyName: type
|
|
6965
7148
|
mapping:
|
|
@@ -6996,6 +7179,8 @@ components:
|
|
|
6996
7179
|
elicitation: '#/components/schemas/Event_elicitation'
|
|
6997
7180
|
elicitation_complete: '#/components/schemas/Event_elicitation_complete'
|
|
6998
7181
|
workflow_complete: '#/components/schemas/Event_workflow_complete'
|
|
7182
|
+
human_input: '#/components/schemas/Event_human_input'
|
|
7183
|
+
wiring_manifest: '#/components/schemas/Event_wiring_manifest'
|
|
6999
7184
|
|
|
7000
7185
|
# design/99 §E2 (SHIPPED, core TaskEventIdentity) — additive per-event identity on every CONTENT event (NOT on
|
|
7001
7186
|
# the lifecycle arms turn_end/compacted/suspended/done/failed). eventId = uuidv7 dedup/resume handle (the durable
|
|
@@ -7271,6 +7456,13 @@ components:
|
|
|
7271
7456
|
properties:
|
|
7272
7457
|
type: { const: task_progress }
|
|
7273
7458
|
taskId: { type: string, description: 'The subagent task id (the delegated sub-run).' }
|
|
7459
|
+
taskType:
|
|
7460
|
+
type: string
|
|
7461
|
+
description: >
|
|
7462
|
+
server >=7.3.0 (core 5.14.0, board [2854] six-car batch item 2) — the DELEGATION KIND discriminator
|
|
7463
|
+
(`background_agent` | `workflow`), so a consumer routes a progress frame without inferring the kind
|
|
7464
|
+
from the id shape. ABSENT on a synchronous child's frames (absence means "no fleet row" — tolerate
|
|
7465
|
+
it). Open on read.
|
|
7274
7466
|
usage:
|
|
7275
7467
|
type: object
|
|
7276
7468
|
description: 'Cumulative usage as of this turn boundary (closed 3-field set, byte-aligned to core).'
|
|
@@ -7289,6 +7481,121 @@ components:
|
|
|
7289
7481
|
parentToolCallId: { type: string }
|
|
7290
7482
|
sourceTaskId: { type: string }
|
|
7291
7483
|
bgAgentId: { type: string }
|
|
7484
|
+
Event_human_input:
|
|
7485
|
+
type: object
|
|
7486
|
+
description: >
|
|
7487
|
+
server >=7.3.0 (core 5.14.0 design/171) — HUMAN INPUT LIFECYCLE: who fed this run what, and whether it
|
|
7488
|
+
landed. One frame per carrier (objective / live steer / nextTurn / parked-steer resume / wake). It carries
|
|
7489
|
+
NO body text: this is the attribution + delivery ledger, not the message (join content through
|
|
7490
|
+
`message_committed` + stream order). Live emissions PRECEDE the commit and omit `entryId`.
|
|
7491
|
+
SERVICE PROJECTION: core's `principal` is deliberately NOT forwarded (the stream is already owner-scoped —
|
|
7492
|
+
the frame does not restate the tenant). `issuer` / `actor.id` / `actor.issuer` are host-derived identity
|
|
7493
|
+
strings and arrive SECRET-REDACTED.
|
|
7494
|
+
allOf: [{ $ref: '#/components/schemas/EventIdentity' }]
|
|
7495
|
+
additionalProperties: false # 封闭 + identity 本地镜像:见 Event_reasoning 处的长注
|
|
7496
|
+
required: [type, inputId]
|
|
7497
|
+
properties:
|
|
7498
|
+
type: { const: human_input }
|
|
7499
|
+
inputId: { type: string, description: 'Stable per-input id (also the durable steering queue idempotency key).' }
|
|
7500
|
+
sessionSeq: { type: integer, description: 'Leg-scoped monotone sequence of human inputs.' }
|
|
7501
|
+
carrier: { type: string, description: 'Which carrier delivered it (steer / objective / a source word). Open on read.' }
|
|
7502
|
+
source: { type: string, description: 'objective | steer | next_turn | wake | external | system. Open on read.' }
|
|
7503
|
+
delivery: { type: string, description: 'applied | queued | parked_for_wake. Open on read.' }
|
|
7504
|
+
issuer: { type: string, description: 'Who asserted the identity (host-derived; REDACTED).' }
|
|
7505
|
+
actor:
|
|
7506
|
+
type: object
|
|
7507
|
+
description: >
|
|
7508
|
+
Speaker attribution — NEVER authority. `hostAsserted:false` means the host did NOT derive the identity
|
|
7509
|
+
from ingress credentials; render it as unverified (core's own `[from ...]` renderer appends
|
|
7510
|
+
" (unverified)" on exactly this bit). `id`/`issuer` arrive REDACTED.
|
|
7511
|
+
additionalProperties: false
|
|
7512
|
+
properties:
|
|
7513
|
+
id: { type: string }
|
|
7514
|
+
hostAsserted: { type: boolean }
|
|
7515
|
+
issuer: { type: string }
|
|
7516
|
+
entryId: { type: string, description: 'The committed session entry this input became (absent on the live pre-commit emission).' }
|
|
7517
|
+
eventId: { type: string }
|
|
7518
|
+
parentToolCallId: { type: string }
|
|
7519
|
+
sourceTaskId: { type: string }
|
|
7520
|
+
bgAgentId: { type: string }
|
|
7521
|
+
Event_wiring_manifest:
|
|
7522
|
+
type: object
|
|
7523
|
+
description: >
|
|
7524
|
+
server >=7.3.0 (core 5.14.0 design/173) — ASSEMBLY SELF-EVIDENCE: what this leg actually wired, emitted at
|
|
7525
|
+
prepare. Root / child / resume legs each emit their OWN manifest (a delegated child's rides the CHILD's
|
|
7526
|
+
stream by design). SERVICE PROJECTION: the `governance` section core stamps `audience:"operator"` is
|
|
7527
|
+
STRIPPED by the server before this frame reaches a tenant stream (core contract: "no projection => do not
|
|
7528
|
+
disclose") — it is deliberately absent from this schema. `configFingerprint` is ALSO stripped for the same
|
|
7529
|
+
reason: core hashes the whole manifest (governance included) unsalted, and governance is four booleans —
|
|
7530
|
+
sixteen combinations a tenant could brute-force against everything else it already receives, recovering the
|
|
7531
|
+
section that was removed. Both come back on the operator-scoped projection, not here.
|
|
7532
|
+
allOf: [{ $ref: '#/components/schemas/EventIdentity' }]
|
|
7533
|
+
additionalProperties: false # 封闭 + identity 本地镜像:见 Event_reasoning 处的长注
|
|
7534
|
+
required: [type, manifest]
|
|
7535
|
+
properties:
|
|
7536
|
+
type: { const: wiring_manifest }
|
|
7537
|
+
manifest:
|
|
7538
|
+
type: object
|
|
7539
|
+
additionalProperties: false
|
|
7540
|
+
properties:
|
|
7541
|
+
schemaVersion: { type: integer }
|
|
7542
|
+
leg:
|
|
7543
|
+
type: object
|
|
7544
|
+
additionalProperties: false
|
|
7545
|
+
properties: { kind: { type: string, description: 'root | child | resume. Open on read.' } }
|
|
7546
|
+
ask:
|
|
7547
|
+
type: object
|
|
7548
|
+
additionalProperties: false
|
|
7549
|
+
description: 'Approval seam: how it is wired, where it came from, and what it effectively resolves to.'
|
|
7550
|
+
properties:
|
|
7551
|
+
form: { type: string }
|
|
7552
|
+
provenance: { type: string, description: 'spec | deps. Open on read.' }
|
|
7553
|
+
effective: { type: string }
|
|
7554
|
+
question:
|
|
7555
|
+
type: object
|
|
7556
|
+
additionalProperties: false
|
|
7557
|
+
description: >
|
|
7558
|
+
Question channel — three-valued (wired / absent / stripped_bg_lane, the last meaning the engine
|
|
7559
|
+
stripped a bg child per-request face). `interactiveToolsWithoutDeliveryFace` is the composition-lie flag.
|
|
7560
|
+
properties:
|
|
7561
|
+
wired: { type: string }
|
|
7562
|
+
provenance: { type: string }
|
|
7563
|
+
interactiveToolsWithoutDeliveryFace: { type: boolean }
|
|
7564
|
+
interaction:
|
|
7565
|
+
type: object
|
|
7566
|
+
additionalProperties: false
|
|
7567
|
+
properties: { posture: { type: string, description: 'interactive | headless | absent. Open on read.' } }
|
|
7568
|
+
elicit:
|
|
7569
|
+
type: object
|
|
7570
|
+
additionalProperties: false
|
|
7571
|
+
properties:
|
|
7572
|
+
seamWired: { type: boolean }
|
|
7573
|
+
serversOptedIn: { type: integer }
|
|
7574
|
+
parkLane:
|
|
7575
|
+
type: object
|
|
7576
|
+
additionalProperties: false
|
|
7577
|
+
description: >
|
|
7578
|
+
Durable park lane: capability vs EFFECTIVE policy. `effective` is THREE-VALUED — `"unresolved"`
|
|
7579
|
+
means the engine has not decided yet; never fold it to false.
|
|
7580
|
+
properties:
|
|
7581
|
+
capable: { type: boolean }
|
|
7582
|
+
effective: { oneOf: [{ type: boolean }, { type: string }] }
|
|
7583
|
+
reasons: { type: array, items: { type: string } }
|
|
7584
|
+
checkpointDurability: { type: string }
|
|
7585
|
+
session:
|
|
7586
|
+
type: object
|
|
7587
|
+
additionalProperties: false
|
|
7588
|
+
properties: { store: { type: string } }
|
|
7589
|
+
fleet:
|
|
7590
|
+
type: object
|
|
7591
|
+
additionalProperties: false
|
|
7592
|
+
properties:
|
|
7593
|
+
backgroundAgentStore: { type: boolean }
|
|
7594
|
+
hostChildEventSink: { type: boolean }
|
|
7595
|
+
eventId: { type: string }
|
|
7596
|
+
parentToolCallId: { type: string }
|
|
7597
|
+
sourceTaskId: { type: string }
|
|
7598
|
+
bgAgentId: { type: string }
|
|
7292
7599
|
Event_workspace_changed:
|
|
7293
7600
|
type: object
|
|
7294
7601
|
description: >
|
|
@@ -7655,6 +7962,313 @@ components:
|
|
|
7655
7962
|
enum: [allowed, denied, expired]
|
|
7656
7963
|
description: '"tool_approval_complete" only. `expired` = TTL/abort/disconnect — a fail-closed DENY the shell should render as such, not as "still pending".'
|
|
7657
7964
|
|
|
7965
|
+
# ── design/172 流内审批协议(#151;UNRELEASED,候 server 7.3.0 + STREAM_APPROVAL_ENABLED,默认 OFF)──
|
|
7966
|
+
# 与上面的 ToolApprovalFrame 是**并行加帧,不替换**:开关打开时同一只 ask 出两帧(先 tool_approval,
|
|
7967
|
+
# 后 approval_request),两帧同带 `approvalId` ⇒ 消费端按它去重、优先渲染新帧。
|
|
7968
|
+
ApprovalRequestFrame:
|
|
7969
|
+
type: object
|
|
7970
|
+
description: >
|
|
7971
|
+
IN-STREAM approval card (SSE, `type` IS the event name; design/172 §3.1). NOT an AgentEvent arm — it
|
|
7972
|
+
interleaves on the live token stream like tool_approval/question/elicitation. Settle it via
|
|
7973
|
+
POST /v1/tasks/{taskId}/asks/{askId}/decision.
|
|
7974
|
+
|
|
7975
|
+
WINDOW: `expiresAtMs` is cast ONCE and never recomputed; `expiresInMs` = max(0, expiresAtMs - serverNowMs)
|
|
7976
|
+
and is therefore MONOTONICALLY DECREASING across replays — a reconnect NEVER renews the window.
|
|
7977
|
+
|
|
7978
|
+
REPLAY: on a new connection the server replays every STREAM_PENDING card as a PREAMBLE, and that preamble
|
|
7979
|
+
is the shell's FULL RECONCILIATION BASELINE — any local card not in it must be dropped (revoke frames can
|
|
7980
|
+
be lost across a process boundary; the preamble is the structural compensation). Historical approval
|
|
7981
|
+
frames replayed from the durable events tail are for TIMELINE RENDERING ONLY, not a card-set baseline
|
|
7982
|
+
(their `expiresInMs` is the value frozen when the frame was minted). Preamble frames carry no SSE `id:`
|
|
7983
|
+
and take no part in the Last-Event-ID cursor.
|
|
7984
|
+
|
|
7985
|
+
UNKNOWN SHAPES ARE NEVER AUTO-DENIED: an unknown schemaVersion/kind or an unrenderable card gets a
|
|
7986
|
+
GENERIC card (safety metadata + manual approve/deny); if it cannot be shown, keep the frame and tell the
|
|
7987
|
+
operator, letting the window fall through to park. Folding "I don't understand it" into a refusal is the
|
|
7988
|
+
deadlock this protocol exists to remove.
|
|
7989
|
+
|
|
7990
|
+
NOT EVERY ASK HAS A CARD: the non-streaming POST /v1/tasks (no delivery plane) and council/team subtask
|
|
7991
|
+
legs produce none; an adhoc (non-durable) sync stream's taskId is one-shot and has no replay handle; a
|
|
7992
|
+
background child's ask arriving after the host run ended is NOT thrown at the dead stream — it parks onto
|
|
7993
|
+
the durable gate (the existing inbox surface).
|
|
7994
|
+
required: [type, schemaVersion, askId, taskId, kind, card, expiresInMs, expiresAtMs, serverNowMs, approvalId]
|
|
7995
|
+
additionalProperties: true
|
|
7996
|
+
properties:
|
|
7997
|
+
type: { type: string, enum: [approval_request] }
|
|
7998
|
+
schemaVersion:
|
|
7999
|
+
type: integer
|
|
8000
|
+
enum: [1]
|
|
8001
|
+
description: >
|
|
8002
|
+
Frame shape version. THIS SCHEMA DESCRIBES v1 ONLY — a frame carrying any other version is not this
|
|
8003
|
+
shape; read it as ApprovalFrameEnvelope and render the generic card. NEVER auto-deny.
|
|
8004
|
+
askId:
|
|
8005
|
+
type: string
|
|
8006
|
+
description: >
|
|
8007
|
+
The DURABLE identity (derived from the five-tuple sourceTaskId/runId/toolCallId/legKey/
|
|
8008
|
+
parentToolCallId). Dismissal, decision and reconciliation all key on THIS. Deduplicate by it (the
|
|
8009
|
+
same frame may be delivered repeatedly). VOID AFTER A PARK and never reused across legs — a resume
|
|
8010
|
+
re-asks with a NEW askId, so "the thing you just approved may be asked again" is normal and the
|
|
8011
|
+
shell must explain it.
|
|
8012
|
+
taskId: { type: string, description: 'wire run id — the {taskId} segment of the decision path.' }
|
|
8013
|
+
kind:
|
|
8014
|
+
type: string
|
|
8015
|
+
enum: [permission]
|
|
8016
|
+
description: >
|
|
8017
|
+
v1 closed set. `content` is a reserved slot, not implemented (design/172 §8③). A frame with any
|
|
8018
|
+
other kind is not this shape — read it as ApprovalFrameEnvelope and render the generic card.
|
|
8019
|
+
card: { $ref: '#/components/schemas/ApprovalCard' }
|
|
8020
|
+
expiresInMs: { type: integer, description: 'Remaining budget = max(0, expiresAtMs - serverNowMs). Monotonically decreasing across replays; the window is never renewed.' }
|
|
8021
|
+
expiresAtMs: { type: integer, description: 'Absolute server-clock deadline. Cast ONCE, never recomputed.' }
|
|
8022
|
+
serverNowMs: { type: integer, description: 'Server clock at mint time — the shell corrects its local clock skew with it.' }
|
|
8023
|
+
approvalId:
|
|
8024
|
+
type: string
|
|
8025
|
+
description: >
|
|
8026
|
+
Bridge to the legacy frame / dedupe key — same value as the parallel tool_approval frame. During the
|
|
8027
|
+
transition window POST /v1/tool-approvals/{approvalId}/respond still works.
|
|
8028
|
+
|
|
8029
|
+
ApprovalCard:
|
|
8030
|
+
type: object
|
|
8031
|
+
description: >
|
|
8032
|
+
The card payload — a NEUTRAL PROJECTION (design/172 §6.7: the server neither restates nor rewrites; the
|
|
8033
|
+
shell owns rendering). UNTRUSTED FOR DISPLAY: `message`/`args`/`sourceAgentName`/`delegation.agentName`
|
|
8034
|
+
are server-redacted and length-bounded but remain model/tool-authored bytes — render only, never re-feed
|
|
8035
|
+
to a model. `args` has a 16 KiB BYTE cap (UTF-8 bytes, not characters); over the cap it is omitted with
|
|
8036
|
+
`argsOmitted: true` and the shell must render an elision stub, NOT conclude "no arguments".
|
|
8037
|
+
required: [toolName, message, risk]
|
|
8038
|
+
additionalProperties: true
|
|
8039
|
+
properties:
|
|
8040
|
+
toolName: { type: string, description: 'Canonical core tool name.' }
|
|
8041
|
+
message: { type: string, description: 'The engine ask text, secret-redacted. UNTRUSTED for display.' }
|
|
8042
|
+
args: { description: 'The pending call''s args, redacted + byte-bounded. Absent (with argsOmitted) when over the cap or unserializable. UNTRUSTED for display.' }
|
|
8043
|
+
argsOmitted: { type: boolean, enum: [true], description: 'Present (true) only when args exceeded the byte cap. Never false.' }
|
|
8044
|
+
toolCallId: { type: string, description: 'The engine''s tool-call id (the `call_…` on the assistant message''s tool_use block) — same domain as ToolApprovalFrame.toolCallId.' }
|
|
8045
|
+
risk: { $ref: '#/components/schemas/ApprovalRiskAxes' }
|
|
8046
|
+
fromSubagent: { type: boolean, enum: [true], description: 'Present ⇔ the ask comes from a DELEGATED child — the explicit discriminator (a worker cannot forge it).' }
|
|
8047
|
+
sourceTaskId: { type: string, description: 'The child''s core session id — row-join value + fallback discriminator.' }
|
|
8048
|
+
sourceAgentName: { type: string, description: 'The child agent''s display name, redacted. UNTRUSTED.' }
|
|
8049
|
+
delegation: { $ref: '#/components/schemas/ApprovalCardDelegation' }
|
|
8050
|
+
|
|
8051
|
+
ApprovalRiskAxes:
|
|
8052
|
+
type: object
|
|
8053
|
+
description: >
|
|
8054
|
+
The two risk axes plus the coarse safety marker (design/172 §3.1; SDK ApprovalRiskAxes).
|
|
8055
|
+
|
|
8056
|
+
🔴 THREE-STATE, NOT TWO: true / false / ABSENT = the engine did not annotate it. The shell must render
|
|
8057
|
+
absence as "not annotated" and MUST NOT fold it to false — rendering "unknown" as "safe" is exactly the
|
|
8058
|
+
failure this contract forbids. The axes are optional because core exposes them additively on AskRequest,
|
|
8059
|
+
so an older engine/server omits them entirely.
|
|
8060
|
+
|
|
8061
|
+
`requiresRealApproval` is the ONE marker core always carries ("this is a safety-class ask" — the gate
|
|
8062
|
+
that refuses blanket-allow). It is NOT a substitute for, nor the conjunction of, the two axes: folding
|
|
8063
|
+
"deletes data" and "sends data out" into a single boolean erases the basis for the human decision.
|
|
8064
|
+
required: [requiresRealApproval]
|
|
8065
|
+
additionalProperties: true
|
|
8066
|
+
properties:
|
|
8067
|
+
irreversible: { type: boolean, description: 'The call is irreversible. ABSENT = not annotated — do not read as false.' }
|
|
8068
|
+
egress: { type: boolean, description: 'The call sends data across a boundary. ABSENT = not annotated — do not read as false.' }
|
|
8069
|
+
requiresRealApproval: { type: boolean, description: 'Coarse safety-class marker (core AskRequest.requiresRealApproval). Always present.' }
|
|
8070
|
+
|
|
8071
|
+
ApprovalCardDelegation:
|
|
8072
|
+
type: object
|
|
8073
|
+
description: 'Delegation provenance for a child ask (present only alongside fromSubagent).'
|
|
8074
|
+
required: [parentToolCallId, depth]
|
|
8075
|
+
additionalProperties: true
|
|
8076
|
+
properties:
|
|
8077
|
+
parentToolCallId: { type: string, description: 'The host-side delegating tool-call id — the card''s attribution anchor.' }
|
|
8078
|
+
depth: { type: integer, description: 'Delegation depth (a direct child of the host = 1).' }
|
|
8079
|
+
agentName: { type: string, description: 'Child agent name. UNTRUSTED — spawning-model free text (server-redacted and length-bounded).' }
|
|
8080
|
+
|
|
8081
|
+
ApprovalRevokeFrame:
|
|
8082
|
+
type: object
|
|
8083
|
+
description: >
|
|
8084
|
+
BATCH-LEVEL card revocation (SSE; design/172 §3.3). A batch = the blast radius of ONE run-level abort
|
|
8085
|
+
(every in-flight tool call of that run plus its synchronous delegation subtree). On receipt the shell
|
|
8086
|
+
clears that batch's local cards (whitelist-pick by `askIds`) and waits for the new askIds a resume mints.
|
|
8087
|
+
|
|
8088
|
+
Siblings already DECIDED are NOT in `askIds` (an accepted decision is not affected by revocation).
|
|
8089
|
+
Revoke frames CAN BE LOST (frames produced by the reconciler / orphan legs have no seq to allocate and
|
|
8090
|
+
ride the live plane only) — the structural compensation is the open-stream preamble baseline (see
|
|
8091
|
+
ApprovalRequestFrame), not a redelivery of this frame.
|
|
8092
|
+
required: [type, schemaVersion, batchId, askIds, reason, serverNowMs]
|
|
8093
|
+
additionalProperties: true
|
|
8094
|
+
properties:
|
|
8095
|
+
type: { type: string, enum: [approval_revoke] }
|
|
8096
|
+
schemaVersion: { type: integer, enum: [1], description: 'This schema describes v1 only (see ApprovalFrameEnvelope for the unknown-version read).' }
|
|
8097
|
+
batchId: { type: string }
|
|
8098
|
+
askIds: { type: array, items: { type: string }, description: 'The asks revoked (moved to VOID) by this frame.' }
|
|
8099
|
+
reason:
|
|
8100
|
+
type: string
|
|
8101
|
+
description: >
|
|
8102
|
+
`superseded_by_park` = a sibling''s window expired and the whole batch degraded to the durable park;
|
|
8103
|
+
`aborted` = the run was cancelled. Read as OPEN: an unknown reason is handled as a generic revoke
|
|
8104
|
+
(clear the cards), never specially.
|
|
8105
|
+
serverNowMs: { type: integer }
|
|
8106
|
+
|
|
8107
|
+
AskDecidedConflictBody:
|
|
8108
|
+
allOf:
|
|
8109
|
+
- $ref: '#/components/schemas/ErrorResponse'
|
|
8110
|
+
- type: object
|
|
8111
|
+
properties:
|
|
8112
|
+
decision:
|
|
8113
|
+
allOf: [{ $ref: '#/components/schemas/ApprovalAskDecision' }]
|
|
8114
|
+
description: 'FIRST-DECISION ECHO (`conflict.ask_decided` only): the decision that was accepted.'
|
|
8115
|
+
decidedAtMs: { type: integer, description: 'When the first decision was durably accepted.' }
|
|
8116
|
+
actor:
|
|
8117
|
+
allOf: [{ $ref: '#/components/schemas/ActorAssertion' }]
|
|
8118
|
+
description: 'Who decided. Omitted when the persisted actor fails its schema check (car4 §12-D).'
|
|
8119
|
+
description: >
|
|
8120
|
+
409 body. `conflict.ask_decided` carries the FIRST-DECISION ECHO; `conflict.ask_idempotency` carries the
|
|
8121
|
+
bare `errorCode` (the key belongs to a different ask — change the key, not the ask).
|
|
8122
|
+
🔴 `note` is NEVER echoed: the echo is bounded by the requester's visible permissions (design/172 §3.2).
|
|
8123
|
+
Retrying the same decision can never succeed — single-winner CAS means the outcome is already cast.
|
|
8124
|
+
|
|
8125
|
+
AskGoneBody:
|
|
8126
|
+
allOf:
|
|
8127
|
+
- $ref: '#/components/schemas/ErrorResponse'
|
|
8128
|
+
- type: object
|
|
8129
|
+
properties:
|
|
8130
|
+
sessionId: { type: string, description: 'gone.ask_parked ONLY — gate reconciliation coordinate 1; drive the existing durable-gate flow with it.' }
|
|
8131
|
+
gateBoundCallId: { type: string, description: 'gone.ask_parked ONLY — gate reconciliation coordinate 2.' }
|
|
8132
|
+
gateBoundInputHash: { type: string, description: 'gone.ask_parked ONLY — gate reconciliation coordinate 3.' }
|
|
8133
|
+
state: { type: string, enum: [DENIED, VOID], description: 'gone.ask ONLY — the terminal state.' }
|
|
8134
|
+
deniedReason:
|
|
8135
|
+
type: string
|
|
8136
|
+
description: >
|
|
8137
|
+
gone.ask ONLY — attribution, GROUPED BY `state`. `DENIED` carries the single-member deny
|
|
8138
|
+
vocabulary `routing_failure_fail_closed`; `VOID` carries the separate (and growing) void
|
|
8139
|
+
vocabulary, e.g. `adhoc_leg_no_durable_domain`. The two vocabularies never mix.
|
|
8140
|
+
description: >
|
|
8141
|
+
410 body. THE TWO LEGS ARE DISJOINT: `gone.ask_parked` carries exactly the three gate coordinates,
|
|
8142
|
+
`gone.ask` carries exactly `{state, deniedReason?}`. Routing off field presence across the legs is how a
|
|
8143
|
+
consumer ends up driving the wrong durable gate.
|
|
8144
|
+
🔴 The checkpoint token is NEVER here: the resume credential is not externally disclosed. Use
|
|
8145
|
+
`sessionId`.
|
|
8146
|
+
🔴 ATTRIBUTION SPLIT: `DENIED` is produced ONLY by routing failure. An operator refusal is
|
|
8147
|
+
`DECIDED(decision="deny")` and reaches the caller as a 200 or as the 409 first-decision echo — never as
|
|
8148
|
+
`DENIED`. A timeout never produces a deny.
|
|
8149
|
+
|
|
8150
|
+
AskParkingBody:
|
|
8151
|
+
allOf:
|
|
8152
|
+
- $ref: '#/components/schemas/ErrorResponse'
|
|
8153
|
+
- type: object
|
|
8154
|
+
properties:
|
|
8155
|
+
retryAfterSec: { type: integer, description: 'Suggested lower bound before polling again; paired with the Retry-After header.' }
|
|
8156
|
+
description: >
|
|
8157
|
+
425 body (`parking.ask_retry`). 🔴 NOT "retry may succeed": the window is already closed and the decision
|
|
8158
|
+
can never be accepted. The retry fetches the FINAL ROUTING INFO (poll until the 410 hands over the gate
|
|
8159
|
+
coordinates). Carries NO gate coordinates — they are not written until the batch binds.
|
|
8160
|
+
|
|
8161
|
+
ApprovalFrameEnvelope:
|
|
8162
|
+
type: object
|
|
8163
|
+
description: >
|
|
8164
|
+
The UNKNOWN-SHAPE read of either approval frame — the minimal common shape on the wire, and the typed
|
|
8165
|
+
counterpart of design/172's "unknown shapes are never auto-denied" clause.
|
|
8166
|
+
|
|
8167
|
+
ApprovalRequestFrame and ApprovalRevokeFrame describe **v1 only** (`schemaVersion: 1`, plus
|
|
8168
|
+
`kind: permission` on the request frame). Leaving those discriminants open would let a schema
|
|
8169
|
+
simultaneously say "any version may arrive" and keep promising the complete v1 `card` — which is exactly
|
|
8170
|
+
how a consumer ends up dereferencing `card.risk.requiresRealApproval` on a frame that never promised it.
|
|
8171
|
+
Read the frame as this envelope, narrow on the discriminants, and fall back to the generic card when it
|
|
8172
|
+
does not narrow.
|
|
8173
|
+
required: [type, schemaVersion]
|
|
8174
|
+
additionalProperties: true
|
|
8175
|
+
properties:
|
|
8176
|
+
type: { type: string, description: 'Echo of the SSE event name. Open: a third frame family may appear.' }
|
|
8177
|
+
schemaVersion: { type: integer, description: 'Frame shape version. Anything but 1 means this package does not describe the payload.' }
|
|
8178
|
+
|
|
8179
|
+
ApprovalAskDecision:
|
|
8180
|
+
type: string
|
|
8181
|
+
enum: [approve, deny]
|
|
8182
|
+
description: >
|
|
8183
|
+
The decision on an IN-STREAM approval card (design/172 §3.2). A TWO-word set — deliberately NOT the
|
|
8184
|
+
three-word ToolApprovalDecision: `allow_session` ("remember for this session") is a live-bridge-only
|
|
8185
|
+
semantic and does not exist on POST /v1/tasks/{taskId}/asks/{askId}/decision.
|
|
8186
|
+
|
|
8187
|
+
ActorAssertion:
|
|
8188
|
+
type: object
|
|
8189
|
+
description: >
|
|
8190
|
+
Decision-maker identity assertion (design/171 §5.1; the cross-repo source of truth is
|
|
8191
|
+
`@sema-agent/registry-core` ActorAssertionWire — the SDK holds a value copy).
|
|
8192
|
+
|
|
8193
|
+
NORMATIVE, not a style note: an ACTOR NEVER PARTICIPATES IN THE PERMISSION DECISION — authority always
|
|
8194
|
+
belongs to the principal; this shape exists only for audit/echo/presentation. `id` is ALWAYS server-minted
|
|
8195
|
+
(= the verified principal; an auth-off deployment mints the `"_"` sentinel), and a wire requester may
|
|
8196
|
+
submit ONLY `label`.
|
|
8197
|
+
required: [id]
|
|
8198
|
+
additionalProperties: false # registry-core's ActorAssertionWire is .strict()
|
|
8199
|
+
properties:
|
|
8200
|
+
id: { type: string, minLength: 1, maxLength: 256, description: 'Server-minted principal ("_" sentinel when auth is off). Never submittable by the requester.' }
|
|
8201
|
+
label: { type: string, maxLength: 256, description: 'Unverified display text — the ONLY field a requester may submit (server redacts + bounds it). UNTRUSTED.' }
|
|
8202
|
+
verified: { type: boolean, description: 'Whether the server minted this id under an authenticated context (auth-off ⇒ false, honestly). Never submittable.' }
|
|
8203
|
+
via:
|
|
8204
|
+
type: string
|
|
8205
|
+
enum: [owner, operator]
|
|
8206
|
+
description: >
|
|
8207
|
+
Audit axis for which authorization path accepted the decision: `owner` (row owner) or `operator`
|
|
8208
|
+
(explicit operator seat). CLOSED — verbatim-equal to the owning schema
|
|
8209
|
+
(registry-core ActorAssertionWire). Widening it here without widening the owner is copy drift.
|
|
8210
|
+
|
|
8211
|
+
AskDecisionBody:
|
|
8212
|
+
type: object
|
|
8213
|
+
description: 'Request body of POST /v1/tasks/{taskId}/asks/{askId}/decision (design/172 §3.2).'
|
|
8214
|
+
required: [decision]
|
|
8215
|
+
additionalProperties: false
|
|
8216
|
+
properties:
|
|
8217
|
+
decision: { $ref: '#/components/schemas/ApprovalAskDecision' }
|
|
8218
|
+
updatedInput:
|
|
8219
|
+
description: >
|
|
8220
|
+
Operator-edited args (WHOLE REPLACEMENT, not a patch). Valid on the approve arm only (leniently
|
|
8221
|
+
ignored on deny). Passed through verbatim: core re-runs PreToolUse + the full policy chain on the
|
|
8222
|
+
edit; the server interprets nothing. Whether it was forwarded shows up as AskDecisionAck
|
|
8223
|
+
.updatedInputForwarded.
|
|
8224
|
+
note:
|
|
8225
|
+
type: string
|
|
8226
|
+
maxLength: 2048
|
|
8227
|
+
description: >
|
|
8228
|
+
Audit note. NEVER echoed back on the 409 first-decision echo (the echo is bounded by the requester's
|
|
8229
|
+
visible permissions).
|
|
8230
|
+
idempotencyKey:
|
|
8231
|
+
type: string
|
|
8232
|
+
minLength: 1
|
|
8233
|
+
maxLength: 255
|
|
8234
|
+
description: >
|
|
8235
|
+
Decision idempotency key (<= 255). The same key retried replays the ORIGINAL 200 (no second CAS);
|
|
8236
|
+
the same key against a DIFFERENT askId is a 409 `conflict.ask_idempotency` — deliberately fail-loud,
|
|
8237
|
+
because replaying across asks would both mask a caller key-management bug and violate design/172''s
|
|
8238
|
+
"never reuse an old consent" clause.
|
|
8239
|
+
actor:
|
|
8240
|
+
type: object
|
|
8241
|
+
description: 'Display name of the decision-maker. ONLY `label` is accepted — id/verified are always server-minted; any extra key is a 400 request.body_shape.'
|
|
8242
|
+
required: [label]
|
|
8243
|
+
additionalProperties: false
|
|
8244
|
+
properties:
|
|
8245
|
+
label: { type: string, maxLength: 256 }
|
|
8246
|
+
|
|
8247
|
+
AskDecisionAck:
|
|
8248
|
+
type: object
|
|
8249
|
+
description: 'The 200 ack of POST /v1/tasks/{taskId}/asks/{askId}/decision (design/172 §3.2 rule ①; also the replay body for an idempotent retry).'
|
|
8250
|
+
required: [askId, taskId, decision, decidedAtMs]
|
|
8251
|
+
additionalProperties: false # server-composed envelope (car4 §6), not an engine passthrough — closed on purpose
|
|
8252
|
+
properties:
|
|
8253
|
+
askId: { type: string }
|
|
8254
|
+
taskId: { type: string }
|
|
8255
|
+
decision: { $ref: '#/components/schemas/ApprovalAskDecision' }
|
|
8256
|
+
decidedAtMs: { type: integer, description: 'Server instant at which the decision was durably accepted.' }
|
|
8257
|
+
actor:
|
|
8258
|
+
allOf: [{ $ref: '#/components/schemas/ActorAssertion' }]
|
|
8259
|
+
description: >
|
|
8260
|
+
OPTIONAL: when the persisted decision_actor fails its schema check the server OMITS this field
|
|
8261
|
+
rather than answering 500 (car4 §12-D). Never dereference `actor.id` without a presence check.
|
|
8262
|
+
updatedInputForwarded:
|
|
8263
|
+
type: boolean
|
|
8264
|
+
enum: [true]
|
|
8265
|
+
description: >
|
|
8266
|
+
Present (true) when the approve carried `updatedInput` AND a live in-process entry was actually
|
|
8267
|
+
settled with it (the server forwarded the edited args to the engine). 🔴 TRUE OR ABSENT, NEVER FALSE
|
|
8268
|
+
— so "no live entry (pure durable / another replica)" and "no edit sent" are indistinguishable on the
|
|
8269
|
+
wire. An idempotent replay does NOT promise byte-identity on this field: it is a function of THIS
|
|
8270
|
+
replica's live state and is not persisted.
|
|
8271
|
+
|
|
7658
8272
|
QuestionFrame:
|
|
7659
8273
|
type: object
|
|
7660
8274
|
description: >
|