Skip to content

Decide an approval

View as Markdown

This guide gets a waiting approval decided, so the run that waits on it goes on.

A run that waits for a person creates an approval, such as a gated tool call or a long conversation. A reviewer decides it, and the run moves on. Approvals reference has every field, decision, code and console page.

  • A run that waits for an approval. Ask before a tool runs makes one.
  • kindgi dev running in your pack's directory, with Kindgi 0.1's CLI.
  • For the app's half: Node 22.12 or later, Python 3.11 or later, or Java 17 or later, with Kindgi 0.1's client, and KINDGI_API_URL and KINDGI_API_TOKEN set.

Whoever decides an approval must be a reviewer. A tenant admin reads approvals without being one (Approvals reference), but registers to decide.

Under kindgi dev, you're one already. When it starts on a database where you aren't a reviewer yet, it registers you, the user of its token, as an admin. List the reviewers:

Terminal window
pnpm exec kindgi reviewers list
{
"data": [
{
"id": "6c11cfb1-d0c2-46dd-a3a0-e859a57b5da0",
"tenantId": "9126df1e-094b-496d-9ea8-7daab93c3f4f",
"userId": "26fc414a-b49f-4e1f-a022-162865770178",
"role": "admin",
"createdAt": "2026-10-05T20:10:59.747Z"
}
],
"hasMore": false
}

On any other runtime, register each person who reviews, with a role. Run it as that person: the reviewer is the user the API token belongs to. That covers any token of theirs, an API key or a console sign-in:

Terminal window
pnpm exec kindgi reviewers register --spec='{"role":"standard","displayName":"Ada Lovelace"}'

--spec is the reviewer, as JSON:

  • role is standard, senior or admin.
  • displayName is the name the console shows.

It prints the new reviewer:

