approval.request
The thing a group chat cannot do: an action, a preview, a risk class, who may answer, and an audit record.
Direction: Harness → Chips
Sent by: a harness
Requires a run: yes
Schema: approval.request.json
Fields
Section titled “Fields”Every event also carries the envelope.
| Field | Required | Type | Description |
|---|---|---|---|
v |
yes | 1 |
Protocol major version. |
type |
yes | "approval.request" |
|
id |
yes | string |
The event’s own id. Stable across a retry of the same event. |
ts |
yes | string |
When it happened, by Chips’ clock. Stamped on the way in; a value the sender writes is replaced. |
workspace_id |
yes | string |
A workspace identifier: ws_ followed by a ULID. |
space_id |
no | string |
The space, when the event belongs to one. |
run_id |
no | string |
The run, when the event belongs to one. |
seq |
no | number |
Assigned by the server on append, monotonic per workspace. Absent on the way in. |
idempotency_key |
no | string |
Required on every client-originated write. Sending the same key twice is a no-op. |
actor |
yes | object | The participant an event is attributed to. |
actor.kind |
yes | "person" | "harness" | "agent" | "system" |
Whether a person, a harness, one of a harness’s agents or Chips itself acted. |
actor.id |
yes | string |
The participant id, or system for Chips. |
actor.harness_id |
no | string |
For an agent: the harness it speaks through, whose budget and runs are the agent’s (ADR 0087). Filled in by Chips. |
approval_id |
yes | string |
Minted by the harness, so a retry after a reconnect is the same ask. |
step_id |
no | string |
The plan step this ask belongs to. |
action |
yes | object | An action a harness proposes to take. |
action.kind |
yes | string |
A dotted verb, for example email.send or payment.transfer. Policy rules glob on it. |
action.input |
no | unknown |
The arguments, shaped by the tool being called. |
action.external |
no | boolean |
True when the action leaves the workspace. |
action.irreversible |
no | boolean |
True when the action cannot be undone. |
action.money_usd |
no | number |
Amount at stake, in US dollars. |
question |
no | string |
The question the person answers yes or no, as one sentence in plain words: Turn off the Robinhood connector?. The card leads with it. Without it the card is headed by the preview’s title, else the action’s kind (ADR 0142). |
effect |
no | string |
What a yes does, in a sentence or two: Its 73 tools disappear from every Space. Drawn under the question. |
lasts |
no | "once" | "standing" |
How long a yes keeps working: once for this action alone, standing for a grant that holds until it is taken back, such as a folder an agent may keep working in. The card says which. |
preview |
no | object | What the approver sees. Omit only when the action speaks for itself. |
preview.format |
yes | "markdown" | "plain" | "diff" | "json" | "table" |
How to render the body. |
preview.body |
yes | string |
The rendered preview. |
preview.title |
no | string |
A heading for the card. |
risk |
yes | enum[] | Risk classes. The policy engine matches rules against these. One of external, external_communication, irreversible, destructive, financial, credential, privacy, legal. |
sources |
no | object[] | Where the harness got what it is about to act on. |
sources[].kind |
yes | "external" | "internal" |
Whether the source is outside the workspace. |
sources[].label |
yes | string |
What to show in the receipt, for example vendor website. |
sources[].url |
no | string |
Where to look, when there is somewhere to look. |
expires_in |
yes | number |
Seconds until the approval expires. Every approval has an expiry and a fallback. |
fallback |
no | "deny" | "allow" |
What happens on expiry. Denying is the default, and the safe one. |
approvers |
no | string[] |
Who may answer, as the harness asks it: owner, any (the room, but its guests), role:<name> (a standing in the space or in the workspace), or a person id. lead (the lane’s lead, when the run is a lane’s) is a policy rule’s to name, never a harness’s, and never for an ask that matters — money, a deletion, an external action reach a person. Chips resolves them — policy’s own list when a rule names one — to ids before the event is stored, and only those may decide it; a person may hand it on with approval.routed. |
editable |
no | boolean |
Whether the approver may change the input before allowing. |
resume_token |
no | string |
Lets a paused command-line run continue hours later. Approvals outlive processes. |
Example
Section titled “Example”{ "v": 1, "type": "approval.request", "id": "evt_01K4ZQPX000000000000000068", "ts": "2026-09-02T14:22:04.004Z", "workspace_id": "ws_01K4ZQPX000000000000000001", "space_id": "spc_01K4ZQPX000000000000000002", "actor": { "kind": "harness", "id": "hrn_01K4ZQPX000000000000000004" }, "run_id": "run_01K4ZQPX000000000000000005", "approval_id": "apr_01K4ZQPX000000000000000006", "step_id": "email-dana", "action": { "kind": "email.send", "input": { "to": "dana@northwind.com", "subject": "PO #4471 confirmation" }, "external": true, "irreversible": true }, "question": "Send this email to dana@northwind.com?", "effect": "It confirms PO #4471 for 120 units, delivered 19 September.", "lasts": "once", "preview": { "format": "markdown", "title": "To dana@northwind.com", "body": "Hi Dana, confirming PO #4471 for 120 units, delivery 19 September. Shout if anything looks off." }, "risk": ["external_communication", "irreversible"], "sources": [ { "kind": "external", "label": "vendor website", "url": "https://northwind.example/contact" } ], "expires_in": 3600, "fallback": "deny", "approvers": ["usr_01K4ZQPX000000000000000003"], "editable": true, "resume_token": "rt_9f2c7d0184a05c3b"}