Decide an approval
A run that waits for a person (a gated tool call or a long conversation) creates an approval. A reviewer decides it, and the run moves on.
Reviewers and roles
Section titled “Reviewers and roles”Only reviewers see approvals. Until you register, every approvals command is refused:
kindgi approvals listError [auth]: This token was not provisioned with a reviewer role; approvals surface is reviewer-only.Register yourself, the user your API token belongs to, with a role:
kindgi reviewers register --spec='{"role":"standard","displayName":"Ada Lovelace"}'{ "id": "b4bc07d5-1640-4c4c-9cbe-710aa6ca7d36", "tenantId": "3a9fb26d-9782-4bdb-a4f9-3c97d2644b72", "userId": "b9b6a8af-84ee-4514-a609-a76230750869", "role": "standard", "displayName": "Ada Lovelace", "createdAt": "2026-10-03T19:55:32.200Z"}The roles rank standard, senior, admin. Each approval has a
requiredRole, and a reviewer sees and decides the approvals that need their
role or a lower one. An approval that needs a higher role doesn't show up in
the list, and fetching or deciding it answers not-found.
Registering again changes the role. kindgi reviewers list shows the
reviewers; kindgi reviewers unregister <reviewer-id> removes one (their
past decisions stay).
Find what's waiting
Section titled “Find what's waiting”kindgi approvals list --status=pendingkindgi approvals get <approval-id>Without --status, the list includes decided approvals too. In each
approval:
subjectKindis what waits:tool-call:pendingis a tool call (thesubjectRefhas thetoolIdand thearguments);agent-turn:session-hitl-gateis a conversation that reached its turn limit (thesubjectRefhas theconversationIdand theturnCount).requiredRoleis the lowest role that may decide it.provenanceRef.runIdis the run that waits.
Decide
Section titled “Decide”kindgi approvals complete <approval-id> --decision=approve --rationale="Confirmed with the on-call engineer"--rationale is optional and is kept with the decision. The four decisions:
| Decision | The approval | The run |
|---|---|---|
approve |
approved |
Continues: the tool runs, or the turn runs. |
reject |
rejected |
A tool call: the tool doesn't run, and the agent gets the rejection as the tool's result. A turn: the run fails with hitl-rejected. |
escalate |
escalated, and a new approval for the next role up |
Keeps waiting for the new approval. |
withdraw |
withdrawn |
Keeps waiting; cancel it with kindgi runs cancel <run-id>. |
An approve or reject resumes the run within the same call, so the run has moved on by the time the command returns. A decided approval can't be decided again.
Escalate
Section titled “Escalate”A standard reviewer who wants a second opinion escalates:
kindgi approvals complete 2134897a-d176-4ca8-b000-8bc00d79ad04 --decision=escalate --rationale="Needs sign-off from the incident lead"{ "kind": "escalated", "approval": { "id": "2134897a-d176-4ca8-b000-8bc00d79ad04", … "requiredRole": "standard", "status": "escalated", … }, … "nextApproval": { "id": "a65cd23c-1bdb-47d8-9b3a-aaec36475a4f", … "requiredRole": "senior", "status": "pending", … }, "waitpointResolved": false}The new approval needs a senior reviewer, so the standard reviewer no
longer sees it. A senior (or admin) reviewer approves or rejects
a65cd23c-…, and the run continues.
Withdraw
Section titled “Withdraw”Withdraw closes an approval without deciding what it asked. The run keeps waiting, so cancel it:
kindgi approvals complete <approval-id> --decision=withdrawkindgi runs cancel <run-id>Cancelling a run doesn't close its approval either: withdraw the approval of a run you cancelled, so it leaves the pending list.
When nobody decides
Section titled “When nobody decides”Each approval has a deadline, its expiresAt: 24 hours after it's asked for,
or the agent's own hitl.timeoutMs (one hour:
conversationPolicy: { hitl: { tools: {}, timeoutMs: 3_600_000 } }). When the
deadline passes with no decision:
-
Below
admin, the approval becomesescalated, and a new one asks the next role up (standard, thensenior, thenadmin), with a deadline as long again. The run keeps waiting. -
At
admin, the approval becomesexpired, and the run's turn fails:"error":{"code":"hitl-cancelled","message":"Tool-call HITL cancelled for acme-desk.echo: timeout","reason":"timeout"}
A decision made before the deadline always wins. In 0.1 a deadline always
escalates this way: hitl.onTimeout (auto-approve, auto-reject) isn't
applied yet.
From your app
Section titled “From your app”A reviewer usually decides in your app, not in a terminal. The client lists the pending approvals and records the decision; the reviewer is the user the API token belongs to.
import { createClient } from '@kindgi/sdk/client';
const kindgi = createClient(); // KINDGI_API_URL, KINDGI_API_TOKEN, or the running kindgi dev
const pending = await kindgi.approvals.list({ status: 'pending' });for (const approval of pending.items) { console.log(approval.id, approval.title, approval.subjectRef);}
const first = pending.items[0];if (first) { const result = await kindgi.approvals.decide(first.id, { decision: 'approve', rationale: 'Checked the dashboard', }); console.log(result.approval.status, result.waitpointResolved);}46f0f9ad-f22d-4a93-8337-25b7c32a7473 HITL review: acme-ops.post-update { callId: 'dev-echo-call-1', toolId: 'acme-ops.post-update', agentId: 'acme-ops.status-agent', … arguments: { message: 'Checkout is slow for some customers.' }, …}approved truefrom kindgi.client import Kindgi
kindgi = Kindgi() # KINDGI_API_URL, KINDGI_API_TOKEN, or the running kindgi dev
pending = kindgi.approvals.list(status="pending")for approval in pending.data: print(approval.id, approval.title, approval.subject_ref)
if pending.data: result = kindgi.approvals.complete( pending.data[0].id, decision="approve", rationale="Checked the dashboard" ) print(result.approval.status, result.waitpoint_resolved)0afd1543-fe4e-45ca-af34-2effc1095bab HITL review: acme-ops.post-update {'callId': 'dev-echo-call-1', 'toolId': 'acme-ops.post-update', 'agentId': 'acme-ops.status-agent', …, 'arguments': {'message': 'Search is back.'}, …}approved True