{
"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. A reviewer sees and decides the approvals that need their role or a lower one. Registering again changes the role. A tenant admin can also add reviewers in the console, on the Reviewers page.

Email lets reviewers hear about an approval without watching the console. It's optional, and off by default.

Set KINDGI_REVIEWER_EMAIL=on in the runtime's environment. It sends through the emailed sign-in link's server, so KINDGI_AUTH_EMAIL_SMTP_URL and KINDGI_AUTH_EMAIL_FROM must be set too. Turned on without them, the runtime refuses to start.

The runtime then emails reviewers when an approval starts waiting, and when a waiting approval is assigned:

  • Who: the people the approvals list would show it to: its assigned reviewer, or else every active reviewer of its required role or above. Only those who may read its project are emailed.
  • Assigned later: a reviewer an approval is assigned to after it started waiting is emailed then, unless they were already emailed about it.
  • What: its title, project, required role and when it was asked, and a link to it in the console. Never what it's about: that stays behind sign-in.
  • How often: a reviewer's first email goes at once. Anything more for them within five minutes comes as one digest at the end of the five minutes. An approval decided in the meantime is left out.

One approval, as a reviewer gets it, with the ids elided:

Subject: Approval needed: Refund over the limit
An approval is waiting for you.
Refund over the limit
Project: Default
Needs: a standard reviewer or above
Asked: 2026-10-10 01:37 UTC
…/console/approvals/…
Sign in to decide: what each approval is about is only in the console.
You're getting this because you're a reviewer in Kindgi.
Excerpt

Several in one digest have the subject "2 approvals are waiting for you", with the same lines for each. For a system of your own, an approval.requested webhook carries the same facts.

The approval's id is what you decide it by.

List the approvals waiting for a reviewer, then read one:

Terminal window
pnpm exec kindgi approvals list --status=pending
pnpm exec kindgi approvals get <approval-id>
  • --status=pending lists the approvals nobody is assigned to yet, for any reviewer of their role. Without --status, the list includes decided approvals too.
  • <approval-id> is an approval's id, from the list.

To see the approvals assigned to you, oldest first:

Terminal window
pnpm exec kindgi approvals list --status=assigned,in_review --assigned-to=me --order=asc
  • --status takes several statuses, comma-separated.
  • --assigned-to=me keeps the approvals assigned to you. Assigning one moves it to assigned.
  • --order=asc lists the oldest first.

Each approval says what waits, who may decide it and which run waits on it. An approval's fields lists them.

From a run, kindgi runs resume <run-id> says what it waits for, and how it goes on. <run-id> is the run's id:

Terminal window
pnpm exec kindgi runs resume 45d986f1-8a8d-4341-a786-5ddcf239bb93
Run 45d986f1-8a8d-4341-a786-5ddcf239bb93 waits for approval b3fcf729-0137-430e-92a3-2ff10f4a6bde ("HITL review required for Chat desk"), for a standard reviewer or above. It continues once the approval is decided: kindgi approvals complete b3fcf729-0137-430e-92a3-2ff10f4a6bde --decision=approve (or --decision=reject).

That sentence goes to stderr. Its standard output is the same answer as JSON, for a script:

{
"kind": "approval",
"runId": "45d986f1-8a8d-4341-a786-5ddcf239bb93",
"status": "suspended",
"approvals": [
{
"id": "b3fcf729-0137-430e-92a3-2ff10f4a6bde",
"title": "HITL review required for Chat desk",
"requiredRole": "standard",
"status": "pending"
}
],
"alsoWaitsOn": []
}

Its exit code says which:

Exit The run
0 isn't waiting: Run … is completed: it isn't waiting, so there's nothing to resume.
3 waits for an approval, named in the sentence
4 waits on the runtime: a queued start, a child run, a retry, a lease

Once the approval is decided, the run goes on by itself. From your app, runs.get answers the same as the run's waitingFor.

In the console, a reviewer's Inbox lists every approval they may decide, as The Inbox describes.

Deciding the approval lets the run go on, or ends what it asked for.

Terminal window
pnpm exec kindgi approvals complete <approval-id> --decision=approve --rationale="Confirmed with the on-call engineer"
  • <approval-id> is the approval's id.
  • --decision is one of the four decisions in the table.
  • --rationale (optional) is kept with the decision, and goes into the audit trail.
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 Ends: its turn fails with hitl-withdrawn.

An approve, reject or withdraw resolves the run within the same call. So the run has moved on by the time the command returns. A decision can be refused, such as on an approval that's no longer waiting: Decisions lists why.

In the console, a reviewer decides on the approval's page or in the Inbox, as Deciding in the console shows.

Escalating asks a reviewer of the next role up instead. A standard reviewer who wants a second opinion escalates:

Terminal window
pnpm exec kindgi approvals complete 2134897a-d176-4ca8-b000-8bc00d79ad04 --decision=escalate --rationale="Needs sign-off from the incident lead"

It prints the escalated approval and the new one:

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

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.

Withdrawing closes an approval without deciding what it asked, when the request is moot. The run waiting on it can't go on, so it ends:

Terminal window
pnpm exec kindgi approvals complete <approval-id> --decision=withdraw --rationale="Asked by mistake"

Its turn fails with hitl-withdrawn: "The approval for tool call acme-desk.echo was withdrawn".

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.

Ask your coding agent: "In my app, let a reviewer see the pending approvals and approve one." For reference, the code looks like this, with what it prints:

review.ts
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.data) {
console.log(approval.id, approval.title, approval.subjectRef);
}
const first = pending.data[0];
if (first) {
const result = await kindgi.approvals.decide(first.id, {
decision: 'approve',
rationale: 'Checked the dashboard',
});
console.log(result.approval.status, result.waitpointResolved);
}

It prints each pending approval, then the decision's outcome:

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 true
Excerpt

The first pending approval is approved, and its run went on: waitpointResolved is true. Listing approvals has the list's other options, such as one project's approvals or one run's.