Skip to content

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

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.
{
"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"
}