# Decisions

> Every action request gets one of three answers, with reasons a person can read and the signals that produced them. This page is the whole of how an answer is reached.

Source: https://immiscible.fly.dev/docs/concepts/decisions

An agent describes what it is about to do. Immiscible answers with one of three words:

| Decision | Meaning | What the agent should do |
|---|---|---|
| `allow` | The action is within a mandate, the agent's tier and every floor below them | Proceed. Pass the `receipt` to whoever needs proof |
| `approval_required` | A person must decide this one | Tell its user it is waiting, then poll |
| `deny` | Refused, with reasons | Stop, and tell its user why |

A refusal is a decision, not an error: it comes back as HTTP `200` with `"decision": "deny"`. HTTP errors are for requests Immiscible could not evaluate at all (see [errors](https://immiscible.fly.dev/docs/api/errors.md)). When Immiscible cannot reach a decision, the answer is `deny` with a reason, never `allow`.

## The action request

```json
{
  "type": "payment",
  "summary": "Pay the March invoice from Northwind Supplies",
  "payment": { "amount": 182000, "currency": "GBP", "merchant": { "name": "Northwind", "domain": "northwind.example" } },
  "provenance": [{ "source": "email", "detail": "invoice attached to an inbound email" }],
  "volume": { "records": 1 },
  "session": { "client": "claude-code", "id": "8f0c2d1e" },
  "idempotencyKey": "inv-2026-03-northwind"
}
```

| Field | Required | Meaning |
|---|---|---|
| `type` | yes | `payment`, `data.release`, `email.send`, `calendar.write`, `account.change`, `tool.call` or your own dotted type |
| `summary` | yes | One sentence a person can read: what and why. Shown to whoever approves |
| `payment` | for payments | `amount` in whole minor units, `currency` (ISO 4217), `merchant` with a `domain` |
| `data` | for data releases | `fields` from the vault, the `recipient` domain, a `purpose` |
| `target` | for other actions | the `domain` or `recipient` the action reaches |
| `provenance` | no, but see below | what influenced the request: `user`, `agent`, `web`, `email`, `document` or `tool` |
| `volume.records` | for data that moves | how many records; an export that will not say is asked about |
| `session` | no | the inference session, so declared provenance can be compared with what the gateway saw |
| `idempotencyKey` | recommended | retries with the same key get the same decision; a different body is `409` |

> **Warning**
> A request with no provenance is treated as if a stranger wrote it. Declare it honestly: the integrations Immiscible ships fill it in from the client, not from the model.

## How the answer is reached

Every request is scored against the signals below. Signals combine but never override one another: **any deny wins, then any approval, then allow.** After the engine decides, the agent's [autonomy tier](https://immiscible.fly.dev/docs/concepts/autonomy-tiers.md) may turn an `allow` into a question, never the other way round.

1. **The kill switch.** A frozen agent is denied everything (`agent_frozen`).
2. **Authority.** Some active [mandate](https://immiscible.fly.dev/docs/concepts/mandates.md) must cover this action type for this agent (`no_mandate` otherwise), within its limits, currency and recipients.
3. **The floors.** Checks no mandate can switch off: the Rule of Two, lookalike domains, injection language, velocity, sensitive fields.
4. **Standing.** The agent's tier and the volume of data it moves.

## Signals

| Signal | Fires when | Effect |
|---|---|---|
| `agent_frozen` | the agent is frozen | deny |
| `no_mandate` | nothing authorises this action type for this agent | deny |
| `mandate_pending_confirmation` | a rule for this action type exists but waits for a second owner to confirm it; the reason names the rule | deny |
| `over_transaction`, `over_period` | the amount breaches the mandate | deny |
| `currency_mismatch` | the payment currency differs from the mandate's | deny |
| `blocked_merchant` | the merchant is on the mandate's block list | deny |
| `recipient_not_allowed` | data or a message is going to a domain the mandate does not cover | deny |
| `lookalike_domain` | the domain is within two edits of a known one (`arnazon.com`) or uses a confusable character | deny |
| `approve_above` | the amount is above the mandate's approval line | approval |
| `new_merchant` | a merchant not on the allow list and not seen before | as the mandate says: ask, allow or deny |
| `rule_of_two` | untrusted provenance, plus a payment or data release, plus an external effect | approval, whatever the mandate says |
| `provenance_mismatch` | the agent declared only trusted sources, but the gateway saw untrusted content enter the session | approval; deny for a payment to a merchant the agent has never paid and the mandate does not list |
| `injection_language` | the summary or provenance carries instruction-override or pressure language | approval |
| `velocity` | too many requests in a short window: 10 actions in 10 minutes, or 200 tool calls, counted apart (both set in the workspace settings) | approval |
| `sensitive_field` | a release includes passport, national id, bank account, card or health data | approval, unless the mandate names that field and recipient |
| `tier_intern`, `tier_amount`, `tier_release`, `tier_action` | the agent's tier does not cover this alone | approval |
| `volume_over_tier`, `volume_undeclared` | more records than the tier moves alone, or an export that will not say | approval |
| `volume_hard_limit` | more records than the tier may ever move in one action | deny |

## The decision

```json
{
  "id": "act_Vd1x0a9e",
  "decision": "approval_required",
  "reasons": ["this payment was prompted by content from an email, and it would spend money"],
  "risk": {
    "score": 72,
    "signals": [{ "id": "rule_of_two", "severity": "high", "detail": "untrusted provenance (email) + payment + external effect" }]
  },
  "mandateId": "mdt_91c3e0b2",
  "approval": { "id": "apr_3k9d02aa", "url": "https://immiscible.fly.dev/app/approvals/apr_3k9d02aa", "expiresAt": "2026-10-04T10:30:00Z" },
  "expiresAt": "2026-10-04T10:30:00Z"
}
```

`reasons` is for people; `risk.signals` is for code. An `allow` carries a `receipt`. Every decision records the access profile version and hash it was made under, and lands in the [evidence ledger](https://immiscible.fly.dev/docs/concepts/evidence.md) with the trace it belongs to.

## Asking a person

When the answer is `approval_required`, nothing happens until someone decides. The person sees the agent, the mandate it acts under, what it wants to do, every signal explained in plain English, and what influenced the request. They can approve, deny, or freeze the agent.

- **Who decides.** The person the agent acts for, owners and admins, or only the workspace's named approvers when it has them. Above the workspace's line, neither the person the agent acts for nor whoever wrote its mandate may approve: separation of duties.
- **Where.** The console, an email link, or [Slack and Teams](https://immiscible.fly.dev/docs/guides/approvals-in-chat.md). A decision in chat is the console's decision, with the same checks.
- **Deadlines.** Each action type has a wait. Half way to it the request escalates to named contacts; at the deadline a background sweep refuses it, whether or not anyone is looking.
- **Fresh look.** At the moment of approval the request is checked against the mandate again. A frozen agent's request cannot be approved.

The agent polls `GET /v1/actions/:id` (or the MCP tool `check_action_status`): every five seconds for the first minute, every thirty after. Do not retry with a fresh idempotency key to get a different answer: each attempt is a new request, and a burst of them trips `velocity`.

> **Note**
> An approved action's receipt carries `"hum": true`, so a merchant or an auditor can tell that a person approved this specific action, not only the mandate.

## Next

- [Mandates](https://immiscible.fly.dev/docs/concepts/mandates.md): the authority a decision is checked against.
- [Autonomy tiers](https://immiscible.fly.dev/docs/concepts/autonomy-tiers.md): how an agent earns fewer questions.
- [`POST /v1/actions/authorize`](https://immiscible.fly.dev/docs/api/post-v1-actions-authorize.md): the endpoint, with examples.
