# Approvals and questions

The **Inbox** is where work stops and waits for a person. Two things land there: calls that need approval before they run, and questions a job asked because it could not decide on its own.

## Approvals

An approval is a call that matched a rule saying a person releases it. The call is parked, not failed. Nothing was sent, and no credential was unlocked, because the rules are checked before any of that happens.

### Decide what needs one

The gate lives in the policy document, and writing it takes an approver key:

```ts
await approver.policy.setPolicy({
  policy: {
    approvalGate: {
      includeDestructiveClass: true,
      toolSlugs: ["stripe.create_refund", "gmail.send_message"],
      pendingTtlSeconds: 3600n,
    },
  },
});
```

A call matches when its tool is named, or when it is destructive and you asked for the whole destructive class. The class setting is the one worth starting from: it catches every deletion in the catalog, including in apps you connect later, with no list to maintain.

`pendingTtlSeconds` is how long a parked call waits. Short enough that a decision made this morning cannot fire this evening, long enough that your reviewer is not racing a clock. An hour suits somebody reviewing as they go. If approvals are handled once a day, raise it deliberately, and understand that what was approved in the morning is what runs in the afternoon.

### What the assistant sees

A successful answer, carrying a status that says it is waiting and an approval id to come back with. The right behaviour is to say so and stop:

```ts
if (call.status === ToolCallStatus.PENDING_APPROVAL) {
  return `That needs a person to approve it. I have submitted it (${call.approvalId}) and will pick it up once it is approved.`;
}
```

Retrying without the id does not create a second pending item. A retry whose person, tool, and arguments match an existing one gets the same id back, so retrying is safe and accomplishes nothing.

### Releasing it

Open the Inbox, read the arguments, and decide. Show the arguments to whoever is deciding: the arguments are what is being approved, not the tool name.

Two rules hold before anything moves, and both refuse with a message naming the rule.

1. The person releasing it holds an approver key.
2. The key that requested the call may not be the key that approves it.

Who the approver is comes from the key that was presented, never from anything in the request, so no caller can name its own reviewer. That is what makes this a separation of duty rather than a convention.

### Then the call runs

The assistant retries with the approval id. An approval is single use and covers exactly that tool with exactly those arguments; it is checked against a fingerprint of them, so a retry that changed one field is refused and needs its own approval.

An expired approval is not a released one. The clock bounds an approved-but-unused call too.

### Approval is not confirmation

`confirm` on a call is the assistant stating that it told the person what would change. The assistant sets it itself, and it guards against a silent deletion.

An approval is a different person with a different key agreeing. Use confirmation for "the user knows". Use the approval gate for "somebody else agreed".

## Questions

A job that cannot decide something on its own can ask. The question arrives in the Inbox addressed to a person, with the work it is blocking named beside it. Answer it and the job continues from where it stopped.

Questions are addressed through the project's directory of people, so a question can be routed to the person who owns the account it concerns rather than to whoever happens to be looking. A question nobody answers expires, and the job parks rather than guessing.

From your own code, the asks are readable and answerable through the [JobsService reference](../reference/api/jobs.md).
