Skip to content

Concepts

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.

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

DecisionMeaningWhat the agent should do
allowThe action is within a mandate, the agent’s tier and every floor below themProceed. Pass the receipt to whoever needs proof
approval_requiredA person must decide this oneTell its user it is waiting, then poll
denyRefused, with reasonsStop, 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). 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"
}
FieldRequiredMeaning
typeyespayment, data.release, email.send, calendar.write, account.change, tool.call or your own dotted type
summaryyesOne sentence a person can read: what and why. Shown to whoever approves
paymentfor paymentsamount in whole minor units, currency (ISO 4217), merchant with a domain
datafor data releasesfields from the vault, the recipient domain, a purpose
targetfor other actionsthe domain or recipient the action reaches
provenanceno, but see belowwhat influenced the request: user, agent, web, email, document or tool
volume.recordsfor data that moveshow many records; an export that will not say is asked about
sessionnothe inference session, so declared provenance can be compared with what the gateway saw
idempotencyKeyrecommendedretries with the same key get the same decision; a different body is 409

#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 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 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

SignalFires whenEffect
agent_frozenthe agent is frozendeny
no_mandatenothing authorises this action type for this agentdeny
mandate_pending_confirmationa rule for this action type exists but waits for a second owner to confirm it; the reason names the ruledeny
over_transaction, over_periodthe amount breaches the mandatedeny
currency_mismatchthe payment currency differs from the mandate’sdeny
blocked_merchantthe merchant is on the mandate’s block listdeny
recipient_not_alloweddata or a message is going to a domain the mandate does not coverdeny
lookalike_domainthe domain is within two edits of a known one (arnazon.com) or uses a confusable characterdeny
approve_abovethe amount is above the mandate’s approval lineapproval
new_merchanta merchant not on the allow list and not seen beforeas the mandate says: ask, allow or deny
rule_of_twountrusted provenance, plus a payment or data release, plus an external effectapproval, whatever the mandate says
provenance_mismatchthe agent declared only trusted sources, but the gateway saw untrusted content enter the sessionapproval; deny for a payment to a merchant the agent has never paid and the mandate does not list
injection_languagethe summary or provenance carries instruction-override or pressure languageapproval
velocitytoo 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_fielda release includes passport, national id, bank account, card or health dataapproval, unless the mandate names that field and recipient
tier_intern, tier_amount, tier_release, tier_actionthe agent’s tier does not cover this aloneapproval
volume_over_tier, volume_undeclaredmore records than the tier moves alone, or an export that will not sayapproval
volume_hard_limitmore records than the tier may ever move in one actiondeny

#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 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. 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.

#Next