# Immiscible > Immiscible checks what your AI agents ask to spend, share and do before they do it, asks a person when your rules say so, and keeps a signed record of every decision. --- This file is the core of the Immiscible documentation at https://immiscible.fly.dev/docs, as Markdown: getting started, the concepts, the answers and the decision API. Every other section is a file of its own, listed at the end and in https://immiscible.fly.dev/llms.txt; each page is also at its own address plus .md. --- # Immiscible documentation > See what your company spends on AI, what it could save, and which keys nobody is watching. Then put every agent's model requests, payments and tool calls behind rules a person wrote, with a record anyone can check. Source: https://immiscible.fly.dev/docs Most companies cannot say what they spend on AI, which team spends it, or how much of it a cheaper model would have done just as well. Immiscible starts there. It reads your bills from OpenAI and Anthropic, or the traffic through its gateway, and tells you what went where, what could have cost less, and which keys nobody is watching. The savings are estimates, and every one shows how it was worked out. The same workspace then governs the agents themselves. Immiscible is not an agent; it sits between your agents and anything consequential they do, so a model request, a payment or a tool call is decided against rules a person wrote, and a person is asked when a person should decide. Where to start: - [The AI check](https://immiscible.fly.dev/check): drop last month's usage export and see your spend and possible savings in a couple of minutes. No account needed. - [For finance teams](https://immiscible.fly.dev/docs/finance.md): see your spend, set a budget, choose who approves. No code. - [Add the analyst to Claude](https://immiscible.fly.dev/docs/analyst.md): ask what you spent, what is wasted and which keys are unwatched, in your own words. - [Quickstart](https://immiscible.fly.dev/docs/quickstart.md): `npx immiscible init` in your agent's project, then your first decision, in five minutes. - [API reference](https://immiscible.fly.dev/docs/api.md): every route the server answers, generated from the router, with curl, Node and Python. How the pieces fit together, the four ways an agent reaches the gate and the conventions these docs use are on [How it fits together](https://immiscible.fly.dev/docs/concepts.md). --- # For finance teams > Getting started without writing any code. See what the company spends on AI and what it could save, give each team a budget, and choose who approves when an agent wants to spend above your line. Source: https://immiscible.fly.dev/docs/finance This page is for whoever looks after the money. Nothing on it needs an engineer, and nothing on it is code. If someone else will connect your agents, there is a button for inviting them along the way. ## What you are setting up Three things, in the order most finance teams want them. **See what you spend.** One usage file from OpenAI or Anthropic is enough to see last month's AI spend by model and by team, and which keys nobody is watching. If you only want this, the [AI check](https://immiscible.fly.dev/check) does it without an account. **See what you could save.** The same check prices what cheaper models doing the same kind of work, and caching, would have cost. Those figures are estimates and say so: we never see your prompts, so each is the most it could be, and the report shows how it was worked out. Once your engineer sends model traffic through the [gateway](https://immiscible.fly.dev/docs/guides/gateway.md), the estimate becomes a measured figure. **Decide what agents may spend.** Give each team a monthly [budget](https://immiscible.fly.dev/docs/guides/gateway.md#budgets). When an agent wants to pay for something, it asks first: inside your rules it goes ahead; above your line, or to a supplier nobody has paid before, a person decides. Everything is written down in a record nobody can quietly change. ## 1. Sign up Go to [https://immiscible.fly.dev/signup](https://immiscible.fly.dev/signup). You can sign up with Google, with Microsoft, or with an email address and a password. Every new workspace starts with thirty days of Business, with no card. After that it stays free, with 3 governed agents and up to 5 people, and nothing is deleted. ## 2. See the example The first screen shows a request Immiscible would hold for you: a team about to run a long batch on the most expensive model, close to its monthly AI budget, when a cheaper model can do the same work. Choose **Use the cheaper model** or **Let it run** to see what each does. It is an example: nothing is spent and nothing is recorded. You can see it again later from Overview. ## 3. Four short questions Setup is four steps, one decision each. Every step can be skipped, and anything you skip waits on Overview, ready when you are. | Step | What it asks | What to know | |---|---|---| | Run the AI check | A usage file, or the person who holds the admin key | Download last month's usage from OpenAI or Anthropic and drop it in; the step shows you where to find it. Or we send the key holder a one-page link that lasts 14 days. The key can read the bill and nothing else. You see what you spent, what you could save and which keys nobody is watching, and Overview links to the newest report. | | Where requests reach you | Slack, Microsoft Teams or email | With Slack you approve or deny in the message itself. Teams needs whoever runs it for you; we send them a short guide. Email always works. | | Who else approves | Their email addresses | Approvers answer requests and change nothing else. They are free on every plan. | | Turn on protection | Two rules, already written | Payments above an amount you choose need a person, and so does any supplier you have not paid before; each team also gets a monthly AI budget. The step lists what those rules would have held, including what your uploaded usage says about the budget, then you choose **Turn on protection** or **Keep watching only**. | **Keep watching only** means nothing is blocked yet: Immiscible records what it would have done, so you can see it working before it holds anything up. ## 4. Approve your first real request When an agent asks for something above your line, you hear about it where you chose in setup. Each request says who is asking, how much, to whom, and why it came to you. **Approve** lets that one payment through; **Deny** stops it, and the agent is told why. Above a set amount, approving asks you to confirm it is really you, with a passkey or a code from your authenticator app. The console works on a phone too: open it in your phone's browser and add it to your home screen to get notifications. ## Where things are | | | |---|---| | **Overview** | What needs you first, then how the month is going | | **Approvals** | Everything waiting for a person, and what was decided | | **Spend** | What the company spends on AI this month: by provider, by team, against budgets, a forecast, alerts, and a check against the bills | | **Agents** | Every agent acting for someone here, what it may do, and the button that stops it | | **Rules** | Your company rules: what any agent may do on its own | | **Records** | Everything an agent asked to do, what was decided, by whom, and the signed proof | | **Settings** | People, billing, sign-in, and **Connections** for Slack, Teams, accounting and reporting tools | ## Things finance teams usually ask **Are the savings real?** They are estimates until traffic goes through the gateway, and the report says so beside each figure. It prices the tokens you were billed for at a cheaper model's rates; it cannot see your prompts, so it cannot know whether the cheaper model would have done every task as well. In shadow mode the gateway records what it would have routed and at what price, which turns the estimate into a measured figure without changing anything. **Does Immiscible hold our money?** No. It never holds funds, card numbers or wallet keys. It says yes or no; your card, bank or wallet moves the money. **What does it cost?** We charge for the agents we govern, not for people: approvers, finance and auditors are free. Free covers 3 governed agents. Team is £39 a month, billed yearly, with 10 agents; Business is £499 a month, billed yearly, with 100 agents, SCIM and ten years of records. It never takes a share of what your agents or models spend. See [pricing](https://immiscible.fly.dev/pricing). **Can I stop an agent straight away?** Yes. Open **Agents**, choose the agent, then **Stop**. From that moment it can do nothing until someone you trust starts it again. See [the kill switch](https://immiscible.fly.dev/docs/guides/kill-switch.md). **What do I give our auditors?** **Settings**, **Export for your auditor**: a signed bundle of the records they ask for, which they can check without an account and without trusting us. See [evidence](https://immiscible.fly.dev/docs/concepts/evidence.md). **Can it reach our accounting tools?** Under **Settings**, **Connections**: monthly journals to [Xero](https://immiscible.fly.dev/docs/guides/xero.md) and [QuickBooks](https://immiscible.fly.dev/docs/guides/quickbooks.md), every agent card charge matched to its approval in [Ramp](https://immiscible.fly.dev/docs/guides/ramp.md), and spend sent to [Google Sheets or a board link](https://immiscible.fly.dev/docs/guides/finance-dashboards.md). **Who connects the agents?** Your engineer. In setup or on Overview, choose **Invite them**; they get a page of their own with everything they need, and the [quickstart](https://immiscible.fly.dev/docs/quickstart.md) takes them about five minutes. --- # The console > A short tour of the console for whoever answers requests and watches the money. Where each thing lives, how to review a held request with its evidence beside it, and the keys that make a busy morning quick. Source: https://immiscible.fly.dev/docs/console The console is where a person sees every agent the company runs, what each one spends, and what is waiting for a decision. It lives at [https://immiscible.fly.dev/app](https://immiscible.fly.dev/app). Everything in it can also be done from [Slack or Teams](https://immiscible.fly.dev/docs/guides/approvals-in-chat.md), by email, from the [CLI](https://immiscible.fly.dev/docs/cli.md) or through the [API](https://immiscible.fly.dev/docs/api.md); the console is where it all shows at once. To look around before you connect anything, choose **Open the sample workspace** in the workspace menu. Its company, agents and figures are made up, and nothing in it moves real money. ## Where things live | Section | What it holds | |---|---| | Overview | What needs you now, the setup steps you skipped, and the month so far | | Approvals | Every request waiting for a person, with its evidence beside it | | Spend | This month, Budgets, By team, Forecast and Bills | | Agents | Each agent, its rules, its key and its kill switch | | Rules | The company rules, then models, enforcement and reviews under Advanced | | Records | Every signed receipt and decision, the activity log and the evidence ledger | Settings holds the workspace, its people and connections, and everything technical under **For engineers**. What you see depends on your role. An approver sees Approvals, Agents and Records, and nothing else: they answer requests, and cannot change a rule or a budget. ## Reviewing a held request Open **Approvals**. The waiting requests are listed on the left, the one you are deciding sits in the middle, and the evidence it rests on is on the right, so you never decide from a summary alone. The evidence pane has three tabs: - **Email** (or **What it read**): what the agent says influenced the request, and what the [gateway](https://immiscible.fly.dev/docs/guides/gateway.md) saw it read, marked by where each came from. - **Payee**: how many times this payee has been paid before, the total, and the last three payments, so a first payment or a changed bank account stands out. - **Request**: the request exactly as the agent sent it. Above the buttons, one line says what your rules make of it: **Deny recommended** when a rule refuses it, **Hold recommended** when there is a warning sign a person should check (instructions that arrived by email, pressure to pay quickly, a lookalike domain), or **Approve if you expected it** when nothing looks wrong and a person is asked only because of the amount or a new payee. The line comes from the rules and the signals behind the [decision](https://immiscible.fly.dev/docs/concepts/decisions.md), never from a model, and it lists up to three reasons. > **Tip** > A thumbs up or down beside that line tells whoever writes the rules whether they asked too much or too little. It is kept in the audit log and changes nothing on its own. Approving or denying is recorded with your name, signed, and added to the [evidence ledger](https://immiscible.fly.dev/docs/concepts/evidence.md). The agent that was waiting gets its answer at once. ## What an agent has left Each agent's page opens on its money. **AI spend** shows what it has spent this month against its own budget, what is left, and the day the month ends; it is the same figure **Spend > Budgets** shows for that agent. Under each of its payment rules, **Payments** shows what it has paid this period against the rule's limit, and the day the period starts again. At 90% of a budget a warning says so, with the days left to go. Whoever manages the agent, or the person it acts for, can then choose **Ask for a temporary increase**: an amount and a reason. The request waits for another owner to confirm, never the person who asked, and only one can wait at a time. Once confirmed it raises this month's budget only; next month starts from the budget as it was set. The request is kept in the audit log, and the increase itself in the [evidence ledger](https://immiscible.fly.dev/docs/concepts/evidence.md). ## The guided tour The sample workspace has a tour of eight stops on the real console: what AI costs this month, what needs you, the £18,400 courier invoice, the evidence beside it, what your rules say, what an agent has left, the signed records, and where it reaches you. Choose **Take the tour** beside the sample data label, or open [https://immiscible.fly.dev/app?tour=1](https://immiscible.fly.dev/app?tour=1). Back and Next move between stops, the counter says where you are ("3 of 8"), and Escape ends it. It remembers its stop, so a reload carries on where you were. An approver sees only the stops their role can open. ## Search and keys **⌘ K** (Ctrl K on Windows and Linux) opens one search for everything: pages, records, people and rules, by name or by amount. It only finds what your role may read. | Keys | What they do | |---|---| | `g` then `o`, `a`, `g`, `s`, `p` or `r` | Overview, Approvals, Agents, Spend, Rules or Records | | `j` and `k` | The next and previous row, or request | | `a` and `d` | Approve or deny the request in focus | | `n` | Notifications | | `?` | Every shortcut, in one sheet | The keys never fire while you are typing in a field. ## Next - [For finance teams](https://immiscible.fly.dev/docs/finance.md): setting the workspace up without code. - [Approvals in Slack and Teams](https://immiscible.fly.dev/docs/guides/approvals-in-chat.md): answering the same requests where you already work. - [The kill switch](https://immiscible.fly.dev/docs/guides/kill-switch.md): stopping one agent, or all of them, at once. --- # Add the analyst to Claude > The AI spend analyst lives in Claude, Claude Code, Cursor and ChatGPT through Immiscible's MCP server. Ask it what you spent on AI, what is being wasted and which keys nobody is watching. When it wants to change something, a person approves first. Source: https://immiscible.fly.dev/docs/analyst The analyst is five tools on Immiscible's MCP server at `https://immiscible.fly.dev/mcp`. Add the server to your assistant, and you can ask it, in your own words, what your company spent on AI last month, what cheaper models or caching would have cost, and which API keys nobody is watching. It answers from your own bills, through the same AI check as [https://immiscible.fly.dev/check](https://immiscible.fly.dev/check). It can also ask to set a monthly budget or to switch a key off. It never does either on its own: a person approves each one, in Slack, Teams or the console, and the analyst hands back a signed receipt once it is done. ## Add it You need an Immiscible workspace with some spend in it: a provider's read-only admin key or a usage export, connected in the console under Spend, then Bills, or traffic through the gateway. For the keys, connect the providers under Settings, For engineers, Keys. The analyst answers for the person it acts for, and spend is visible to owners, admins and analysts. Someone with another role will be told so. Claude Code: ```bash claude mcp add --transport http immiscible https://immiscible.fly.dev/mcp \ --header "Authorization: Bearer $IMMISCIBLE_AGENT_KEY" ``` Claude (web and desktop): ```text Settings, Connectors, Add custom connector Name: Immiscible URL: https://immiscible.fly.dev/mcp Then sign in, and choose the agent the connection acts as. ``` Claude Desktop (config file): ```json { "mcpServers": { "immiscible": { "command": "node", "args": ["/path/to/immiscible-sdks/packages/immiscible-desktop/server/index.mjs"], "env": { "IMMISCIBLE_URL": "https://immiscible.fly.dev", "IMMISCIBLE_AGENT_KEY": "ask_..." } } } } ``` Cursor: ```json { "mcpServers": { "immiscible": { "url": "https://immiscible.fly.dev/mcp", "headers": { "Authorization": "Bearer ${env:IMMISCIBLE_AGENT_KEY}" } } } } ``` ChatGPT: ```text Add https://immiscible.fly.dev/mcp as a custom connector, where your plan allows one, and sign in. The connection acts as the agent you choose. ``` - **Claude Code**: `npx immiscible init` makes the agent and writes `IMMISCIBLE_AGENT_KEY` to `.env`; load it with `export $(grep IMMISCIBLE_ .env | xargs)`, then run the command. `npx immiscible mcp --client claude-code` prints it for your own server. - **Claude on the web and Claude Desktop**: the custom connector signs in with OAuth, so no key is pasted. The config file is the other way: it runs the bridge from `packages/immiscible-desktop`, which has no dependencies, with the agent key in its environment. Claude Desktop reads `claude_desktop_config.json`, under Settings, Developer. - **Cursor**: the entry goes in `.cursor/mcp.json`, or `~/.cursor/mcp.json` for every project. `npx immiscible mcp --client cursor` prints it with a one-click install link. - **ChatGPT**: the same server, through a custom connector and OAuth. The tools that change something come back "waiting for approval" whatever the assistant does, and a person in your workspace decides. Every client, with the config file paths, is in [add the MCP server](https://immiscible.fly.dev/docs/answers/add-the-mcp-server.md). ## Then paste this ```text You can use Immiscible's spend analyst tools. Tell me what we spent on AI in the last 30 days and where it went (spend_summary), what could have been cheaper (find_waste), and which API keys nobody is watching (unwatched_keys). Call every saving an estimate and say "up to"; never add the routing and caching figures together. End with at most three things to do. Do not change anything unless I ask. If I ask you to set a budget or switch a key off, use set_budget or revoke_key, tell me it is waiting for a person to approve, and only finish it once check_action_status says allow. ``` ## What it can do | Tool | What it does | Changes anything | |---|---|---| | `spend_summary` | The last 30 days of AI spend, by provider, model, team or key, and a month-end forecast once there are 14 recent days | no | | `find_waste` | What cheaper models (routing) and caching could have saved, each an upper bound labelled as an estimate with its method; the five biggest cost drivers; whether a budget is missing | no | | `unwatched_keys` | Keys that still work but have not been used in 90 days, have no named owner, or sit on a personal email address, each with its redacted value, owner, last use and spend | no | | `set_budget` | Asks for a monthly budget for the workspace or a team. A person approves it first | yes, once approved | | `revoke_key` | Asks to switch off a key from `unwatched_keys`. A person approves it, and then an owner confirms it in the console before the key stops working at the provider | yes, once approved and confirmed | The three that read are marked read only, and the two that act are marked destructive, so your assistant knows which is which. ## How it asks before it acts A change goes through the same gate as every agent's request: 1. The assistant calls `set_budget` or `revoke_key`. Nothing changes. Immiscible records the request and asks a person, in Slack, Teams or the console, with the amount or the key written out. 2. The assistant tells you it is waiting, and checks with `check_action_status`. 3. Once a person approves, the assistant calls the same tool again with the `actionId`. Exactly what was approved is applied, once, and the answer carries the signed receipt, which anyone can check at `https://immiscible.fly.dev/v1/verify`. For the analyst to ask at all, its agent needs the **Spend analyst** rule: in the console, open Rules ([https://immiscible.fly.dev/app/mandates](https://immiscible.fly.dev/app/mandates)) and add it to that agent. If the agent acts for you and the workspace has another owner, they confirm the rule first, as with any rule that gives an agent something new. Without the rule, the request is refused and the assistant says which rule to add. With it, every budget and key change still goes to a person; the rule only lets the analyst ask. A budget can be approved by an owner or an admin. Switching a key off needs an owner to confirm it after the approval, and a different owner where there is more than one, as every revocation in [discovery](https://immiscible.fly.dev/docs/guides/discovery.md) does. ## What it cannot see - Prompts and answers. The savings are estimates of the most they could be, not what you would have saved. - Keys from providers you have not connected for discovery, and agents that use keys you never gave us. - Spend on tools billed by seat, such as Copilot. - Anything before the 30 days it reads. ## Directory listings As of 6 October 2026, the analyst is not listed in any directory: not in Claude's connector directory, the ChatGPT app directory, Cursor's directory or the MCP Registry. The `server.json` for the MCP Registry is ready in the repository but has not been published. Until a listing is live, add the server by its address, as above. We will say here when that changes, and we will not put a button on the site for a listing that is not live. --- # Quickstart in five minutes > One command creates an agent with a payment rule. Then watch Immiscible send one payment to a person, allow it once they approve, and refuse a lookalike supplier on its own. Source: https://immiscible.fly.dev/docs/quickstart To see it work first, with no account and nothing leaving your machine, run `npx immiscible try`: an action allowed, a payment held for you to approve, a lookalike supplier denied, and the signed receipt verified. Then: You need an Immiscible workspace (sign up at [https://immiscible.fly.dev/signup](https://immiscible.fly.dev/signup), or [run your own](https://immiscible.fly.dev/docs/guides/deploy-and-backups.md)), Node 22.13 or later, and `curl`. Not an engineer? [For finance teams](https://immiscible.fly.dev/docs/finance.md) is the same start without any code. ## 1. Create the agent In your agent's project: ```bash npx immiscible init --name "Invoice agent" --purpose pays_invoices ``` It signs you in through the browser, creates the agent and its rule, writes `IMMISCIBLE_URL` and `IMMISCIBLE_AGENT_KEY` to `.env` (and `.env` to `.gitignore`), prints the code for the SDK it finds, and ends with a live test call: ```text ✓ Created Invoice agent in Quayside Rule: Pays up to £10,000 at a time and £50,000 a month. Payments above £5,000 need a person. A supplier it has not paid before needs a person. ✓ Added IMMISCIBLE_URL and IMMISCIBLE_AGENT_KEY in .env ✓ Governed by Immiscible: Invoice agent (Quayside) ``` In a workspace with more than one owner, a new rule waits for a second owner to confirm it, and the end reads like this instead: ```text ! Waiting for another owner to confirm the rule for Invoice agent (Quayside). Until then everything it asks for is refused. The connection works: the test payment reached Immiscible and was refused, as it should be while the rule waits. ``` `init` then exits with code 10: the agent and its key are ready, and the rule takes effect once the other owner confirms it in the console. Run `npx immiscible init` again afterwards and the test call is made afresh under the confirmed rule. The rule's limits come from your workspace's templates, so yours may differ. Every option, and what to do in CI, is in [the CLI](https://immiscible.fly.dev/docs/cli.md). Then load the two variables into your shell: ```bash set -a; . ./.env; set +a ``` > **Note** > A new agent starts at your workspace's starting tier. That is **intern** unless an owner has chosen **junior** in the console, and an intern has a person sign off its payments until it has a record of good decisions. Expect an intern's first calls to ask; that is the system working. See [autonomy tiers](https://immiscible.fly.dev/docs/concepts/autonomy-tiers.md). ## 2. Ask before paying Before the agent pays, it says what it wants to do and what influenced it: curl: ```bash curl -X POST "$IMMISCIBLE_URL/v1/actions/authorize" \ -H "authorization: Bearer $IMMISCIBLE_AGENT_KEY" \ -H "content-type: application/json" \ -d '{ "type": "payment", "summary": "Pay Acme Supplies invoice 0931", "payment": { "amount": 125000, "currency": "GBP", "merchant": { "name": "Acme Supplies", "domain": "acme-supplies.example" } }, "provenance": [ { "source": "user", "detail": "invoice approved in the finance inbox" } ], "idempotencyKey": "inv-0931" }' ``` Node: ```ts // npm install @immiscible/sdk import { Immiscible } from '@immiscible/sdk'; // Reads IMMISCIBLE_URL and IMMISCIBLE_AGENT_KEY. const immiscible = new Immiscible(); const decision = await immiscible.pay({ amount: 125000, // minor units: £1,250.00 currency: 'GBP', merchant: { name: 'Acme Supplies', domain: 'acme-supplies.example', }, summary: 'Pay Acme Supplies invoice 0931', provenance: [ { source: 'user', detail: 'invoice approved in the finance inbox', }, ], idempotencyKey: 'inv-0931', }); ``` Python: ```python # pip install immiscible from immiscible import Immiscible # Reads IMMISCIBLE_URL and IMMISCIBLE_AGENT_KEY. immiscible = Immiscible() decision = immiscible.pay( 125000, # minor units: £1,250.00 "GBP", { "name": "Acme Supplies", "domain": "acme-supplies.example", }, summary="Pay Acme Supplies invoice 0931", provenance=[ { "source": "user", "detail": "invoice approved in the finance inbox", }, ], idempotency_key="inv-0931", ) ``` The agent has never paid this supplier, so a person decides: ```json { "id": "act_b4811df1", "decision": "approval_required", "status": "pending_approval", "reasons": [ "This agent has not paid acme-supplies.example before. A person must approve new merchants." ], "mandateId": "mdt_31b2a52d", "risk": { "score": 20, "signals": [ { "id": "new_merchant", "severity": "medium", "effect": "approval", "detail": "This agent has not paid acme-supplies.example before. A person must approve new merchants." } ] }, "approval": { "id": "apr_aee8d38b", "url": "https://immiscible.fly.dev/app/approvals/apr_aee8d38b", "expiresAt": "2026-10-06T13:13:32.770Z" }, "expiresAt": "2026-10-06T13:13:32.770Z" } ``` With the SDKs, `guard()` (or `pay()` with a function) asks, waits for the person, runs your code only on allow, and settles: see [the SDKs](https://immiscible.fly.dev/docs/sdks.md). ## 3. Approve it Open `approval.url`. You also get it by email, and in [Slack or Teams](https://immiscible.fly.dev/docs/guides/approvals-in-chat.md) if they are connected. Choose **Approve**. The agent polls until a person answers: ```bash curl "$IMMISCIBLE_URL/v1/actions/act_b4811df1" \ -H "authorization: Bearer $IMMISCIBLE_AGENT_KEY" ``` ```json { "id": "act_b4811df1", "decision": "allow", "status": "allowed", "reasons": [ "Approved by sam@quayside.example.", "This agent has not paid acme-supplies.example before. A person must approve new merchants." ], "receipt": "eyJhbGciOiJFZERTQSIs..." } ``` The receipt's `hum` claim is `true`: a person approved this specific payment. Anyone can [verify it](https://immiscible.fly.dev/docs/concepts/receipts.md) without an account. ## 4. Report what happened After paying, the agent settles the action, so the ledger holds the outcome and not only the permission: ```bash curl -X POST "$IMMISCIBLE_URL/v1/actions/act_b4811df1/settle" \ -H "authorization: Bearer $IMMISCIBLE_AGENT_KEY" \ -H "content-type: application/json" \ -d '{ "status": "completed", "amount": 125000 }' ``` Settling above the authorised amount is recorded as an incident and alerts the owner. ## 5. Watch it ask, and refuse Send the same request with `"amount": 620000` (£6,200) and the idempotency key `inv-0933`. It is above the £5,000 line, so a person decides: ```json { "decision": "approval_required", "reasons": [ "£6,200.00 is above the £5,000.00 that Pays invoices lets the agent spend without asking." ] } ``` Now set the domain to `acme-suppl1es.example` (a one for the i), the source to `email`, and the key to `inv-0934`. Nobody is bothered: ```json { "decision": "deny", "status": "denied", "reasons": ["acme-suppl1es.example looks like acme-supplies.example but is not it."] } ``` A refusal is a `200` with a decision, not an HTTP error: see [decisions](https://immiscible.fly.dev/docs/concepts/decisions.md). If the agent is an intern, a second payment to Acme also asks (`tier_intern`); it earns the right to pay alone on its record. ## 6. Find the kill switch **Agents**, the agent, then **Stop**. From that moment every request from that agent is refused: its action requests are denied, ones already waiting for approval are cancelled, and its model calls and tool calls through Immiscible get `403 agent_stopped` before anything is sent. Read [the kill switch](https://immiscible.fly.dev/docs/guides/kill-switch.md) before you need it. ## Next - [Govern a coding agent](https://immiscible.fly.dev/docs/guides/mcp-proxy.md): Claude Code and Cursor behind the MCP proxy and the hook. - [Decisions](https://immiscible.fly.dev/docs/concepts/decisions.md): every signal, and the order they combine in. - [The SDKs](https://immiscible.fly.dev/docs/sdks.md): `guard()` wraps authorise, wait, run and settle in one call. ## Without the CLI If you would rather not run `npx`, install an SDK directly (`npm install @immiscible/sdk` or `pip install immiscible`; both have no runtime dependencies) and do step 1 in the console instead. Open **Agents** and choose **Add an agent**: 1. **What does the agent do?** Choose **Pays invoices**, and give it a name a person will recognise on their phone. 2. **Who approves?** The person asked when the agent needs one. Choose **Add the agent**. 3. **Give this to whoever set up the agent.** Open the agent's **setup page** and choose **Collect the agent's key**. The key is shown once. An agent key can ask for permission and nothing else: it holds no card number, no personal data, and it cannot approve its own requests, widen a rule or lift a freeze. Set `IMMISCIBLE_URL` and `IMMISCIBLE_AGENT_KEY` yourself, then carry on from step 2. --- # For AI agents > Everything an agent, or the person wiring one up, needs on one page. Ask before acting, follow the decision, retry safely, keep the receipt, and report what happened. Copy-paste setups for Claude Code, the OpenAI Agents SDK, MCP clients and x402. Source: https://immiscible.fly.dev/docs/ai-agents This page is written to be read by a model as much as by a person. Every page of these docs is also plain Markdown at the same address with `.md` on the end (this one is [https://immiscible.fly.dev/docs/ai-agents.md](https://immiscible.fly.dev/docs/ai-agents.md)), the core (getting started, the concepts, the answers and the decision API) is one file at [https://immiscible.fly.dev/llms-full.txt](https://immiscible.fly.dev/llms-full.txt), every other section is a file of its own under `/llms-full/`, and the index, which lists them all, is [https://immiscible.fly.dev/llms.txt](https://immiscible.fly.dev/llms.txt). ## Set up Immiscible for this project If a person has asked you to "Set up Immiscible for this project", do this, in their project folder. They allow the sign-in once in their browser; you do the rest without a terminal prompt. ```bash npx immiscible login --json # line 1 has verification_uri_complete: show it to the person and wait for them npx immiscible init --yes --json # the agent, its rule, .env, the Claude Code hook (fails closed) and a live test call npx immiscible doctor --json # exit 0 when nothing failed ``` - `init` adds `IMMISCIBLE_URL` and `IMMISCIBLE_AGENT_KEY` to `.env` (never replacing a value without `--force`) and, in a Claude Code project, installs the PreToolUse hook in `.claude/settings.json`, so every Bash, Write, Edit, WebFetch and MCP call asks first. Pass `--no-hook` to skip it. - Not Claude Code? Connect the MCP server instead: `npx immiscible mcp --client --json` prints the entry for that client (see [install in your assistant](#install-in-your-assistant)). - Exit codes: `3` not signed in (run `login`), `4` a flag is needed (the error names it), `10` the rule waits for another owner. Never paste the agent key into chat, and never commit `.env`. - Then tell your person what changed: the agent's name, its rule, and the files you touched. To try it first with no account at all, `npx immiscible try` runs a local demo. ## The contract in six lines 1. Before a payment, a release of personal data, an email, an account change or a tool call that reaches another system, describe it to Immiscible: `POST /v1/actions/authorize`, or the MCP tools `request_payment`, `request_personal_data` and `authorize_action`. 2. `allow`: go ahead with exactly that, and keep the signed `receipt`. 3. `approval_required`: do not act; tell your person, and poll until a person decides. 4. `deny`: do not act, and do not try another route; tell your person the reasons. 5. Retry with the same idempotency key, never a new one. 6. Afterwards, settle the action with what actually happened. ## Integrate in under five minutes 1. **Get an agent key.** In the console, **Agents**, **Add an agent**, then **Collect the agent's key** on its setup page. It looks like `ask_...`, is shown once, and can only ask: it cannot approve, widen a rule or lift a freeze. 2. **Give it a rule.** **Agents**, **Agent limits**, **Add a rule**. With no rule (a mandate) for an action type, the answer is `deny` with `no_mandate`; there is no default allowance. See [mandates](https://immiscible.fly.dev/docs/concepts/mandates.md). 3. **Ask.** Set two variables and send one request: ```bash export IMMISCIBLE_URL=https://immiscible.fly.dev export IMMISCIBLE_AGENT_KEY=ask_... curl -X POST "$IMMISCIBLE_URL/v1/actions/authorize" \ -H "authorization: Bearer $IMMISCIBLE_AGENT_KEY" \ -H "content-type: application/json" \ -H "idempotency-key: invoice-2026-10-northwind" \ -d '{ "type": "payment", "summary": "Pay the October invoice from Northwind Supplies", "payment": { "amount": 42000, "currency": "GBP", "merchant": { "name": "Northwind", "domain": "northwind.example" } }, "provenance": [{ "source": "user", "detail": "monthly supplier run" }] }' ``` 4. **Follow the decision** (below), then **settle**: ```bash curl -X POST "$IMMISCIBLE_URL/v1/actions/act_7Qm2c1f0/settle" \ -H "authorization: Bearer $IMMISCIBLE_AGENT_KEY" -H "content-type: application/json" \ -d '{ "status": "completed", "amount": 42000 }' ``` Amounts are always whole minor units: `42000` is £420.00. The [quickstart](https://immiscible.fly.dev/docs/quickstart.md) walks the same steps with the console open beside you. ## Decision semantics | `decision` | HTTP | What the agent does | |---|---|---| | `allow` | `200` | Proceed with exactly what was asked. `receipt` is a signed, single-use token; `expiresAt` says until when | | `approval_required` | `200` | Do not act. Tell the person you act for; `approval.url` is the link a person decides at. Poll `GET /v1/actions/:id` (or `check_action_status`) every five seconds for a minute, then every thirty. It becomes `allow` or `deny` | | `deny` | `200` | Do not act and do not try another way. Tell the person the `reasons` | A refusal is an answer, not an error, so it is HTTP `200`. `reasons` are sentences for people; `risk.signals[].id` are stable codes for code (`over_transaction`, `approve_above`, `rule_of_two`, `lookalike_domain` and the rest are listed in [decisions](https://immiscible.fly.dev/docs/concepts/decisions.md#signals)). When Immiscible cannot reach a decision, the answer is `deny`, never `allow`. To tell your person why, ask for the explanation: `GET /v1/actions/:id/explain`, or the MCP tool `explain_decision`. It is read only, safe to call at any time, and answers in plain English: the rule the action was judged under, each reason and signal, and what to do next. ## Error handling HTTP errors mean Immiscible could not evaluate the request at all. Every one carries a stable `type`, a `message`, and, on every `4xx`, a one-sentence `fix` and a `docs` link: ```json { "error": { "type": "agent_key_required", "message": "this endpoint needs an agent key; issue one for the agent in the console under Agents", "fix": "Send an agent key (ask_...), not a workspace or gateway key: in the console open Agents, Add an agent, then Collect the agent's key on its setup page.", "docs": "https://immiscible.fly.dev/docs/api/authentication#agent-keys" } } ``` | You get | Do this | |---|---| | `400 invalid_request` | correct each field in `error.errors` (`field` is a dotted path into the body) and send again | | `401 invalid_api_key` | the key is wrong, revoked or missing; do not retry until a person gives you a new one | | `403 agent_stopped`, or a `deny` with `agent_frozen` | stop everything; a person has stopped this agent and only a person can restart it | | `409 idempotency_conflict` | you reused a key for a different request; use a new key for a new request | | `429 rate_limited` | wait `retry-after` seconds, then retry with the same idempotency key | | `5xx` or no answer | retry with the same idempotency key and back off; if it never answers, do not act. Never treat an error as `allow` | The full list is in [errors and headers](https://immiscible.fly.dev/docs/api/errors.md). Branch on `error.type`; the `message` is for people and may change. ## Idempotency Send an idempotency key with every action request, in the body as `idempotencyKey` or as the `Idempotency-Key` header, 1 to 128 printable characters of your choosing, without spaces. - The same key with the same body returns the same decision, and never asks a person twice. That is how to retry after a timeout. - The same key with a different body is `409 idempotency_conflict`. - A new key is a new request. Do not use a fresh key to get a different answer: a burst of attempts trips the `velocity` signal, which asks a person. The SDKs make the key for you; the OpenAI Agents SDK integration uses the model's tool call id, so a retried tool call is the same action. ## Receipts An `allow` carries `receipt`: a compact JWS signed with Ed25519, single use, naming the agent, the action, the mandate, the amount and whether a person approved it (`"hum": true`). - Hand it to whoever needs proof: a merchant, a card issuer, an auditor. - Anyone can check it with `POST /v1/verify` and `{ "receipt": "eyJ..." }`, no account needed, or offline against the public key set at [https://immiscible.fly.dev/.well-known/immiscible-keys.json](https://immiscible.fly.dev/.well-known/immiscible-keys.json). - A receipt proves one action was allowed. It does not prove the action happened; settling records that. More in [receipts](https://immiscible.fly.dev/docs/concepts/receipts.md). ## Copy-paste setups Claude Code: ```bash # 1. Immiscible's MCP server, so Claude can ask before paying or sharing data claude mcp add --transport http immiscible https://immiscible.fly.dev/mcp \ --header "Authorization: Bearer $IMMISCIBLE_AGENT_KEY" # 2. The PreToolUse hook, so every Bash, Write, Edit, WebFetch and MCP call is # checked before it runs, whether or not the model remembers to ask mkdir -p ~/.immiscible curl -fsSL https://immiscible.fly.dev/downloads/claude-code-hook.mjs -o ~/.immiscible/claude-code-hook.mjs # then add the hook to ~/.claude/settings.json (see "The Claude Code hook") ``` OpenAI Agents SDK: ```ts import { Agent, run, tool } from '@openai/agents'; import { Immiscible } from '@immiscible/sdk'; import { guardOpenAITools } from '@immiscible/sdk/openai-agents'; const immiscible = new Immiscible().run(); // IMMISCIBLE_URL and IMMISCIBLE_AGENT_KEY const agent = new Agent({ name: 'Buyer', tools: guardOpenAITools([buy], { client: immiscible, mapToAction: ({ args }) => Immiscible.paymentAction({ amount: args.pence, currency: 'GBP', merchant: args.domain, provenance: [{ source: 'user' }] }), }), }); await run(agent, 'Renew the team licence at vendor.example.'); ``` MCP clients: ```json { "mcpServers": { "immiscible": { "url": "https://immiscible.fly.dev/mcp", "headers": { "Authorization": "Bearer ${env:IMMISCIBLE_AGENT_KEY}" } } } } ``` x402: ```ts import { Immiscible, x402Fetch } from '@immiscible/sdk'; const pay = x402Fetch(new Immiscible(), { pay: ({ requirements, paymentRequired }) => myX402Client.createPaymentHeader(requirements, paymentRequired), // your signer, called only on allow provenance: [{ source: 'user', detail: 'the analyst asked for this report' }], }); const res = await pay('https://api.data-vendor.example/v1/quotes'); ``` - **Claude Code**: the hook and its settings are in [the Claude Code hook](https://immiscible.fly.dev/docs/guides/mcp-proxy.md#the-claude-code-hook); its model traffic can go through the gateway too (`ANTHROPIC_BASE_URL=https://immiscible.fly.dev/anthropic`). - **OpenAI Agents SDK**: Python, the guardrail alternative and every option are in [the OpenAI Agents SDK guide](https://immiscible.fly.dev/docs/sdks/integrations/openai-agents.md). Install with `npm install @immiscible/sdk` or `pip install immiscible`. - **MCP clients**: Claude, ChatGPT and other connector-capable clients can instead add `https://immiscible.fly.dev/mcp` as a custom connector and sign in with OAuth; see [Claude and ChatGPT as connectors](https://immiscible.fly.dev/docs/guides/mcp-proxy.md#claude-and-chatgpt-as-connectors). - **Any other MCP client** (Cursor, VS Code, Windsurf, Codex, Gemini CLI): `npx immiscible mcp --client ` prints its entry; see [add the MCP server](https://immiscible.fly.dev/docs/answers/add-the-mcp-server.md). An AI coding agent can install Immiscible itself with `npx immiscible login --json` then `npx immiscible init --yes --json`, a person allowing the sign-in once. - **x402**: Immiscible sits between the `402` and your signature and never signs; see [x402 payments](https://immiscible.fly.dev/docs/guides/x402.md). ## Install in your assistant One line for each, from the packages in [efr7-7/immiscible-sdks](https://github.com/efr7-7/immiscible-sdks). Each one connects to the MCP server at `https://immiscible.fly.dev/mcp` and reads the agent key from `IMMISCIBLE_AGENT_KEY` (which `npx immiscible init` writes to `.env`) or signs in with OAuth. | Where | Install | What you get | |---|---|---| | Claude Code (plugin) | `/plugin marketplace add efr7-7/immiscible-sdks`, then `/plugin install immiscible@immiscible` | the fail-closed hook on every tool call, the MCP server, the `ask-before-acting` skill and the `immiscible-analyst` subagent, which reads approvals, decisions and spend and never acts | | Claude Code (no plugin) | `npx immiscible init` | the hook in `.claude/settings.json`, the agent, its rule and `.env` | | Claude Desktop | open `immiscible.mcpb` (built from `packages/immiscible-desktop`) and paste the agent key | the eleven tools, through a local bridge with no dependencies | | Claude and ChatGPT on the web | add `https://immiscible.fly.dev/mcp` as a custom connector and sign in | the eleven tools, acting as the agent you pick | | ChatGPT and Codex (plugin) | `packages/immiscible-openai`: a plugin with the MCP server (OAuth) and the `ask-before-acting` skill | the same | | Cursor | `npx immiscible mcp --client cursor` prints `.cursor/mcp.json` and a one-click `cursor://` install link | the eleven tools | | VS Code | `code --add-mcp '{"name":"immiscible","type":"http","url":"https://immiscible.fly.dev/mcp"}'` | the eleven tools, with OAuth sign-in | | Gemini CLI | `git clone https://github.com/efr7-7/immiscible-sdks && gemini extensions install ./immiscible-sdks/packages/immiscible-gemini`, or `gemini mcp add --transport http -H "Authorization: Bearer $IMMISCIBLE_AGENT_KEY" immiscible https://immiscible.fly.dev/mcp` | the eleven tools and the same instructions as context | | Codex CLI | `codex mcp add immiscible --url https://immiscible.fly.dev/mcp --bearer-token-env-var IMMISCIBLE_AGENT_KEY` | the eleven tools | | Windsurf (Devin Desktop) | `npx immiscible mcp --client windsurf` prints the entry | the eleven tools | Every entry is in [add the MCP server](https://immiscible.fly.dev/docs/answers/add-the-mcp-server.md). The plugins, the Desktop bundle and the Gemini extension ship with the 0.1.1 release of the public repository; none is listed in a directory yet. ## Ask a person above an amount *"How do I stop my Claude Code agent paying more than £500 without approval?"* 1. **Write a payment rule with an approval line.** **Agents**, **Agent limits**, **Add a rule**, kind payment, with **Ask me above** £500. In the API that is a payment mandate with `approveAbove: 50000`, sent by a signed-in owner or admin to [`POST /api/w/:wid/mandates`](https://immiscible.fly.dev/docs/api/post-api-w-wid-mandates.md); `perTransaction` stays the ceiling nobody can talk past (above it is `deny`, not a question): ```json { "agentId": "agt_4f2c91a7", "kind": "payment", "title": "Claude Code purchases", "currency": "GBP", "perTransaction": 200000, "perPeriod": 500000, "period": "month", "approveAbove": 50000, "newMerchant": "approve" } ``` 2. **Make every payment pass the gate where the amount is visible.** The strongest first: - the [card rail](https://immiscible.fly.dev/docs/guides/card-rail.md): bind a Stripe Issuing card (or another issuer through signed webhooks) to the agent, and the issuer asks Immiscible before money moves; no receipt, no payment; - the [MCP proxy](https://immiscible.fly.dev/docs/guides/mcp-proxy.md) in front of a payment tool, with a tool map that says where the amount is (`"type": "payment", "amountPath": "amount"`); - the MCP server's `request_payment` tool or `POST /v1/actions/authorize`, which the agent calls itself, so pair it with one of the two above. 3. **Close the side doors with the Claude Code hook.** It sends Claude Code's own tools (Bash, Write, Edit, WebFetch) as `tool.call` actions; a `tool.call` rule that lists its domains refuses a shell command that posts to anywhere else, such as a payment API the rule does not name. The agent's tier still applies on top: a new agent is an intern and a person signs off every payment, which is stricter than £500. For it to pay up to £500 alone it must reach `senior` (pays alone up to £1,000), on evidence. See [autonomy tiers](https://immiscible.fly.dev/docs/concepts/autonomy-tiers.md). ## One budget across OpenAI and Anthropic *"What's the best way to govern agent spend across OpenAI and Anthropic?"* 1. **Connect the providers.** Under **Settings**, **Connections**, paste your OpenAI and Anthropic keys. Traffic is served on your own contracts; Immiscible never resells inference. 2. **Point every client at the gateway.** One base URL per protocol: OpenAI-shaped at `https://immiscible.fly.dev/v1`, Anthropic-shaped at `https://immiscible.fly.dev/anthropic`, with an Immiscible key instead of the provider's: ```bash export ANTHROPIC_BASE_URL=https://immiscible.fly.dev/anthropic # Claude Code, the Anthropic SDK export OPENAI_BASE_URL=https://immiscible.fly.dev/v1 # the OpenAI SDK and compatible tools ``` 3. **Set budgets.** By team, person or workspace, for a calendar month, checked against the projected cost before each call. Amounts are millionths of a US dollar, so `500000000` is $500. With a gateway key issued with the admin scope (see [authentication](https://immiscible.fly.dev/docs/api/authentication.md#workspace-keys)): ```bash curl -X POST "https://immiscible.fly.dev/v1/admin/budgets" \ -H "authorization: Bearer $IMMISCIBLE_ADMIN_KEY" -H "content-type: application/json" \ -d '{ "scope": "org", "baseAllocation": 500000000, "hardCeiling": 600000000, "ownerId": "finance@example.com" }' ``` Near the allocation the gateway nudges, then routes to cheaper eligible models; at the allocation it answers `402 approval_required` naming the owner; past the ceiling, `429 budget_exhausted`. 4. **Switch to enforce.** Every workspace starts in [shadow mode](https://immiscible.fly.dev/docs/guides/gateway.md#shadow-mode-first), where nothing is blocked, not even an exhausted budget: run a week, read **Assessment**, then switch to **Enforce** under **Rules**, **Models and enforcement**. 5. **Find the spend that bypasses it.** [Discovery](https://immiscible.fly.dev/docs/guides/discovery.md) reads OpenAI, Anthropic and OpenRouter admin APIs for keys and projects outside the gateway, and [finance dashboards](https://immiscible.fly.dev/docs/guides/finance-dashboards.md) put spend by team, provider and agent where finance already looks. Inference that runs in a vendor's own backend (Devin, GitHub Copilot, Cursor's hosted models) cannot pass through any gateway; it is reconciled from the vendor's API and marked `governed: false`. See [the gateway](https://immiscible.fly.dev/docs/guides/gateway.md#budgets). ## The MCP server `https://immiscible.fly.dev/mcp` speaks Streamable HTTP (JSON-RPC 2.0, protocol `2025-06-18`, also `2025-03-26` and `2024-11-05`). Authenticate with an agent key as `Authorization: Bearer ask_...`, or an OAuth access token from the connector sign-in. Its tools: | Tool | Call it | Read only | |---|---|---| | `request_payment` | before spending any money | no | | `request_personal_data` | before giving anyone the person's details | no | | `authorize_action` | before any other consequential action: email, calendar, account changes, tool calls | no | | `check_action_status` | to poll after `approval_required` | yes | | `explain_decision` | to say in plain English why something was allowed, held or refused | yes | | `settle_action` | once, after an allowed action, with what happened | no | | `spend_summary` | when the person asks what the company spent on AI, by provider, model, team or key | yes | | `find_waste` | when they ask what could be cheaper: routing and caching estimates, each an upper bound | yes | | `unwatched_keys` | when they ask which API keys nobody is watching | yes | | `set_budget` | to ask for a monthly budget; a person approves before it is set | no | | `revoke_key` | to ask to switch a key off; a person approves, then an owner confirms | no | The last five are the AI spend analyst; [add the analyst to Claude](https://immiscible.fly.dev/docs/analyst.md) says how they answer and act. A refused call comes back as a tool result with `isError: true` and text naming the error, the fix and the docs link, so the model can correct itself. The server's card is at [https://immiscible.fly.dev/mcp/server-card](https://immiscible.fly.dev/mcp/server-card). ## Machine-readable | What | Where | |---|---| | Docs index for models | [https://immiscible.fly.dev/llms.txt](https://immiscible.fly.dev/llms.txt) | | The core of the docs in one file | [https://immiscible.fly.dev/llms-full.txt](https://immiscible.fly.dev/llms-full.txt); every other section at `https://immiscible.fly.dev/llms-full/
.txt`, listed in llms.txt | | Any page as Markdown | the page address plus `.md`, for example [https://immiscible.fly.dev/docs/quickstart.md](https://immiscible.fly.dev/docs/quickstart.md) | | OpenAPI 3.1 for the decision API | [https://immiscible.fly.dev/openapi.json](https://immiscible.fly.dev/openapi.json) | | MCP server card | [https://immiscible.fly.dev/mcp/server-card](https://immiscible.fly.dev/mcp/server-card), listed in [https://immiscible.fly.dev/.well-known/ai-catalog.json](https://immiscible.fly.dev/.well-known/ai-catalog.json) | | Receipt signing keys | [https://immiscible.fly.dev/.well-known/immiscible-keys.json](https://immiscible.fly.dev/.well-known/immiscible-keys.json) | --- # The Immiscible CLI > One command governs the agent in your project. Sign in through the browser, create the agent and its rule, write .env, install the Claude Code hook and make a live test call. Works for people and for AI coding agents running non-interactively. Source: https://immiscible.fly.dev/docs/cli The fastest way to put Immiscible in front of an agent: ```bash npx immiscible init ``` It signs you in if you are not, finds what your project uses, creates the agent and its rule, adds `IMMISCIBLE_URL` and `IMMISCIBLE_AGENT_KEY` to `.env`, installs the Claude Code hook where there is Claude Code, prints the code for your SDK, and ends with a live test call: ```text ✓ Governed by Immiscible: Invoice agent (Acme) ``` The CLI is the `immiscible` package on npm. It needs Node 22.13 or later and has no dependencies. To have an `immiscible` command without `npx`, install it once with `npm install -g immiscible`. Or install it with one line, which checks Node, installs the package and shows the welcome card: ```bash curl -fsSL https://immiscible.fly.dev/install.sh | sh ``` ```powershell irm https://immiscible.fly.dev/install.ps1 | iex ``` `immiscible about` shows the card again: the version, the credits and the links. It is drawn only in a terminal; on a pipe or in CI it prints plain text, and `--json` gives the same facts as data. The card draws the Cardinal squares in dots, with the dot field thickening towards the right; under 98 columns it goes compact, and under 74 it is plain. Set `IMMISCIBLE_CARD=compact` or `IMMISCIBLE_CARD=classic` to choose, and `IMMISCIBLE_NO_MOTION=1` to turn off the animation. This is the developer CLI. The repository also has an operator CLI, `immiscible-server` (run in a checkout as `npm run admin -- `), which runs a server rather than talking to one: backups, integrity checks, the demo seed. They are different programs, and `immiscible-server init` points you back here. ## See it first, with no account ```bash npx immiscible try ``` From 0.2.0. In under a minute, offline: a made-up finance agent asks three times, and the fake Immiscible server the SDKs test against, started on 127.0.0.1 with its own signing key, decides. Looking up an invoice is allowed; paying £1,250 to a supplier it has not paid before is held, and you approve or deny it at the prompt; paying a lookalike of a known supplier is denied. Then `try` saves the receipt, runs `immiscible verify` on it, says what just happened, and ends on `immiscible init`. The fake imitates a rule; it is not the real policy engine. Nothing leaves the machine, and the fake stops when `try` ends. Without a terminal, pass `--yes` to approve the held payment. ## Check a receipt ```bash immiscible verify receipt.jwt --keys keys.json ``` From 0.2.0. Checks a signed receipt offline, with the same verifier as the SDK: the Ed25519 signature, the key id, the type and the expiry. It prints what was allowed: the action, the amount and where it went, and whether a person approved it. `--keys` takes a saved copy of the issuer's `/.well-known/immiscible-keys.json` (nothing is fetched) or its URL; without it, the keys come from your server and the receipt must be that server's. The receipt can be a file, the token itself, or `-` for stdin. It needs no account and no agent key, and exits 12 when the receipt is not valid. ## Sign in ```bash immiscible login ``` The CLI shows a one-time code and opens your browser at `/app/device`. Sign in if you are not, check the code matches your terminal, pick the workspace and choose **Allow**. The terminal says who you are signed in as. This is the OAuth 2.0 device authorization grant ([RFC 8628](https://www.rfc-editor.org/rfc/rfc8628)), with PKCE: the code in the browser is useless without the secret the CLI kept. The token is stored in `~/.config/immiscible/credentials.json` (or under `$XDG_CONFIG_HOME`), readable only by you (mode 0600), one entry per server. It acts for you in the workspace you picked and can only read agents and status and add agents with a rule from your templates: it never approves anything or changes an existing rule. It lasts 90 days. End it with `immiscible logout`, from your sessions in the console, or by signing out everywhere or changing your password; an owner or admin can end any CLI sign-in to the workspace. Your workspace's sign-in rules (address allowlist, single sign-on, two-factor, an administrator ending your sessions) apply to it on every use. | | | |---|---| | `immiscible login --url https://immiscible.your-company.com` | sign in to a server you run yourself (or set `IMMISCIBLE_URL`) | | `immiscible login --token imc_...` | store a token you already have, after checking the server accepts it | | `IMMISCIBLE_TOKEN=imc_... immiscible status` | use a token without storing it: what CI does | | `immiscible whoami` | who, which workspace, which server, and where the token came from | | `immiscible logout` | revoke this machine's token and forget it | | `immiscible token create --name ci` | a CI token, shown once (see [below](#non-interactive)) | The server is chosen in this order: `--url`, `IMMISCIBLE_URL`, `IMMISCIBLE_URL` in the project's `.env`, the server you last signed in to, then `https://immiscible.fly.dev`. ## Govern the agent in a project ```bash immiscible init ``` 1. **Detects the project**, from files only: `package.json` (`openai`, `@anthropic-ai/sdk`, `ai`, `langchain` and `@langchain/*`, `@openai/agents`), `pyproject.toml`, `requirements*.txt` or `Pipfile` (`openai`, `anthropic`, `langchain`, `openai-agents`), a `.claude/` directory or `CLAUDE.md`, MCP configs (`.mcp.json`, `.cursor/mcp.json`, `.vscode/mcp.json`), and x402 or wallet SDKs. 2. **Asks the minimum**: the agent's name, and what it does, from the same purposes as the console's **Add an agent** (each shown with the rule it gets in your workspace). New agents start at the workspace's starting tier, intern unless an owner has chosen junior, as everywhere. 3. **Creates the agent and its rule.** When the agent acts for you and your workspace has another owner, the rule goes to them: *Sent to another owner to confirm; payments start once they do.* Until then the agent is connected and everything it asks for is refused. 4. **Writes `.env`**: adds `IMMISCIBLE_URL` and `IMMISCIBLE_AGENT_KEY`. A value already there is never replaced without asking; a `.env` that points at another server stops init before anything is made (use `--url` to keep it, or `--force`). In a git repository it adds `.env` to `.gitignore` (creating the file if need be), because `.env` holds the agent key; it shows the line and asks first, and `--no-gitignore` leaves `.gitignore` alone. 5. **Installs the Claude Code hook** in a Claude Code project, after showing the change to `.claude/settings.json` and asking: the hook file goes in `.claude/hooks/immiscible-claude-code-hook.mjs`, and one `PreToolUse` entry is added with the full matcher and `|| exit 2`, so it fails closed. Your other settings and hooks are left as they are. 6. **Prints the code** for the SDK it found. 7. **Makes a live test call** through the gate as the agent and prints `✓ Governed by Immiscible: ()`. While the rule still waits for another owner it prints `! Waiting for another owner to confirm the rule` instead, and exits 10 with `"ok": false`. The test is marked as a test and tidies up after itself: nobody is notified, and a question for a person is cancelled at once, so nobody has to decide it. Running `init` again changes nothing that is already right: the key in `.env` is checked and reused, `.env` and `.claude/settings.json` are left byte for byte, and the test call reuses its idempotency key while the agent's rules are unchanged. Once a rule changes (another owner confirms it, say), the test is made afresh, so the answer printed is always the current one. If the key in `.env` is no longer accepted, init offers to replace it. The hook entry it writes: ```json { "matcher": "Bash|Write|Edit|MultiEdit|NotebookEdit|WebFetch|mcp__(?!immiscible__(check_action_status|explain_decision|spend_summary|find_waste|unwatched_keys)$).*", "hooks": [ { "type": "command", "command": "node --env-file-if-exists=\"$CLAUDE_PROJECT_DIR/.env\" \"$CLAUDE_PROJECT_DIR/.claude/hooks/immiscible-claude-code-hook.mjs\" || exit 2", "timeout": 60 } ] } ``` Immiscible's own read-only MCP tools (`check_action_status`, `explain_decision`, `spend_summary`, `find_waste` and `unwatched_keys` on the `immiscible` server) are left out, so the hook never asks Immiscible about asking Immiscible; its tools that act still go through. A new agent's default rule (General tasks) lets provably read-only calls (`ls`, `git status`, `git diff`, `git log`, reading a file) go ahead without a person; each is still decided and recorded. An intern asks before anything that writes, runs or deletes something. Once the agent is past its intern stage, edits, test runs and builds inside its project go ahead without a person (`localWrites: "allow-after-intern"`); the hook sends the project, `CLAUDE_PROJECT_DIR` or the working directory, and a call that names a path outside it asks (from 0.2.0: an older hook does not say which project, so those calls still ask). At every standing and under every rule, these still ask: a destructive command (`rm -rf`, `git push --force`, `git reset --hard`, `git rebase`, `curl ... | sh`), any `git push`, a deploy, a publish, a package install from the network, anything run with `sudo`, and a change to `.claude/settings.json` or git's hooks; a secrets file leaving the machine is refused. The rule says so on the agent's page; to have a person sign off reads too, replace it with one that sets `readOnly: "ask"` or leaves it out; to have a person approve every edit and test run, replace it with one that sets `localWrites: "ask"`. Beside the hook, init adds Claude Code deny rules to the same file, shown in the same diff: `Bash(rm -rf:*)`, `Bash(rm -fr:*)`, `Bash(sudo rm:*)`, `Bash(git push --force:*)`, `Bash(git push -f:*)`, `Bash(git reset --hard:*)`, `Bash(git clean -f:*)`, `Read(./.env)` and `Read(./.env.*)`. Claude Code refuses those itself, before any hook runs and without the network. Deny rules you already have are kept, in your order. The hook reads `IMMISCIBLE_URL` and `IMMISCIBLE_AGENT_KEY` from the project's `.env` (a variable already in the environment wins). More on what it sends and how it decides: [Claude Code and Cursor](https://immiscible.fly.dev/docs/guides/mcp-proxy.md). | Flag | | |---|---| | `--name ` | the agent's name | | `--purpose ` | `pays_invoices`, `books_travel`, `handles_refunds`, `buys_software`, `answers_customers` or `other` | | `-y`, `--yes` | accept defaults and confirm changes; required when not at a terminal | | `--hook`, `--no-hook` | install the Claude Code hook without `.claude/`, or never | | `--no-test` | skip the live test call | | `--no-gitignore` | leave `.gitignore` alone | | `--force` | replace `IMMISCIBLE_URL` in `.env` when it points elsewhere | | `--dir ` | the project (default: the current directory) | ## Govern every project on a machine ```bash immiscible install claude-code [--scope user|managed] [--transport command|http] [--key ] [--dry-run] ``` `init` governs one project; `install claude-code` puts the hook in front of every Claude Code session on the machine. `--scope user` (the default) merges it into `~/.claude/settings.json`; `--scope managed`, run as an administrator, writes Claude Code's managed settings file, which people cannot override, and sets `allowManagedHooksOnly` so only managed hooks run. The default command transport fails closed; `--transport http` has Claude Code post each event to your server instead, and lets a call go on if the server cannot be reached at all. It shows the diff and asks first (`--yes` when there is no terminal), `--dry-run` writes nothing, and a second run changes nothing. After copying the hook it runs `node --self-test`. Pair it with the **Coding agent baseline** rule. More in [the Claude Code fleet pack](https://immiscible.fly.dev/docs/guides/claude-code-fleet.md). | Flag | | |---|---| | `--scope ` | `user` or `managed` | | `--transport ` | `command` (fails closed) or `http` | | `--key ` | an agent key to write into the settings' `env`; left out, each person's environment supplies `IMMISCIBLE_AGENT_KEY` | | `--gateway ` | send Claude Code's model traffic through your Immiscible gateway (`ANTHROPIC_BASE_URL`) | | `--dry-run` | show the change, write nothing | | `-y`, `--yes` | write without asking; required when not at a terminal | ## Hooks for Codex, Cursor, Windsurf and Gemini CLI ```bash immiscible install codex # or cursor, windsurf, gemini; for you sudo immiscible install codex --scope managed # for everyone on this machine immiscible install gemini --dry-run # show the change, write nothing ``` Copies one hook, `coding-agent-hook.mjs` (Node, no dependencies), and adds one entry to the agent's own hook configuration, so the agent asks Immiscible before each shell command and MCP tool call, and before each file write where it has that event. The same rules decide as for the Claude Code hook. It fails closed: when Immiscible cannot answer, the call is refused, and the command ends in `|| exit 2` so a missing file or a missing `node` blocks it too. Every other setting and hook in the file stays as it is, an older Immiscible entry is replaced in place, and running it again changes nothing. After copying the hook it runs `node --self-test`, and only then changes the agent's configuration. | Agent | `--scope user` (the default) | `--scope managed` | |---|---|---| | Codex | `~/.codex/hooks.json` | `requirements.toml` (`/etc/codex` on macOS and Linux), with `allow_managed_hooks_only = true` | | Cursor | `~/.cursor/hooks.json` | the enterprise `hooks.json` (`/Library/Application Support/Cursor`, `/etc/cursor`, `C:\ProgramData\Cursor`) | | Windsurf | `~/.codeium/windsurf/hooks.json` | the system `hooks.json` (`/Library/Application Support/Windsurf`, `/etc/windsurf`, `C:\ProgramData\Windsurf`) | | Gemini CLI | `~/.gemini/settings.json` | the system `settings.json` (`/Library/Application Support/GeminiCli`, `/etc/gemini-cli`, `C:\ProgramData\gemini-cli`), with `hooksConfig.enabled` | The hook reads `IMMISCIBLE_URL` and `IMMISCIBLE_AGENT_KEY` from the environment, then from `hook.env` beside it (written with your server's address, and the key with `--key`). Cursor and Windsurf start hooks from the app rather than your shell, and Gemini CLI gives hooks a reduced environment, so `hook.env` is how they find the key. When a decision needs a person, Cursor asks you at the keyboard with the approval link; Codex and Gemini CLI wait up to four minutes for someone to approve in Slack, Teams, email or the console; Windsurf documents no hook timeout, so it refuses with the link and you run it again once approved. More in [hooks for other coding agents](https://immiscible.fly.dev/docs/guides/coding-agent-hooks.md). | Flag | | |---|---| | `--scope ` | for you, or for everyone on the machine (run as an administrator) | | `--key ` | an agent key, written to `hook.env` (left out, each person's environment supplies it) | | `--dry-run` | show the change and write nothing | | `-y`, `--yes` | write without asking; required when not at a terminal | With `--json`: `{ ok, target, scope, config, configState, hookFile, envFile, command, diff, changed, selfTest, warnings }`. `install` is in the next CLI release; until it is published, run it from a checkout as `node packages/immiscible-cli/bin/immiscible.mjs install codex`. ## Check the setup ```bash immiscible doctor ``` | Check | Fails when | |---|---| | Node.js | warns below 22.13 (the hook command needs `--env-file-if-exists`) | | Server | `/healthz` does not answer | | Clock | this machine is 60 seconds or more from the server (signed receipts allow 60); warns from 5 | | Signed in | the token is not accepted (not being signed in only warns) | | Environment | `IMMISCIBLE_URL` or `IMMISCIBLE_AGENT_KEY` is missing from `.env` and the environment; warns when the shell's value differs from `.env` (the hook uses the shell's, so doctor checks that one) | | Agent key | the key is not accepted, or the agent is stopped; warns when it has no rule yet | | Claude Code hook | in a Claude Code project: not installed, not the full matcher, the command does not end in `exit 2`, a timeout above 60, or it does not refuse when Immiscible cannot be reached (doctor runs it against an address nothing answers on); warns that the hook is out of date when the file differs from the one this CLI ships or (from 0.2.0) the one your server serves | | .gitignore | warns when `.env` holds the key and is not ignored | Each problem comes with a fix and a link here. The exit code is 0 when nothing failed and 7 when something did. ## What needs you ```bash immiscible status ``` What waits for approval (with a link to each), today's decisions since 00:00 UTC (allowed, asked a person, refused; `init`'s own connection tests are not counted), and this month's AI spend measured by the gateway and agent payments, in your workspace's books currency at the newest European Central Bank rate. ## Evidence for an auditor ```bash immiscible evidence ai-act --out pack.zip ``` Downloads the [EU AI Act deployer evidence pack](https://immiscible.fly.dev/docs/guides/eu-ai-act-deployers.md) for the workspace you are signed in to, from 0.3.0 of the CLI (not yet published on npm): a zip with `pack.json` and a readable `SUMMARY.md`, or the JSON alone when `--out` ends in `.json`. Owners, admins, security admins and auditors can export it; other roles get exit code 6. It never replaces a file unless you pass `--force`, and it exits 12 when the pack reports that the ledger did not verify, so a scheduled export notices. ## What your agents can touch ```bash npx immiscible check # this project and your home directory; nothing is sent npx immiscible check --upload # also a report link from the findings, for seven days ``` The local half of the [AI check](https://immiscible.fly.dev/check). It reads files and runs nothing: the MCP servers in `.mcp.json`, `.cursor/mcp.json`, `.vscode/mcp.json`, `~/.claude.json`, `~/.cursor/mcp.json`, Claude Desktop, Windsurf and Gemini CLI, and what each can do (make payments, run shell commands, write to a database, send email); Claude Code's permission settings (`Bash(*)`, `bypassPermissions`, MCP tools allowed without asking); and provider keys in `.env` files, MCP configs and shell config (`~/.zshrc`, `~/.bashrc`, `~/.profile` and the like), with whether git ignores the file. No sign-in is needed. ```text Checked ~/code/hollis-agents (nothing left this machine) Agents OpenAI Agents SDK, LangChain MCP 3 servers · stripe can make payments · postgres can write to a database Claude Claude Code may run any shell command without asking (Bash(*) is allowed) Keys 2 provider keys in .env · 1 in shell config Riskiest first 1 stripe (MCP, .mcp.json) can make payments with a live secret key, so with no limit but the account's, and nothing asks a person first. 2 Claude Code may run any shell command without asking (Bash(*) is allowed), in .claude/settings.json. 3 postgres (MCP, .mcp.json) can write to a database, and nothing asks a person first. ``` A key is never printed or sent. It is shown as its provider, its prefix and last four characters (as the provider's own console shows it) and a fingerprint, `sha256:` and the first 12 hexadecimal characters of its hash, so you can tell two keys apart without seeing either. `--upload` sends only the findings, the sentences above, to `POST /api/check/upload`: no key, redacted or not, no file contents, and no full path (a file outside the project is named from your home directory, such as `~/.zshrc`). It cannot see whether a file is already committed (that needs git itself) or what a key may do at its provider. With `--json`: `{ ok, exitCode, agents, mcp, claude, env, keys, findings, uploaded }`. The exit code is 11 when something is high risk (a payment tool that never asks, any shell command allowed, a key in a file git does not ignore) and 0 otherwise. ## What your agents did ```bash npx immiscible scan # the last 7 days; nothing is sent npx immiscible scan --since 30d --html report.html ``` Reads the session history coding agents already keep on your machine, from 0.3.0 of the CLI (not yet published on npm), and needs no account. | Agent | Read from | |---|---| | Claude Code | `~/.claude/projects/*/*.jsonl` (or `$CLAUDE_CONFIG_DIR/projects`), and the hooks and `permissions.defaultMode` in `~/.claude/settings.json` and each project's `.claude/settings.json` and `settings.local.json` | | Codex | `~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl` (or `$CODEX_HOME/sessions`), and `approval_policy` and `sandbox_mode` in `~/.codex/config.toml` | | Gemini CLI | `~/.gemini/tmp/*/chats/session-*.json`, and the hooks and approval settings in `~/.gemini/settings.json` | | Cursor, Windsurf | noted when installed, not read: both keep their history in a database whose format is not documented | It reports sessions by agent and repository; commands run, flagging force pushes, `rm -rf`, `terraform apply` or `destroy`, `kubectl` and package publishes; files read and written, flagging reads of `.env`, keys, `*.pem` and cloud and registry credentials; outbound calls and the domains reached; a secret read followed by a network call in the same session, the shape of a secret leaving the machine; cost per session from the recorded token counts at list prices (a model without a published price is shown as not priced, never guessed); and the permission modes and hooks in effect, including bypass modes and hooks that are not Immiscible's. It leads with the three most important findings, one sentence each, then the sections. An agent that is not installed is skipped with a note. It also looks for secrets the agents' own history files hold in plain text (agents write tool output to their transcripts word for word: a `cat .env`, a token in a failed deploy's output): private keys, AWS, GitHub, Slack, npm, Stripe, OpenAI, Anthropic, OpenRouter and Google keys, by their published shapes. Each distinct secret is counted once, reported by kind and folder only, and the finding says to rotate them. If your git email is at a company domain, it counts the other people at that domain who committed to these repositories in the last 90 days, from git on your machine, and shows the number; `--no-team` turns that off. No prompt text, file contents or secret values are printed or written, in any format: only paths, command names, domains and counts, and the output says so at the top. A command is reduced to the programs it runs, the secret paths it reads and the hosts it reaches; the command line itself is never shown. `--html ` writes the same report as one self-contained page with no script and no remote resource. With `--json`: `{ ok, exitCode, local, privacy, window, sources, totals, byAgent, byRepo, commands, files, network, mcp, exfiltration, permissions, sessions, transcripts, team, findings }`. The exit code is 11 when something is high risk (a secret read then the network, a live secret in the history files, a force push, approvals bypassed, a `SessionStart` hook that is not Immiscible's) and 0 otherwise. ## Fix it in one command ```bash npx immiscible guard # every coding agent on this machine, local rules, no account npx immiscible guard --dry-run # every change, nothing written npx immiscible guard --off # put every file back exactly as it was ``` The other half of `scan`, from 0.3.0 of the CLI (not yet published on npm): guard puts one fail-closed hook in front of each coding agent it finds (Claude Code, Codex, Cursor, Windsurf, Gemini CLI), with the same hooks `install` writes. With no account the hooks decide on your machine, sending nothing anywhere, from a short set of rules: | | | |---|---| | Refused | force pushes to `main`, `master`, `release/*` or production; `rm -r` of the root or home directory; a secret file and a network or upload command in one call; disk wipes; network or shutdown lines added to shell start-up files | | Asked | publishing a package or image; `terraform`, `pulumi`, `kubectl` and `helm` changes; `curl \| sh`; `sudo`; history rewrites; dropping a database; edits to agent configuration (`.claude/settings.json`, `hooks.json`, `.mcp.json`), shell start-up files and CI workflows; and any network call after the session read a secret file | | Allowed | everything else | Claude Code and Cursor ask you at the keyboard. Codex, Windsurf and Gemini CLI cannot ask from a hook, so a call that needs a person is refused with the way to go ahead (run it yourself if you meant it). Before a call that deletes or overwrites files in a git repository, the hooks take a checkpoint, so [`undo`](#undo) can put the files back. For Claude Code, guard also keeps the settings honest during a session, from 0.3.0 of the CLI. At the start of a session the hook notes the hooks, broad permission rules, bypass mode and MCP switches in the user, project and local settings. Claude Code reloads a settings file that changes mid-session and runs the [`ConfigChange` hook](https://code.claude.com/docs/en/hooks#configchange) first; a change that adds a hook, removes Immiscible's, turns hooks off, allows `Bash(*)` and the like, turns on bypass permissions or adds an MCP server is kept out of the session, with the reason and "if you made the change, restart Claude Code to load it". That is how a planted hook, such as the one the Shai-Hulud worm wrote into `.claude/settings.json`, or a script the agent ran gets in. Managed settings cannot be blocked and are not judged. A project hook new to the machine is named once when a session starts. Hook commands are kept only as a hash and the program's name, never their arguments. A session's "read a secret" mark is kept in `~/.immiscible/state` as the file's short name, never its contents, for a day. `--connect --key ` points the same hooks at your Immiscible server instead, so a named person approves in Slack, Teams, email or on the phone and every decision is signed. `--off` puts back every file guard changed, from `~/.immiscible/guard.json`: a file it created is removed, a file it changed is restored byte for byte, and a file someone has changed since guard wrote it is left alone and named. `--agents claude-code,codex` limits it to some agents; `--scope managed` writes each agent's managed settings, for everyone on the machine. With `--json`: `{ ok, mode, scope, dryRun, state, agents, refused, asked, changed, selfTest }`. ## Undo what an agent deleted ```bash npx immiscible undo # the checkpoints of the last 7 days npx immiscible undo 8b38f79ca2 --dry-run # what would change npx immiscible undo 8b38f79ca2 # put the files back ``` Undo before approve, from 0.3.0 of the CLI (not yet published on npm). Before a call that deletes or overwrites files in a git repository (`rm`, `git clean`, `git reset --hard`, `git checkout --`, `git restore`, `find -delete`, edits to agent configuration), the guard's hooks take a checkpoint: every tracked and untracked file git does not ignore, kept as a commit under `refs/immiscible/checkpoints/` in that repository. It takes about a tenth of a second in a repository of a thousand files. Nothing is pushed, because git pushes branches and tags and never this namespace, and checkpoints older than 7 days are deleted as new ones are taken. `IMMISCIBLE_CHECKPOINTS=off` turns them off. When Claude Code or Cursor asks you to approve such a call, the question ends "If you approve, it can be undone: npx immiscible undo ". A question about something that reaches past the machine, such as a publish, a deploy or the network, ends "This one cannot be undone from here." Codex, Windsurf and Gemini CLI refuse rather than ask, so a refused call takes no checkpoint. `undo ` shows what will change and asks, then takes a checkpoint of how things are now, so the undo can itself be undone. It writes the files back byte for byte with their modes, removes files made since, and restores the index, so staged work stays staged. Commits, branches, ignored files and anything outside the repository are left as they are. Pass `--yes` when there is no terminal. With `--json`: `{ ok, id, repo, command, changes, headMoved, restored, undoWith }`. ## Replay a session ```bash npx immiscible replay # the sessions of the last 7 days npx immiscible replay 3f2a9c1d # one session in full, by the start of its id npx immiscible replay 3f2a9c1d --html run.html # the same, as one self-contained page ``` The flight recorder for your coding agents, from 0.3.0 of the CLI (not yet published on npm). It joins two records already on your machine, the session history Claude Code, Codex and Gemini CLI keep and the decision log `guard`'s hooks write, into one timeline per session: every command, file read and write and fetch in order, with the decision the guard made on each and the rule behind it. A call the guard refused is marked as not run, and a refused call the agent never got to make still appears, on its own line. The hooks write one file a day to `~/.immiscible/decisions` (`IMMISCIBLE_LOG_DIR` to move it, `IMMISCIBLE_LOCAL_LOG=off` to stop it). Each line carries the SHA-256 hash of the line before, so replay notices a line removed or edited, names the file and line, and exits 12. Commands are shown with credentials redacted, by their known shapes and by how random they look; prompt text and file contents are never shown. Nothing leaves the machine and no account is needed. With `--json`, `{ ok, local, chain, sessions }` for the list and `{ ok, local, chain, session }` for one. ## Add the MCP server ```bash immiscible mcp # every client immiscible mcp --client claude-code # or cursor, vscode, windsurf, codex, gemini ``` Prints the one-line command or the config entry that adds Immiscible's MCP server to each client, for the server you use, and changes nothing. The configs read the agent key from `IMMISCIBLE_AGENT_KEY` (which `init` writes to `.env`) or sign in with OAuth; the key itself is never printed. For Cursor it also prints a one-click install link. With `--json`: `{ ok, url, keyVariable, clients: { : { title, command, file, config, note } } }`. More in [add the MCP server](https://immiscible.fly.dev/docs/answers/add-the-mcp-server.md). ## In CI and AI coding agents Every command works without a terminal. Nothing prompts: input comes from flags, and a command that would need to ask stops with exit code 4 and says which flag to pass. Colour is off when stdout is not a terminal or `NO_COLOR` is set, and there is no spinner. ```bash export IMMISCIBLE_URL=https://immiscible.fly.dev # or your own server export IMMISCIBLE_TOKEN=imc_... # from token create --name ci, shown once immiscible init --yes --name "Release agent" --purpose other --json immiscible doctor --json ``` With `--json`, stdout carries one JSON object and nothing else: `ok`, `exitCode`, and the command's result (for `init`: the agent, the rule or the proposal waiting for a second owner, what changed in `.env`, the hook and its diff, the snippet, and the test call; for `doctor`: every check with its status, fix and docs link). Errors are `{ "ok": false, "exitCode": 3, "error": { "code", "message", "fix" } }`. `immiscible help --json` describes every command, flag and exit code as data. `login --json` prints two lines: the code to show a person first, then the result. For CI, make a token for the job rather than reusing your own sign-in: `immiscible token create --name ci` prints a CI token once. It acts for you in the workspace you are signed in to, with your CLI scopes or fewer (`--read-only` leaves out adding agents), and expires in 90 days or sooner (`--days`). `immiscible token list` and `immiscible token revoke ` manage them; owners and admins can also make one in the console, beside service tokens, and see and revoke every CLI sign-in there. ## Exit codes | Code | Meaning | |---|---| | 0 | done | | 1 | unexpected error (a server error, a file that could not be written) | | 2 | usage: an unknown command or flag, or a bad value | | 3 | not signed in, or the token is no longer accepted: run `immiscible login` | | 4 | input needed and this is not a terminal: pass the flags it names | | 5 | the server could not be reached | | 6 | the server refused: a role, a rule, a plan limit | | 7 | `doctor`: a check failed | | 8 | `login`: denied in the browser, or the code expired | | 9 | `init`: the test call did not come back governed | | 10 | `init`: done, and the rule waits for another owner to confirm | | 11 | `check` or `scan`: something high risk was found | | 12 | `verify`: the receipt is not valid (altered, expired, an unknown key or another issuer); `evidence`: the ledger did not verify; `replay`: the decision log's hash chain is broken | The API the CLI uses is in the reference under [Developer CLI](https://immiscible.fly.dev/docs/api/endpoints.md). --- # What is Immiscible? > Short, plain answers: what Immiscible is, how it differs from AI gateways, observability tools and card controls, what it costs, whether you can run it yourself, and what it does not do. Source: https://immiscible.fly.dev/docs/faq ## What is Immiscible? An independent control layer for AI agents and AI spend. Before an agent pays, shares personal data, calls a tool or sends a model request, Immiscible decides against rules a person wrote: `allow` with a signed receipt, `approval_required` so a person decides, or `deny`. It can stop any agent at once, and every decision goes into a signed, hash-chained ledger you can verify offline. See [decisions](https://immiscible.fly.dev/docs/concepts/decisions.md). ## Who is it for? Teams that let AI agents act: pay invoices, buy things, write code, send email, fill in forms with personal data. Finance gets limits, approvals and spend by team; security gets the kill switch, identity and evidence; engineers get one HTTP call, an MCP server, SDKs and a model gateway. ## How is it different from an AI gateway? A gateway routes and meters model requests. Immiscible includes one (OpenAI-shaped and Anthropic-shaped, with budgets checked before each call), but its centre is the decision about what an agent may **do**: payments, data releases and tool calls, with mandates, approvals by a person, receipts and a kill switch. A gateway alone sees prompts, not the payment the agent makes next. See [compare](https://immiscible.fly.dev/docs/compare.md). ## How is it different from LLM observability tools? Observability records what happened so you can debug and evaluate it. Immiscible decides before it happens, and can refuse or hold it for a person. It keeps a record too, but as signed evidence for auditors and merchants, not as traces for evaluating answer quality. ## How is it different from corporate card controls? Card controls limit a card: amounts, merchant categories, freezes. Immiscible decides per action, with the agent's identity, its mandate, what influenced the request (an email, a web page) and a person's approval, and covers data releases, tool calls, crypto and x402 as well as cards. With the [card rail](https://immiscible.fly.dev/docs/guides/card-rail.md), the card issuer asks Immiscible before money moves, so the two work together. ## Do agent frameworks' built-in guardrails not already do this? They run inside one framework, configured by whoever built the agent. Immiscible sits outside every agent, so one set of rules, one kill switch and one record cover Claude, ChatGPT, your own agents and the rest, held by you rather than by any agent vendor. Its SDK integrations wrap framework tools so the two can be used together. ## Which agents and tools does it work with? Anything that can make an HTTP request or speak MCP. Shipped integrations: a Claude Code hook, an MCP server and an MCP proxy (Claude, ChatGPT, Cursor and other MCP clients), SDKs for TypeScript and Python with adapters for the OpenAI Agents SDK, LangChain and the Vercel AI SDK, x402, card issuers, and no-code builders through OpenAPI. Muse is coming soon; Instinct publishes no API, so there is no Instinct connector. See [agents without an API](https://immiscible.fly.dev/docs/guides/agents-without-an-api.md). ## How much does it cost? We charge for the agents we govern, never for the people who approve, and never a share of spend or token volume. Watching (spend from bills, the agent inventory, shadow mode) is free and unlimited. **Free** is £0 with no card: 3 governed agents, up to 5 people, 100 approvals a month in Slack or Teams and 7 days of records. **Team** is £39 a month billed yearly (£49 paid monthly), about $53 and $66, with 10 governed agents, then £5 an agent a month up to 50. **Business** is £499 a month billed yearly (£599 paid monthly), about $675 and $810, with 100 governed agents, then £4 an agent a month up to 500, SCIM and ten years of records. **Enterprise** has no published price: it is for regulated firms and for running it in your own cloud, with ten years of records, a named engineer and your MSA and DPA; talk to us. People are free on every paid plan. Every new workspace starts with thirty days of Business, with no card, then moves to Free; nothing is deleted. We charge in pounds. The details, the [startup programme](https://immiscible.fly.dev/apply/startup) and the [Enterprise evaluation](https://immiscible.fly.dev/apply/scale) are on [the pricing page](https://immiscible.fly.dev/pricing). ## How do you count an agent? A governed agent is anything with its own key, mandate, MCP connection or hook install that asked Immiscible for at least one decision in the month, with your rules enforced. Each person's coding assistant (Claude Code, Cursor, Codex) is one agent. Five people who only send chat through the gateway count as one agent. An agent counts once more for every further 100,000 decisions it asks for in a month. Agents that asked for nothing, and anything only watched, are not counted. Billing shows the count for this month against your plan; it enforces nothing yet, and we tell you before anything changes. Immiscible never resells inference; model traffic runs on your own provider contracts. ## What happens if we go over a limit? Nothing stops today. The governed-agent count is recorded and shown on Billing, and we tell you before anything changes. Going over Free's 5 people starts seven days of grace and an email to the owner. Whatever the plan or the bill, an agent asking to pay, share or act is still decided: no billing state refuses or holds a decision. ## Can I run it myself? Yes. It is one Node process with zero runtime dependencies and one SQLite file, shipped as a read-only Docker image, so it runs in your own VPC. Self-hosting is offered on the Enterprise plan; the server's source code is not public (the SDKs and the CLI are, under MIT, at [efr7-7/immiscible-sdks](https://github.com/efr7-7/immiscible-sdks)). Hosted plans run in the EU (Frankfurt). See [deploy and backups](https://immiscible.fly.dev/docs/guides/deploy-and-backups.md). ## Can an agent just skip it? If the only integration is the agent calling the API, yes: asking is then the agent's choice. That is why the docs lead with paths an agent cannot route around: the MCP proxy holds the tool's credential, the Claude Code hook is run by Claude Code rather than the model, and the card rail makes the issuer ask before money moves. See [ways in](https://immiscible.fly.dev/docs/concepts.md#ways-in). ## What does it not do? - It does not host, resell or fine-tune models. - It does not score answer quality or run evaluations. - It does not hold funds or wallet keys, or sign transactions; the card issuer, wallet or x402 client moves the money after an allow. - It cannot enforce on inference that runs in a vendor's own backend (Devin, GitHub Copilot, Cursor's hosted models); that usage is reconciled from the vendor's API and marked as not governed. - It has no hosted region outside the EU yet: the hosted service runs in Frankfurt, and a US region is not live. ## Where do I start? The [quickstart](https://immiscible.fly.dev/docs/quickstart.md) gets a first decision in five minutes; [for AI agents](https://immiscible.fly.dev/docs/ai-agents.md) is the same in one page for an agent or the person wiring one up. --- # How Immiscible compares > Immiscible beside the kinds of tool it is most often mistaken for: AI gateways, LLM observability, corporate card controls, agent frameworks' built-in guardrails and wallet policy engines. By category, factually, including where each is the better fit. Source: https://immiscible.fly.dev/docs/compare Each category below solves a real problem well. This page says what each one is for, where Immiscible overlaps, and where it does something different. It compares categories, not products: individual products vary, and many combine more than one category. ## At a glance | | Decides before an agent acts | Asks a person for one action | Payments, data and tool calls | Model spend and budgets | Signed, verifiable record | Across agents from any vendor | |---|---|---|---|---|---|---| | AI gateways | model requests | rarely | no | yes | usually logs | yes, for model traffic | | LLM observability | no, records after | no | sees them in traces | reports | traces | yes | | Corporate card controls | card payments | sometimes | card payments only | no | issuer records | any holder of the card | | Framework guardrails | inside the framework | sometimes | what the framework wraps | no | varies | one framework | | Wallet policy engines | on-chain transfers | often | crypto transfers | no | on-chain and logs | any holder of the wallet | | **Immiscible** | **every consequential act and model request** | **yes, in the console, email, Slack or Teams** | **yes** | **yes, through its gateway** | **Ed25519 receipts and a hash-chained ledger** | **yes** | ## AI gateways **What they are for:** one endpoint in front of model providers, for routing, failover, caching, rate limits, cost tracking and budgets. **Overlap:** Immiscible's [gateway](https://immiscible.fly.dev/docs/guides/gateway.md) speaks OpenAI-shaped and Anthropic-shaped traffic, routes under a policy (cost, capability, compliance, jurisdiction), checks budgets before the call and records cost per request, task and team. **Difference:** a gateway governs what goes to the model. Immiscible also governs what the agent does with the answer: the payment, the data release, the tool call. Its gateway records which untrusted content entered a session, so the decision about the next action is judged on what the agent actually read. **Better fit for a gateway alone:** you only need routing, caching and cost reports for model calls, and agents take no consequential actions. Immiscible can also [route through OpenRouter](https://immiscible.fly.dev/docs/guides/openrouter.md), keeping a gateway you already use. ## LLM observability **What it is for:** traces, prompts, latency, cost and evaluations, so engineers can debug and improve an AI application. **Overlap:** Immiscible joins every record to a W3C trace and can export to a SIEM as OCSF or OpenTelemetry. **Difference:** observability records after the fact; Immiscible decides before, and can refuse or hold an action for a person. Its record is evidence: signed, chained and verifiable offline by someone who does not trust the operator. What entered a session and what a proxied tool returned are kept as digests, never as content. **Better fit for observability:** you want to inspect prompts and completions, run evaluations or tune answer quality. The two sit side by side. ## Corporate card controls **What they are for:** limits on a card or cardholder: amounts, merchant categories, single-use cards, freezes, and receipts matched to spend. **Overlap:** Immiscible's [card rail](https://immiscible.fly.dev/docs/guides/card-rail.md) makes the issuer ask before money moves, applies merchant categories with dual control, and matches charges to the receipts that allowed them (for example with [Ramp](https://immiscible.fly.dev/docs/guides/ramp.md)). **Difference:** a card control sees a card and a merchant. Immiscible sees the agent, its mandate, its autonomy tier, what influenced the request (a person, an email, a web page) and whether the domain is a lookalike, and it covers data releases, tool calls, crypto and [x402](https://immiscible.fly.dev/docs/guides/x402.md) as well as cards. **Better fit for card controls alone:** people, not agents, hold the cards, or agents only ever pay by card within fixed limits nobody needs to approve one by one. ## Agent frameworks' built-in guardrails **What they are for:** checks inside one framework or vendor's agent: input and output filters, tool approval prompts, and policies configured by whoever built the agent. **Overlap:** both can stop a tool call and ask a person. Immiscible's SDKs wrap framework tools ([OpenAI Agents SDK](https://immiscible.fly.dev/docs/sdks/integrations/openai-agents.md), LangChain, Vercel AI SDK) so they can be used together. **Difference:** built-in guardrails live inside the agent and answer to its builder. Immiscible sits outside every agent, so one set of rules, one kill switch and one record cover Claude, ChatGPT, your own agents and the rest. With the MCP proxy and the Claude Code hook, the agent cannot route around it. **Better fit for built-in guardrails:** one agent, one framework, one team, and no need for a record that someone outside that team can check. ## Wallet policy engines **What they are for:** rules on a crypto wallet, enforced by the custodian or signer: allowed addresses, amounts, networks and approval quorums. **Overlap:** Immiscible decides crypto payments before the wallet signs, prices them at the rate of the moment, stops lookalike addresses, and answers the [Fireblocks](https://immiscible.fly.dev/docs/guides/fireblocks.md) Co-Signer callback; there are guides for [Turnkey](https://immiscible.fly.dev/docs/guides/turnkey.md), [Privy](https://immiscible.fly.dev/docs/guides/privy.md), [Circle](https://immiscible.fly.dev/docs/guides/circle.md) and [Coinbase CDP](https://immiscible.fly.dev/docs/guides/coinbase-cdp.md) wallets too. **Difference:** a wallet policy governs one wallet's transfers. Immiscible holds the agent's whole authority across cards, crypto, data and tools, with limits kept in pounds. It never holds keys or signs; the wallet still does. **Better fit for the wallet's own policy:** a treasury team moving funds by hand, with no agents involved. ## Where Immiscible is not the answer - You need a model host or a reseller of inference: Immiscible uses your own provider contracts. - You need evaluations or answer-quality scoring: use an observability or evaluation tool. - The agent's inference runs in a vendor's own backend: it can be reconciled from the vendor's API, not enforced. See [what the gateway cannot see](https://immiscible.fly.dev/docs/guides/gateway.md#what-the-gateway-cannot-see). Questions this page does not answer are probably in the [FAQ](https://immiscible.fly.dev/docs/faq.md). --- # How it fits together > What Immiscible does, the six pieces it is made of, the four ways an agent reaches the gate, and the conventions these docs use. Source: https://immiscible.fly.dev/docs/concepts Immiscible is not an agent. It sits between your agents and anything consequential they do, so a model request, a payment or a tool call is decided against rules a person wrote. This page is the map; each piece links to its own page. ## What it does The [gateway](https://immiscible.fly.dev/docs/guides/gateway.md) is where spend becomes something you can control. Change one base URL and every model request is metered, checked against your budgets before the call, and recorded with what it cost. A new workspace starts in [shadow mode](https://immiscible.fly.dev/docs/guides/gateway.md#shadow-mode-first): nothing is rerouted or refused, and Immiscible records what it would have routed to and at what price, so an estimated saving becomes a measured one before anything changes. [Discovery](https://immiscible.fly.dev/docs/guides/discovery.md) lists the provider keys your people run outside it, with each owner and what it spent. Before an agent pays, releases personal data, sends a message or calls a tool, it asks Immiscible, and the answer is `allow`, `deny` or `approval_required`, with reasons a person can read ([decisions](https://immiscible.fly.dev/docs/concepts/decisions.md)). When a person should decide, they are asked in the console, by email, or in [Slack or Teams](https://immiscible.fly.dev/docs/guides/approvals-in-chat.md), and the agent waits. Any agent, or all of them, can be stopped at once, with [holds](https://immiscible.fly.dev/docs/guides/holds-and-drills.md) that say who may start them again. Every allowed action carries a signed [receipt](https://immiscible.fly.dev/docs/concepts/receipts.md), and every decision, approval, stop and change of access goes into a hash-chained, signed [evidence ledger](https://immiscible.fly.dev/docs/concepts/evidence.md) you can verify offline without trusting us. ## How the pieces fit | Piece | What it is | Where to read | |---|---|---| | Agent | A registered AI agent with an id no AI can change, a sponsor, a purpose and a kill owner | [Kill switch](https://immiscible.fly.dev/docs/guides/kill-switch.md) | | Mandate | A signed, standing authority: what the agent may do, for whom, up to what limit, until when | [Mandates](https://immiscible.fly.dev/docs/concepts/mandates.md) | | Autonomy tier | How much the agent may do without a person, earned on evidence: intern, junior, senior, principal | [Autonomy tiers](https://immiscible.fly.dev/docs/concepts/autonomy-tiers.md) | | Decision | The answer to one action request, with reasons and risk signals | [Decisions](https://immiscible.fly.dev/docs/concepts/decisions.md) | | Receipt | A signed, single-use token proving one action was allowed | [Receipts](https://immiscible.fly.dev/docs/concepts/receipts.md) | | Evidence | The chained, checkpointed ledger of everything above | [Evidence](https://immiscible.fly.dev/docs/concepts/evidence.md) | ## Ways in An agent reaches the gate in one of four ways. Pick the strongest one your agent supports. | Path | Who calls the gate | Can the agent skip it? | |---|---|---| | [MCP proxy](https://immiscible.fly.dev/docs/guides/mcp-proxy.md) | Immiscible, on every tool call, holding the tool’s credential | No: the agent has no other route to the tool | | [Card rail](https://immiscible.fly.dev/docs/guides/card-rail.md) | Your card issuer, before money moves | No: the card declines without a receipt | | [Claude Code hook](https://immiscible.fly.dev/docs/guides/mcp-proxy.md#the-claude-code-hook) | Claude Code itself, before each tool runs | No: the model does not run the hook | | [HTTP API](https://immiscible.fly.dev/docs/api/post-v1-actions-authorize.md) or [MCP server](https://immiscible.fly.dev/docs/guides/mcp-proxy.md#the-mcp-server) | The agent, when it chooses | Yes, so pair it with one of the above | > **Tip** > New here? The [quickstart](https://immiscible.fly.dev/docs/quickstart.md) shows the HTTP API under the CLI so you can see every field. Production deployments usually move to the MCP proxy and the card rail, where the agent cannot route around the gate. ## Conventions in these docs - Amounts are in minor units: `6420` is £64.20. - Examples use `https://immiscible.fly.dev`, the hosted service, as the base URL. If you run your own server, use its address instead. - Keys and tokens are shown as `ask_...`, `ims_...` and `aat_...`. Each is shown once when it is made, and never again. --- # 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. --- # Mandates > A mandate is a standing authority granted to one agent. It is written for a person to read and enforced by a machine, and it is signed, so nobody can edit it in the database without the signature failing. Source: https://immiscible.fly.dev/docs/concepts/mandates The same object you see in the console is the one every action is checked against. An agent with no mandate for an action type gets `deny` with `no_mandate`: there is no default allowance. | Kind | Governs | Example | |---|---|---| | `payment` | spending money | supplier invoices up to £2,000 each, £10,000 a month | | `data` | releasing fields from the vault | address and email to `*.nhs.uk` for booking | | `action` | anything else consequential | `tool.call` and `email.send` only to `acme.com` | ## The object ```json { "id": "mdt_91c3e0b2", "agentId": "agt_4f2c91a7", "kind": "payment", "title": "Weekly groceries", "status": "active", "expiresAt": null, "createdBy": "you@example.com", "signature": "base64url Ed25519 over the canonical mandate", "currency": "GBP", "perTransaction": 15000, "perPeriod": 60000, "period": "week", "merchants": { "allow": ["market.example", "grocer.example"], "block": [] }, "categories": ["groceries"], "approveAbove": 8000, "newMerchant": "approve" } ``` `status` is `active`, `revoked` or `expired`. Amounts are minor units of `currency`. A change is a new signature and a ledger record naming who made it. ## Payment mandates | Field | Meaning | |---|---| | `currency` | the only currency covered; anything else is `currency_mismatch`, a deny | | `perTransaction` | the most one action may spend | | `perPeriod`, `period` | the most all actions under this mandate may spend per `day`, `week` or `month` | | `merchants.allow` | domains the agent may pay without further question | | `merchants.block` | domains it may never pay | | `approveAbove` | ask a person above this amount, even inside the limits | | `newMerchant` | for a merchant not allow-listed and never seen: `approve` (ask), `allow` or `deny` | Breaching `perTransaction` or `perPeriod` is a deny, not a question. To be asked instead, set `approveAbove` below the limit and keep the limit as the ceiling nobody can talk past. The workspace also keeps one allowance and one never-pay list across every mandate, under **Settings**: a payee on the never-pay list is refused whichever mandate an agent cites. ## Data mandates and the vault The vault holds a person's fields (name, address, email, passport number) sealed at rest. An agent never holds them: it asks for a release, and on `allow` the values come back once, in `released`, for that recipient only. | Field | Meaning | |---|---| | `fields` | vault fields this agent may release, for example `address`, `email` | | `recipients` | domains it may release them to; `*.nhs.uk` covers subdomains | | `purposes` | why, for example `booking`; recorded with each release | A release outside `recipients` is `recipient_not_allowed`, a deny. Restricted fields (`passport`, `national_id`, `bank_account`, `card`, `health`) always need a person **unless** the mandate names that field **and** that recipient. A mandate for `passport` to `*` does not count. ## Action mandates | Field | Meaning | |---|---| | `actions` | action types this agent may take: `tool.call`, `email.send`, `calendar.write`, `account.change` or your own | | `domains` | where those actions may reach, for example `github.com`, `acme.com` | | `newDomain` | `approve`: a destination not in `domains` asks a person; `deny`: it is refused. Left out, a list of domains is closed and no list means any destination. An agent added as **Something else** starts with `tool.*` and `newDomain: approve`: anything that reaches another site asks a person | | `readOnly` | `allow`: a provably read-only tool call (Claude Code's Read, Glob, Grep and LS, or one Bash command such as `ls`, `cat`, `pwd`, `which`, `git status`, `git diff` or `git log` with plain arguments: no pipes, redirection, subshells or separators, and no secrets file) goes ahead without a person even while the agent is new, and is still decided and recorded; `ask`, or left out: the agent's standing decides, so a new agent asks. Anything that writes, runs code or reaches another site is judged as before. **Something else** and **Coding agent** start with `allow` | | `localWrites` | `allow-after-intern`: once the agent is past its intern stage, a tool call that edits a file or runs a test or build inside its project goes ahead without a person; an intern still asks before every write. `ask`, or left out: a person decides, at any standing. `allow` is the older word for `allow-after-intern` and means the same, because the intern standing asks before anything that is not read-only either way. **Something else** (General tasks) and **Coding agent** start with `allow-after-intern`; to have a person approve every edit and test run, replace the rule with one that sets `ask`. "Inside its project" is read from the project the hook sends (Claude Code's `CLAUDE_PROJECT_DIR`, or the working directory) and the paths in the call; a call that does not show it stays inside, such as one from an older hook that does not send the project, asks. Whatever this says, and at every standing, these ask: a destructive command (`rm`, `git push --force`, `git reset --hard`, `git clean`, a history rewrite such as `git rebase`, recursive `chmod` or `chown`, `dd`, `mkfs`), any `git push`, a deploy (`fly deploy`, `vercel`, `kubectl apply`, `terraform apply` and the like), a publish (`npm publish`, `twine upload`), a package install from the network (`npm install`, `pip install`, `npx`, or piping a download into a shell), anything run with `sudo`, anything that names a path outside the project, and a change to the agent's own hook and settings (`.claude/settings.json`), `.mcp.json` or git's hooks and config. A command that cannot be read with confidence asks too, and a secrets file or the environment leaving the machine is refused | Three things hold under every action rule, whatever it says and however trusted the agent: - A destructive command asks a person: `rm`, `git push --force`, `git reset --hard`, `git clean`, piping a download into a shell (`curl ... | sh`), writing over a file outside the project (`~/.bashrc`, `~/.ssh`, `/etc`, `.git/hooks`), dropping a database table, publishing a package, and the like (`destructive_command`). - A command that cannot be read with confidence asks a person: one too long to be sent whole, one spelt in escape codes or built from a variable, or one carrying invisible characters (`unverifiable_command`). - A secrets file (`.env`, keys, `~/.aws`, `~/.ssh`) or the environment leaving the machine is refused (`secrets_leaving`). Every site a command names is checked against `domains`, not only the first, and a command that uses the network without naming where (`git push origin`, `ssh host`) asks a person under a rule that names domains (`unnamed_destination`). > **Tip** > Name the domains. A mandate with no domains means any destination, and once untrusted content is in the agent's session the Rule of Two then asks a person before each call. Listing domains turns those questions into clean refusals for anywhere else. ## Rules add up An action needs to fit only one of an agent's mandates, so the broadest one sets the limit. Two things keep a broad mandate from quietly undoing a narrow one: - A payment to a merchant that a mandate allow-lists is judged under that mandate, and an action a mandate names exactly (`tool.call`) is judged ahead of a wildcard (`tool.*`). - Creating a mandate that allows anywhere (an action mandate with no domains, or a payment mandate with no merchant list that allows new merchants) beside a narrower one for the same agent is refused with `409 confirm_broaden` and a `reason` such as "This rule allows more than an existing rule for this agent: tool calls to any domain (Coding agent allows only github.com, registry.npmjs.org)". Send it again with `confirmBroaden: true` to save it; the answer then carries the same sentence as `warning`, and the ledger record says which mandates it widens. ## A rule for your own agent Nobody widens the authority of an agent that acts for them on their own say-so. In a workspace with another owner, a mandate you write for an agent that acts for you, and that gives it anything its current mandates do not (a higher limit per payment or per period, a new kind of authority or action, a merchant, recipient, field or domain it cannot reach now, or no approval line where it has one), is not saved. The answer is `403 second_owner_required`: ```json { "error": { "type": "second_owner_required", "message": "This agent acts for you, and this rule gives it more than it has now (up to £50,000 a payment, more than the £150 it has now), so another owner of this workspace confirms it. It has been sent to them as a proposal.", "proposalId": "prp_6f1c2a9e", "why": ["up to £50,000 a payment, more than the £150 it has now"] } } ``` The proposal waits for another owner for seven days: [`GET /api/w/$IMMISCIBLE_WORKSPACE/proposals`](https://immiscible.fly.dev/docs/api/get-api-w-wid-proposals.md) lists them, and [`POST .../proposals/:pid/confirm`](https://immiscible.fly.dev/docs/api/post-api-w-wid-proposals-pid-confirm.md) saves the mandate exactly as it was asked for, with both names on the record; `.../dismiss` drops it. Whoever proposed it cannot confirm it. Raising the workspace's burst lines (`agentVelocity` in the settings) works the same way. In the same workspace, the person an agent acts for never approves its payments either: someone else does, whatever the mandate's approval line. ## What a mandate cannot do A mandate grants authority. It cannot remove the floors under it. - It cannot switch off the **Rule of Two**: untrusted content driving a payment or data release with an external effect needs a person, however generous the mandate. - It cannot permit a **lookalike domain**. `arnazon.com` is denied even if someone allow-listed it. - It cannot outlive the **kill switch**. A frozen agent is denied everything. - It cannot lift the agent above its **tier**. An intern asks about everything that matters whatever its mandates say. ## Templates The console offers templates (groceries, subscriptions and bills, travel, data sharing). Each is a starting point; every field is editable before saving. A [service token](https://immiscible.fly.dev/docs/api/authentication.md#service-tokens) registering agents as code may only attach mandates **from templates**: a machine never writes a custom spec. ## Revoking and expiry Revocation is immediate: the next request under the mandate is `no_mandate`. A mandate with `expiresAt` stops on its own at that moment. Neither cancels an action already allowed and settled; they stop the next one. ## API | Method | Path | | |---|---|---| | `GET` | [`/api/w/:wid/mandates`](https://immiscible.fly.dev/docs/api/get-api-w-wid-mandates.md) | list | | `POST` | [`/api/w/:wid/mandates`](https://immiscible.fly.dev/docs/api/post-api-w-wid-mandates.md) | create, signed on creation | | `POST` | [`/api/w/:wid/mandates/:mid/revoke`](https://immiscible.fly.dev/docs/api/post-api-w-wid-mandates-mid-revoke.md) | revoke | | `GET` | [`/api/w/:wid/mandate-templates`](https://immiscible.fly.dev/docs/api/get-api-w-wid-mandate-templates.md) | the templates | --- # Autonomy tiers > How much an agent may do without a person is earned on evidence, one rung at a time, with sign-off from the people who carry the risk. An agent nobody has graded is an intern. Source: https://immiscible.fly.dev/docs/concepts/autonomy-tiers Mandates say what an agent may do. Its tier says how much of that it may do **alone**. A tier can only turn an `allow` into a question; it never turns a question or a refusal into an `allow`. ## The four tiers | Tier | Says | Pays alone up to | Releases data alone | Records per action, alone / at most | |---|---|---|---|---| | `intern` | Prepares the work. A person signs off every payment, data release and outside action | nothing | no | 0 / 1,000 | | `junior` | Handles small, routine work alone. Bigger payments and any data release go to a person | £50 | no | 100 / 10,000 | | `senior` | Trusted with everyday payments and releases inside its mandates. Large ones go to a person | £1,000 | yes | 1,000 / 100,000 | | `principal` | Works inside its mandates without routine sign-off. Its mandates are its only limits | no line | yes | no line | The money lines are held in pounds and converted for other currencies at a reference rate; a currency with no reference rate is checked by a person at every tier below principal. Records above the "alone" figure are asked about; above the "at most" figure they are refused (`volume_hard_limit`). New agents start at the workspace's **starting tier**, `intern` unless an owner chose `junior` on the record (with colleagues in the workspace, a second owner confirms). A [service token](https://immiscible.fly.dev/docs/api/authentication.md#service-tokens) registering agents can never ask for more. ## Earning the next rung Promotion is one step at a time, and only when the evidence is there: | To | Clean decisions | Days of history | Days without an incident | Sign-offs | |---|---|---|---|---| | `junior` | 20 | 7 | 7 | business | | `senior` | 150 | 30 | 30 | business, security | | `principal` | 1,000 | 90 | 90 | business, security, legal | An agent refused more than 10% of the time since its last change of tier is not ready: an agent that is refused often is probing, or doing the wrong job. The console shows exactly what is missing ("12 more clean decisions; sign-off from security"). ### Separation of duties - Nobody promotes an agent that acts for them. - Whoever signed off a promotion does not also make it. - Auditors check sign-offs; they do not give them. - A sign-off is for the promotion in front of it, and lapses after 30 days. ### Break-glass An owner can promote without the evidence, with a reason of at least a sentence. With anyone else in the workspace it is a proposal a **different** owner confirms within seven days, and no agent is overridden twice in a week. It is still one rung: break-glass is for a rung, not the ladder. ## Losing it Demotion needs no evidence and takes effect at once. It also happens without anyone choosing it: - an access profile not [recertified](https://immiscible.fly.dev/docs/guides/traces-and-reviews.md#recertification) by its due date demotes the agent to intern and freezes it; - a review verdict of `incident` freezes the agent under an incident hold. ## Standing Every agent carries its standing: the **sponsor** who answers for it, its **purpose** in a sentence, the **kill owner** who may stop it, and when its **assignment** ends (at most 366 days, then someone renews it). The people named must be members of the workspace. ```bash curl -X PATCH "https://immiscible.fly.dev/api/w/$IMMISCIBLE_WORKSPACE/agents/agt_4f2c91a7/standing" \ -H "cookie: __Host-sid=$IMMISCIBLE_SESSION" -H "x-immiscible-csrf: 1" \ -H "content-type: application/json" \ -d '{ "sponsor": "cfo@acme.example", "purpose": "Pays approved supplier invoices", "killOwner": "secops@acme.example", "assignmentEndsAt": "2027-01-31T00:00:00Z" }' ``` Every change of standing and tier is a record in the [evidence ledger](https://immiscible.fly.dev/docs/concepts/evidence.md): who sponsored the agent, who signed off its promotion and on what evidence, who demoted it and why. A promotion is a decision about risk, so it leaves the same kind of evidence a payment does. ## API | Method | Path | | |---|---|---| | `GET` | [`/api/w/:wid/agents/:aid/standing`](https://immiscible.fly.dev/docs/api/get-api-w-wid-agents-aid-standing.md) | tier, readiness, sign-offs | | `PATCH` | [`/api/w/:wid/agents/:aid/standing`](https://immiscible.fly.dev/docs/api/patch-api-w-wid-agents-aid-standing.md) | sponsor, purpose, kill owner, assignment | | `POST` | [`/api/w/:wid/agents/:aid/signoffs`](https://immiscible.fly.dev/docs/api/post-api-w-wid-agents-aid-signoffs.md) | sign off a promotion | | `POST` | [`/api/w/:wid/agents/:aid/tier`](https://immiscible.fly.dev/docs/api/post-api-w-wid-agents-aid-tier.md) | promote or demote | | `GET` | [`/api/w/:wid/governance/scorecard`](https://immiscible.fly.dev/docs/api/get-api-w-wid-governance-scorecard.md) | the signed zero trust scorecard | --- # Receipts > Every allowed action carries a short signed token that says which agent was allowed to do what, under which mandate, and whether a person approved it. Anyone can check it, online or offline. Source: https://immiscible.fly.dev/docs/concepts/receipts A receipt travels with the action. The agent can hand it to a merchant; the card rail spends it; an auditor can check it years later against the ledger. ## What a receipt is A compact JWS, signed with Ed25519. The header: ```json { "alg": "EdDSA", "kid": "k_2026_09", "typ": "assay-receipt+jwt" } ``` The claims: ```json { "iss": "https://immiscible.fly.dev", "sub": "agt_4f2c91a7", "act": "act_7Qm2c1f0", "typ": "payment", "amt": 4200, "cur": "GBP", "mer": "grocer.example", "mdt": "mdt_91c3e0b2", "hum": false, "iat": 1790444901, "exp": 1790445201, "jti": "r_2b7e4c" } ``` | Claim | Meaning | |---|---| | `iss` | the Immiscible deployment that issued it | | `sub` | the agent | | `act` | the action this receipt allows | | `typ` | the action type, for example `payment` | | `amt`, `cur` | the authorised amount in minor units, and its currency | | `mer` | the merchant domain it was authorised for | | `mdt` | the mandate it was allowed under | | `hum` | `true` when a person approved this specific action, not only the mandate | | `iat`, `exp` | issued and expiry times; a receipt is valid for five minutes | | `jti` | a unique id; a receipt is single use | A receipt names no person. It carries pseudonymous ids only the issuing workspace can resolve. ## Verifying online No account and no key are needed: curl: ```bash curl -X POST "https://immiscible.fly.dev/v1/verify" \ -H "content-type: application/json" \ -d '{ "receipt": "eyJhbGciOiJFZERTQSIs..." }' ``` Node: ```ts const res = await fetch('https://immiscible.fly.dev/v1/verify', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ receipt }), }); const { valid, claims, reason, replayed } = await res.json(); ``` Python: ```python import requests res = requests.post("https://immiscible.fly.dev/v1/verify", json={"receipt": receipt}) result = res.json() ``` ```json { "valid": true, "claims": { "iss": "https://immiscible.fly.dev", "sub": "agt_4f2c91a7", "act": "act_7Qm2c1f0", "typ": "payment", "amt": 4200, "cur": "GBP", "mer": "ocado.com", "mdt": "mdt_91c3e0b2", "hum": false, "iat": 1791209943, "exp": 1791210243, "jti": "w0JBpe6jFQ6NA-RbE0MrgUDQ" } } ``` A valid receipt has no `reason`. Online verification marks the receipt as seen, so a second check of the same receipt returns `"valid": false` with `"replayed": true`. `valid: false` always comes with a `reason` in plain English: a bad signature, an unknown key, an expired receipt (`"expired": true`), a wrong issuer, a withdrawn receipt (`"revoked": true`), a charge it does not cover (`"mismatch": true`, when you send `expect`) or a replay. ## Verifying offline The public keys are a JWKS at `https://immiscible.fly.dev/.well-known/immiscible-keys.json`. Fetch it, keep it, and check receipts without calling Immiscible at all. The whole verifier in Node 22, using only `node:crypto`, is in [verifying offline](https://immiscible.fly.dev/docs/security/verifying-offline.md#receipts). Offline verification cannot see replays. If single use matters to you (for payments it should), keep the `jti` values you have seen in the last five minutes, or call `/v1/verify` once per order. ## What a receipt proves, and what it does not It proves that Immiscible, at `iat`, allowed this agent this action under a mandate a person created, and, when `hum` is `true`, that a signed-in person approved this action specifically. It does **not** prove who the person is, that a payment will clear, or that the goods are what the agent described. It is not a payment guarantee and it does not move chargeback liability. It is evidence of authority, to sit alongside the card network's own checks. ## Receipts at the card With the [card rail](https://immiscible.fly.dev/docs/guides/card-rail.md) connected, the receipt is not only evidence, it is the key: a card bound to an agent authorises only against an unused receipt for the same payee and currency that covers the amount, and the authorisation uses it up. ## With agent payment protocols Agent payment protocols are converging on the same idea, a signed statement of what a person authorised: Google's Agent Payments Protocol (AP2), OpenAI and Stripe's Agentic Commerce Protocol (ACP), Visa's Trusted Agent Protocol and Mastercard's Agent Pay. An Immiscible receipt is designed to travel alongside those, as an extra field carrying the independent record of what the person's own rules allowed. We do not claim certification by, or membership of, any of these schemes. --- # Evidence > Every decision, approval, freeze, promotion and change of access is a record in a hash-chained ledger whose head is signed at intervals. A rewrite shows, and you can check it without trusting us. Source: https://immiscible.fly.dev/docs/concepts/evidence Logs written by the system they describe are hard to trust: the same system that made a decision could rewrite the record of it. Immiscible's evidence is built so that a rewrite is visible. ## Three properties 1. **Chained.** Every record names the SHA-256 hash of the record before it. Changing, removing or reordering one breaks the chain at that point. 2. **Checkpointed.** At intervals, and whenever someone asks, the head of the chain is signed with Ed25519 into a checkpoint. A checkpoint you kept proves that the history up to it is the history you were shown, even if every copy we hold were rewritten. 3. **Keyed for the long run.** A bundle carries every public key that ever signed a checkpoint, including retired ones, so a checkpoint from years ago still verifies after rotation. ## What a record holds An abridged decision record. The envelope fields are exact (the [specification](https://immiscible.fly.dev/docs/security/evidence-spec.md#records) defines them); the payload varies by `kind`. ```json { "schema": "assay.evidence.v1", "seq": 4182, "id": "rec_0d1c9e", "at": "2026-10-04T09:12:44.018Z", "prev": "9a0f3c...", "kind": "agent_decision", "payload": { "agentId": "agt_4f2c91a7", "decision": "allow", "signals": [], "subject": { "kind": "agent", "id": "agt_4f2c91a7" }, "traceparent": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01", "profile": { "version": 7, "hash": "c41e..." } }, "hash": "2b77e1..." } ``` Payloads hold digests and pseudonyms in place of personal data. Every record carries a **subject** (the agent, person, single sign-on subject, application or key that acted) and the **W3C trace** it belongs to, and every decision records the access profile version and hash it was made under. ## What is recorded | Area | Kinds include | |---|---| | Agents | decisions, settlements, incidents, freezes and lifts, drills | | Authority | mandates created and revoked, tier changes, sign-offs, overrides | | Access | applications and their versions, profile changes, allowlists, entitlements, recertifications | | People | approvals and denials with channel and who decided, reviews and their verdicts | | Machines | service tokens made and revoked, and every action a token took, by name | | The card rail | every authorisation, approved or declined, and every clearing | | The gateway | per-request records: model, cost, data region, budget, routing rule applied | ## Getting it out - **The bundle.** [`GET /api/w/:wid/evidence/bundle`](https://immiscible.fly.dev/docs/api/get-api-w-wid-evidence-bundle.md): records, checkpoints and keys in one JSON document, or streamed for large workspaces. - **Checkpoints.** [`POST /api/w/:wid/evidence/checkpoints`](https://immiscible.fly.dev/docs/api/post-api-w-wid-evidence-checkpoints.md) signs the head now. Keep the token it returns somewhere we cannot reach. - **Machines.** A [service token](https://immiscible.fly.dev/docs/api/authentication.md#service-tokens) with `evidence:read` reads the bundle and the signed scorecard. - **Your SIEM.** As [OCSF or OpenTelemetry](https://immiscible.fly.dev/docs/guides/siem-export.md), pulled or pushed by signed webhook. ## Verifying it ```bash node scripts/verify-evidence.mjs bundle.json --keys keys-you-kept.json ``` One file, no dependencies, short enough to read before running. Exit code `0` means everything verified; otherwise the report names the first record that broke and every checkpoint that no longer holds. The format and the verifier are specified in the [evidence specification](https://immiscible.fly.dev/docs/security/evidence-spec.md), and the step by step is in [verifying offline](https://immiscible.fly.dev/docs/security/verifying-offline.md). > **Important** > A bundle on its own proves only that it is consistent with itself. Keep the public keys when you start relying on the evidence, or keep checkpoints as they are issued, or both. Either one closes the gap. ## What this is not Evidence proves the record was not changed after it was written and signed. It does not prove a decision was right, and it does not make anyone compliant with anything. It is evidence a compliance process can rely on; the bundle lists the published record-keeping obligations it supports (for example EU AI Act Art. 12 and Art. 26(6)) as references for whoever writes the compliance file. --- # Answers > Short, direct answers to the questions people ask about controlling what AI agents spend, share and do, each with the commands or code to do it and what it cannot do. Source: https://immiscible.fly.dev/docs/answers Each page answers one question in its first two sentences, then shows how, then says where the answer stops. They are written to be quoted, by a person or a model. Every page is also Markdown at its address plus `.md`. ## Spend and payments - [How do I stop an AI agent from spending money without approval?](https://immiscible.fly.dev/docs/answers/stop-an-agent-spending.md) - [How do I control LLM costs per team?](https://immiscible.fly.dev/docs/answers/llm-costs-per-team.md) - [How do I control what an AI agent pays with x402 or a crypto wallet?](https://immiscible.fly.dev/docs/answers/x402-and-wallet-payments.md) ## Approvals and permissions - [How do I make an AI agent ask a person before it acts?](https://immiscible.fly.dev/docs/answers/human-approval-for-agents.md) - [How do I add approval to tool calls in the OpenAI Agents SDK, LangGraph or the Vercel AI SDK?](https://immiscible.fly.dev/docs/answers/framework-tool-approvals.md) - [How do I allow or deny MCP tool calls by policy?](https://immiscible.fly.dev/docs/answers/mcp-tool-permissions.md) - [How do I stop an AI agent sending data where it should not?](https://immiscible.fly.dev/docs/answers/agent-data-exfiltration.md) ## Coding agents - [How do I block dangerous commands in Claude Code?](https://immiscible.fly.dev/docs/answers/claude-code-block-commands.md) - [How do I make Cursor's agent ask before risky tool calls?](https://immiscible.fly.dev/docs/answers/cursor-agent-safety.md) - [How do I add Immiscible's MCP server to Claude Code, Cursor or VS Code?](https://immiscible.fly.dev/docs/answers/add-the-mcp-server.md) ## Stopping, records and regulation - [How do I stop all my AI agents at once?](https://immiscible.fly.dev/docs/answers/kill-switch-for-ai-agents.md) - [What logging do AI agents need for the EU AI Act?](https://immiscible.fly.dev/docs/answers/eu-ai-act-logging.md) ## Choosing - [What kinds of tool control what AI agents spend and do?](https://immiscible.fly.dev/docs/answers/tools-for-controlling-ai-agents.md) - [How Immiscible compares](https://immiscible.fly.dev/docs/compare.md), and the [FAQ](https://immiscible.fly.dev/docs/faq.md). --- # How do I stop an AI agent from spending money without approval? > Put a decision in front of every payment the agent makes, with an amount above which a person must approve. With Immiscible that is one rule ("ask me above £500") and a gate the payment has to pass. Source: https://immiscible.fly.dev/docs/answers/stop-an-agent-spending Put a decision in front of every payment the agent makes, and write the rule as an amount above which a person must approve. With Immiscible, the agent (or the card issuer, or the MCP proxy) asks before any money moves, and the answer is `allow` with a signed receipt, `approval_required` while a person decides in the console, by email, in Slack or in Teams, or `deny`. ## How do I set it up? 1. **Connect the agent.** In the project the agent runs from: ```bash npx immiscible init --purpose pays_invoices ``` It signs you in through the browser, creates the agent with a payment rule, writes `IMMISCIBLE_URL` and `IMMISCIBLE_AGENT_KEY` to `.env`, and ends with a live test call. See [the CLI](https://immiscible.fly.dev/docs/cli.md#init). 2. **Set the approval line.** In the console, **Agents**, **Agent limits**, edit the payment rule and set **Ask me above** to £500. The rule also carries a per-payment ceiling nobody can talk past (above it is `deny`, not a question) and a monthly total. The JSON is in [ask a person above an amount](https://immiscible.fly.dev/docs/ai-agents.md#ask-a-person-above-an-amount). 3. **Ask before paying.** From code, with the TypeScript SDK: ```ts import { Immiscible } from '@immiscible/sdk'; const immiscible = new Immiscible().run(); // IMMISCIBLE_URL and IMMISCIBLE_AGENT_KEY from the environment await immiscible.pay( { amount: 42000, currency: 'GBP', merchant: 'northwind.example', summary: 'October invoice', provenance: [{ source: 'user' }] }, () => payInvoice(), // runs only on allow, after a person approves if one is asked ); ``` Or from an MCP client, with the `request_payment` tool on [the MCP server](https://immiscible.fly.dev/docs/ai-agents.md#the-mcp-server). Amounts are whole minor units: `42000` is £420.00. ## What stops the agent paying some other way? Asking is the agent's choice when the only integration is the agent calling the API. Close the other routes: - **The card rail.** Give the agent a virtual card bound to it, and the issuer asks Immiscible before every authorisation: no receipt, no payment. See [the card rail](https://immiscible.fly.dev/docs/guides/card-rail.md). - **The MCP proxy.** Put the payment tool behind Immiscible, which holds its credential, so the agent cannot call it directly. See [the MCP proxy](https://immiscible.fly.dev/docs/guides/mcp-proxy.md). - **The Claude Code hook.** For coding agents, every shell command and web request is checked before it runs, so a `curl` to a payment API the rule does not name is refused or asked about. See [the Claude Code hook](https://immiscible.fly.dev/docs/guides/mcp-proxy.md#the-claude-code-hook). ## How do I set spend limits for an AI agent? A payment rule (a [mandate](https://immiscible.fly.dev/docs/concepts/mandates.md)) holds the limits: per payment, per day, week or month, which merchants, which categories, and what happens at a new merchant (`approve` or `deny`). On top of the rule, a new agent starts as an intern (unless an owner has set the workspace to start agents as juniors) and a person signs off every payment until it has earned more on evidence; see [autonomy tiers](https://immiscible.fly.dev/docs/concepts/autonomy-tiers.md). With no rule at all the answer is `deny`; there is no default allowance. ## Is it safe to give an AI agent a credit card? Safer with a card bound to the agent and an issuer that asks before money moves. With [the card rail](https://immiscible.fly.dev/docs/guides/card-rail.md) and Stripe Issuing (or another issuer through signed webhooks), every authorisation is decided against the agent's rule, approved or declined in real time, and recorded. Keep the card's own limits as well: they are the backstop if anything upstream fails. ## What does it not do? - It never holds money, wallet keys or card numbers, and never signs a transaction; the issuer, wallet or payment API moves the money after an allow. - It cannot see a payment the agent makes with a credential Immiscible does not stand in front of. Take direct credentials away from the agent. - A person must answer within the approval's lifetime; an unanswered request does not become an allow. Next: [the quickstart](https://immiscible.fly.dev/docs/quickstart.md) runs this end to end in five minutes. --- # How do I add Immiscible's MCP server to Claude Code, Cursor or VS Code? > One command in Claude Code, one JSON entry in Cursor, VS Code, Windsurf, Codex or Gemini CLI, or a custom connector in Claude and ChatGPT. The server is at /mcp and takes an agent key or OAuth sign-in. Source: https://immiscible.fly.dev/docs/answers/add-the-mcp-server The MCP server is at `https://immiscible.fly.dev/mcp` (Streamable HTTP), and it takes an agent key as a bearer token or an OAuth sign-in. In Claude Code it is one command, `claude mcp add --transport http immiscible https://immiscible.fly.dev/mcp --header "Authorization: Bearer $IMMISCIBLE_AGENT_KEY"`; in other clients it is one JSON entry, below. ## 1. Get an agent key ```bash npx immiscible init --yes ``` This writes `IMMISCIBLE_URL` and `IMMISCIBLE_AGENT_KEY` to `.env` (and adds `.env` to `.gitignore`). An AI coding agent can run it too: see [can an AI coding agent install it itself?](#can-an-ai-coding-agent-install-it-itself). Load the key into your shell with `export $(grep IMMISCIBLE_ .env | xargs)`, or skip the key and sign in with OAuth where the client supports it. ## 2. Add the server to your client ```bash npx immiscible mcp # every client's setup, for your server npx immiscible mcp --client cursor # one client; --json for a script ``` It prints the entries below for your own server address, and changes nothing. Claude Code: ```bash claude mcp add --transport http immiscible https://immiscible.fly.dev/mcp \ --header "Authorization: Bearer $IMMISCIBLE_AGENT_KEY" ``` .mcp.json (Claude Code, shared): ```json { "mcpServers": { "immiscible": { "type": "http", "url": "https://immiscible.fly.dev/mcp", "headers": { "Authorization": "Bearer ${IMMISCIBLE_AGENT_KEY}" } } } } ``` .cursor/mcp.json: ```json { "mcpServers": { "immiscible": { "url": "https://immiscible.fly.dev/mcp", "headers": { "Authorization": "Bearer ${env:IMMISCIBLE_AGENT_KEY}" } } } } ``` .vscode/mcp.json: ```json { "inputs": [ { "type": "promptString", "id": "immiscible-agent-key", "description": "Immiscible agent key (ask_...)", "password": true } ], "servers": { "immiscible": { "type": "http", "url": "https://immiscible.fly.dev/mcp", "headers": { "Authorization": "Bearer ${input:immiscible-agent-key}" } } } } ``` Windsurf (Devin Desktop): ```json { "mcpServers": { "immiscible": { "serverUrl": "https://immiscible.fly.dev/mcp", "headers": { "Authorization": "Bearer ${env:IMMISCIBLE_AGENT_KEY}" } } } } ``` Codex CLI: ```toml [mcp_servers.immiscible] url = "https://immiscible.fly.dev/mcp" bearer_token_env_var = "IMMISCIBLE_AGENT_KEY" ``` - **Claude Code**: the command stores the header for you. `.mcp.json` in a shared project expands `${IMMISCIBLE_AGENT_KEY}` from each person's environment, and Claude Code asks each person to approve the server once. - **Cursor**: `.cursor/mcp.json` in the project, or `~/.cursor/mcp.json` for every project. `npx immiscible mcp --client cursor` also prints a one-click install link. - **VS Code** (GitHub Copilot agent mode): `code --add-mcp '{"name":"immiscible","type":"http","url":"https://immiscible.fly.dev/mcp"}'` adds it with OAuth sign-in instead of a key; `npx immiscible mcp --client vscode` also prints a `vscode:mcp/install` link. - **Windsurf** (now Devin Desktop): `~/.config/devin/mcp_config.json` (on Windows `%APPDATA%\devin\mcp_config.json`; older Windsurf releases read `~/.codeium/windsurf/mcp_config.json`). - **Codex CLI**: `codex mcp add immiscible --url https://immiscible.fly.dev/mcp --bearer-token-env-var IMMISCIBLE_AGENT_KEY` writes the entry above. - **Gemini CLI**: `gemini mcp add --transport http -H "Authorization: Bearer $IMMISCIBLE_AGENT_KEY" immiscible https://immiscible.fly.dev/mcp`, or the Gemini extension in `packages/immiscible-gemini`. - **Claude Code plugin and Claude Desktop**: see [install in your assistant](https://immiscible.fly.dev/docs/ai-agents.md#install-in-your-assistant) for the plugin (hook, MCP server, skill and analyst) and the `.mcpb` bundle. - **Claude, ChatGPT and other connector clients**: add `https://immiscible.fly.dev/mcp` as a custom connector and sign in. You choose which agent the connection acts as on the consent screen. See [connectors](https://immiscible.fly.dev/docs/guides/mcp-proxy.md#claude-and-chatgpt-as-connectors). ## 3. Check it works ```bash claude mcp list # Claude Code: immiscible should show as connected npx immiscible doctor # the server, the clock, the key and the hook ``` The client should list eleven tools: `request_payment`, `request_personal_data`, `authorize_action`, `check_action_status`, `explain_decision` and `settle_action`, and the AI spend analyst's `spend_summary`, `find_waste`, `unwatched_keys`, `set_budget` and `revoke_key`. What each is for is in [the MCP server](https://immiscible.fly.dev/docs/ai-agents.md#the-mcp-server). ## Can an AI coding agent install it itself? Yes, with a person allowing the sign-in once. Every command runs without a terminal: input comes from flags, `--json` prints one object, and each outcome has its own [exit code](https://immiscible.fly.dev/docs/cli.md#exit-codes). ```bash npx immiscible login --json # line 1: a link for the person to open; line 2, once they allow it: the result npx immiscible init --yes --json # creates the agent and its rule, writes .env, installs the Claude Code hook, tests the gate npx immiscible mcp --client claude-code --json npx immiscible doctor --json # exit 0 when nothing failed ``` The agent should show the person the `verification_uri_complete` from the first line and wait; nothing is allowed until a person signs in and approves in the browser. In CI, use a token instead: `IMMISCIBLE_TOKEN` from `immiscible token create --name ci`. Exit code `3` means not signed in, `4` means a flag is needed (the error names it), and `10` means the agent's rule waits for another owner to confirm. ## Is the MCP server enough on its own? No. Through the MCP server the model asks before it acts, and a model can choose not to ask. For Claude Code, add the [hook](https://immiscible.fly.dev/docs/guides/mcp-proxy.md#the-claude-code-hook), which Claude Code runs before every tool call whatever the model decides (`npx immiscible init` installs it). For other tools, put them behind the [MCP proxy](https://immiscible.fly.dev/docs/guides/mcp-proxy.md), which holds their credentials so the agent has no other way in. ## Which clients has this been tested with? The server, the Claude Code command, `.mcp.json` and the Claude Code plugin are tested against Claude Code 2.1, the Gemini extension with `gemini extensions validate`, and the Desktop bundle with the MCPB tools. Cursor's agent reads the Cursor entry (running it needs a Cursor account). The Codex, VS Code and Windsurf entries follow each client's documentation as of October 2026 and are not yet tested; if a client changes its format, `npx immiscible mcp` is where we fix it. --- # How do I block dangerous commands in Claude Code? > Use a PreToolUse hook, which Claude Code runs before every tool call whatever the model decides. Immiscible's hook sends each Bash command, file edit, web fetch and MCP call to a rule a person wrote and refuses, asks or allows; it fails closed. Source: https://immiscible.fly.dev/docs/answers/claude-code-block-commands Use a `PreToolUse` hook: Claude Code runs it before every tool call, and a hook that exits with code 2 blocks the call whatever the model wanted. Immiscible's hook sends each Bash command, file write, web fetch and MCP call to a rule a person wrote, then refuses it, asks you with the reasons and an approval link, or lets it through; if Immiscible cannot be reached, it refuses. ## How do I install it? ```bash npx immiscible init ``` In a Claude Code project (one with `.claude/` or `CLAUDE.md`), init shows the change to `.claude/settings.json`, asks, and adds one entry: ```json { "matcher": "Bash|Write|Edit|MultiEdit|NotebookEdit|WebFetch|mcp__(?!immiscible__(check_action_status|explain_decision|spend_summary|find_waste|unwatched_keys)$).*", "hooks": [ { "type": "command", "command": "node --env-file-if-exists=\"$CLAUDE_PROJECT_DIR/.env\" \"$CLAUDE_PROJECT_DIR/.claude/hooks/immiscible-claude-code-hook.mjs\" || exit 2", "timeout": 60 } ] } ``` Beside it, init adds Claude Code deny rules to the same file, in the same diff, so Claude Code itself refuses the worst commands and reading `.env`, before any hook runs: ```json "permissions": { "deny": ["Bash(rm -rf:*)", "Bash(rm -fr:*)", "Bash(sudo rm:*)", "Bash(git push --force:*)", "Bash(git push -f:*)", "Bash(git reset --hard:*)", "Bash(git clean -f:*)", "Read(./.env)", "Read(./.env.*)"] } ``` The matcher covers every Bash command, file change, web fetch and MCP call, except Immiscible's own read-only tools (asking Immiscible whether the agent may ask Immiscible would only loop). `|| exit 2` makes a missing file or a crash block the call instead of letting it through. Without the CLI: `npm install -g @immiscible/claude-code-hook` and `immiscible-claude-code-hook --print-config`. Check it with `npx immiscible doctor`, which also runs the hook against an address nothing answers on to prove it refuses. ## How do I decide which commands are allowed? Write a rule for the agent's tool calls that names the domains it may reach, such as `github.com`, `registry.npmjs.org` and your own. A shell command that posts to anywhere else is then refused (`recipient_not_allowed`), and an MCP server not named (`mcp:github`, or `mcp:*`) is refused too. In the console: **Agents**, **Agent limits**, **Add a rule**, an action rule for `tool.call` with those domains. See [mandates](https://immiscible.fly.dev/docs/concepts/mandates.md#action-mandates). ## How is this different from Claude Code's permissions deny list? Claude Code's own `permissions.deny` rules match tool names and patterns in your settings, and they are the right first layer: use them. The hook adds what a pattern cannot: a decision that knows where a command sends data, asks a named person and waits, counts bursts of calls, applies one rule across Claude Code and your other agents, can be frozen from the console or Slack, and leaves a signed record. The two work together; an `allow` from the hook still goes through Claude Code's own permissions. ## Can it stop rm -rf? Yes, in three layers. Claude Code's own deny rules, which init adds, refuse `rm -rf`, force pushes and reading `.env` before the hook runs. Then, under every Immiscible rule and at every standing, a destructive command (`rm`, `git push --force`, `git reset --hard`, `git clean`, a history rewrite, `curl ... | sh`, writing over `~/.bashrc` or `.git/hooks`) asks a person, and so do any `git push`, a deploy, a publish, a package install from the network, anything run with `sudo` and anything that names a path outside the project. A command too long to send whole, spelt in escape codes or built from a variable asks too. A secrets file or the environment leaving the machine is refused outright. Under the default rule, General tasks, an intern asks before anything that is not provably read-only; once the agent is past its intern stage, edits, test runs and builds inside its project go ahead (`localWrites: "allow-after-intern"`, shown on the agent's page, and an owner can set it to `ask`). A pattern can still miss a determined disguise, and a program the agent runs can delete files by itself. Run the agent where a mistake is recoverable (a container, a branch, a backup), and keep the hook for what crosses a boundary as well: network calls, MCP tools, payments and data. ## How do I apply it to a whole team? Commit `.claude/settings.json` and `.claude/hooks/immiscible-claude-code-hook.mjs` to the repository, and give each person their own agent key in `.env` (`npx immiscible init` per person). For a policy people cannot turn off, Claude Code's managed settings can carry the same hook entry. Every person's agent then shares one set of rules, one kill switch and one record in the console. ## What does it not do? - A new agent starts at the workspace's starting tier: intern, unless an owner has chosen junior in the console. Under the default rule, an intern asks a person before every tool call that changes something, except read-only calls (`ls`, `git status`, reading a file), which go ahead and are recorded. A junior agent edits files and runs tests and builds inside its project without asking; pushes, deploys, publishes, installs, destructive commands and anything outside the project still ask. The project is the one the hook names (`CLAUDE_PROJECT_DIR`, or the working directory); an older hook that does not send the project gets a person for its edits. See [autonomy tiers](https://immiscible.fly.dev/docs/concepts/autonomy-tiers.md) for how an agent earns the next rung. - It sees what Claude Code passes to the hook: the tool, its input and the session. It does not read the model's reasoning. - Claude Code's hosted inference is not enforced by the hook; to meter Claude Code's model spend, point it at the gateway with `ANTHROPIC_BASE_URL=https://immiscible.fly.dev/anthropic`. See [one budget across providers](https://immiscible.fly.dev/docs/ai-agents.md#one-budget-across-openai-and-anthropic). - The hook's tool calls are limited to 300 a minute per agent (`IMMISCIBLE_HOOK_RPM` on your own server), apart from the agent's other requests; past it the hook refuses, because it fails closed. Tool calls also have their own burst line, 200 in 10 minutes by default, after which a person is asked; see [how many tool calls before a person is asked](https://immiscible.fly.dev/docs/guides/mcp-proxy.md#how-many-tool-calls-before-a-person-is-asked). The full reference is [the Claude Code hook](https://immiscible.fly.dev/docs/guides/mcp-proxy.md#the-claude-code-hook). --- # How do I make Cursor's agent ask before risky tool calls? > Put the MCP tools Cursor's agent uses behind Immiscible's MCP proxy, and add Immiscible's MCP server so it can ask before paying or sharing data. Keep Cursor's own settings for its terminal commands; Immiscible's hook is for Claude Code. Source: https://immiscible.fly.dev/docs/answers/cursor-agent-safety Put the MCP tools Cursor's agent uses behind Immiscible's MCP proxy, which holds their credentials and decides each call, and add Immiscible's MCP server so the agent can ask before paying or sharing data. For Cursor's own terminal commands, use Cursor's own settings: Immiscible's command hook is built for Claude Code, not Cursor. ## How do I set it up? 1. Get an agent key: `npx immiscible init --yes` writes it to `.env`. 2. Put each tool server behind the proxy and point Cursor at it in `.cursor/mcp.json`: ```json { "mcpServers": { "github": { "url": "https://immiscible.fly.dev/mcp/proxy/mcu_6c1d0e", "headers": { "Authorization": "Bearer ${env:IMMISCIBLE_AGENT_KEY}" } } } } ``` Then remove Cursor's direct connection to the same tool. See [the MCP proxy](https://immiscible.fly.dev/docs/answers/mcp-tool-permissions.md). 3. Add Immiscible's own MCP server for payments and personal data: `npx immiscible mcp --client cursor` prints the entry and a one-click install link. See [add the MCP server](https://immiscible.fly.dev/docs/answers/add-the-mcp-server.md). ## What about commands in Cursor's terminal? Cursor decides those itself, with its own allow and deny settings for terminal commands. Use them. Immiscible does not see a command Cursor runs unless it passes through something Immiscible stands in front of, such as a proxied tool or the gateway. ## Can Immiscible govern Cursor's model spend? Not the inference Cursor runs on its own hosted models: that cannot pass through any gateway. That usage can be reconciled from the vendor's API and is marked as not governed. Model calls your own code or your own keys make can go through [the gateway](https://immiscible.fly.dev/docs/guides/gateway.md). ## What does it not do? - It does not install anything inside Cursor beyond MCP configuration, and it has no Cursor-specific hook. - A tool Cursor can still reach with its own credential is not governed. --- # How do I make an AI agent ask a person before it acts? > Route each consequential action through a decision that can answer "a person decides". Immiscible holds the request, asks the named person in the console, by email, in Slack or in Teams, and tells the agent once they have decided. Source: https://immiscible.fly.dev/docs/answers/human-approval-for-agents Send each consequential action (a payment, a data release, an email, a tool call that reaches another system) to a decision point before it runs, and let that decision answer "a person decides". With Immiscible the answer is `approval_required`: the agent waits, the person gets the request in the console, by email, or in Slack or Teams with Approve and Deny in the message, and the agent polls until it becomes `allow` or `deny`. ## How do I set it up? 1. **Connect the agent**: `npx immiscible init` writes its key to `.env`. See [the CLI](https://immiscible.fly.dev/docs/cli.md#init). 2. **Write the rule.** Rules say what an agent may do alone and when it must ask: above an amount, at a new merchant, before personal data leaves, before a tool reaches a domain not on its list. A new agent asks about everything that matters until it has earned more ([autonomy tiers](https://immiscible.fly.dev/docs/concepts/autonomy-tiers.md)). 3. **Guard the action.** The SDK asks, waits for the person, and runs your function only on allow: ```ts import { Immiscible, toolAction } from '@immiscible/sdk'; const immiscible = new Immiscible().run(); await immiscible.guard(toolAction('send_email', { to: 'client@acme.example' }, { domain: 'acme.example' }), () => sendEmail()); ``` ```python from immiscible import Immiscible, tool_action run = Immiscible().run() with run.guard(tool_action("send_email", {"to": "client@acme.example"}, domain="acme.example")): send_email() ``` Or over plain HTTP: `POST /v1/actions/authorize`, then poll `GET /v1/actions/:id`. The contract is on [for AI agents](https://immiscible.fly.dev/docs/ai-agents.md#decision-semantics). ## Can approvals happen in Slack or Microsoft Teams? Yes. Requests reach the approver in Slack or Teams with Approve and Deny in the message, and a decision there is the console's decision with every rule the console applies. Above a line you set, chat refuses to approve and sends the person to the console, where their own sign-in and two-factor stand behind the click. See [approvals in Slack and Teams](https://immiscible.fly.dev/docs/guides/approvals-in-chat.md). ## Is there an MCP server for approvals? Yes: `https://immiscible.fly.dev/mcp`. Its tools `request_payment`, `request_personal_data` and `authorize_action` ask before acting, `check_action_status` polls while a person decides, and `explain_decision` says why in plain English. Add it with one command; see [add the MCP server](https://immiscible.fly.dev/docs/answers/add-the-mcp-server.md). ## What does the agent do while it waits? Nothing consequential. It tells the person it acts for, with the approval link, and polls every five seconds for a minute, then every thirty. Retries use the same idempotency key, so a person is never asked twice for the same thing. A request nobody answers does not become an allow. ## What does it not do? - When the agent is the only one asking, asking is its choice. Pair it with a gate it cannot route around: the [Claude Code hook](https://immiscible.fly.dev/docs/guides/mcp-proxy.md#the-claude-code-hook), the [MCP proxy](https://immiscible.fly.dev/docs/guides/mcp-proxy.md) or the [card rail](https://immiscible.fly.dev/docs/guides/card-rail.md). - It does not judge whether the work was good; it decides whether the action may happen, and records it. For framework-specific wiring, see [approvals in the OpenAI Agents SDK, LangGraph and the Vercel AI SDK](https://immiscible.fly.dev/docs/answers/framework-tool-approvals.md). --- # How do I add approval to tool calls in the OpenAI Agents SDK, LangGraph or the Vercel AI SDK? > Wrap the framework's tools with Immiscible's guard. Each tool keeps its name and schema, asks before it runs, waits for a person when the rule says so, and returns a refusal to the model as the tool's result. Source: https://immiscible.fly.dev/docs/answers/framework-tool-approvals Wrap the framework's tools with Immiscible's guard: each tool keeps its name, description and schema, asks Immiscible before it runs, waits for a person when the rule says so, and settles afterwards. A refusal comes back to the model as the tool's result with plain-English reasons, so the agent tells its person instead of trying another way. ## OpenAI Agents SDK ```bash npm install @immiscible/sdk @openai/agents ``` ```ts import { Agent, run } from '@openai/agents'; import { Immiscible } from '@immiscible/sdk'; import { guardOpenAITools } from '@immiscible/sdk/openai-agents'; const immiscible = new Immiscible().run(); const agent = new Agent({ name: 'Buyer', tools: guardOpenAITools([buy], { client: immiscible, mapToAction: ({ args }) => Immiscible.paymentAction({ amount: args.pence, currency: 'GBP', merchant: args.domain, provenance: [{ source: 'user' }] }), }), }); await run(agent, 'Renew the team licence at vendor.example.'); ``` The tool call id is the idempotency key, so a retried call is the same action. Python uses `guard_tools` from `immiscible.integrations`. Every option: [the OpenAI Agents SDK guide](https://immiscible.fly.dev/docs/sdks/integrations/openai-agents.md). ## LangChain and LangGraph ```python from immiscible import Immiscible from immiscible.integrations import guard_langchain_tools immiscible = Immiscible().run(client="langchain") tools = guard_langchain_tools( [buy, search], client=immiscible, map_to_action=lambda call: None if call.name == "search" else Immiscible.payment_action(amount=call.args["pence"], currency="GBP", merchant=call.args["domain"]), ) ``` The guarded tools drop into `ToolNode`, `bind_tools` and the prebuilt agents unchanged; `None` from the mapper means "no decision needed". TypeScript uses `guardLangChainTools` from `@immiscible/sdk/langchain`. See [LangChain and LangGraph](https://immiscible.fly.dev/docs/sdks/integrations/langchain.md). ## Vercel AI SDK ```ts import { Immiscible, toolAction } from '@immiscible/sdk'; import { guardAiTools } from '@immiscible/sdk/ai'; const immiscible = new Immiscible().run({ client: 'vercel-ai' }); const tools = guardAiTools({ deploy }, { client: immiscible, mapToAction: ({ name, args }) => toolAction(name, args, { domain: 'mycompany.com' }), }); ``` A refusal is returned as the tool's result, and the generation's abort signal also aborts a wait for approval. See [the Vercel AI SDK](https://immiscible.fly.dev/docs/sdks/integrations/vercel-ai.md). ## Why not use the framework's own approval feature? Use it where it is enough. The OpenAI Agents SDK's tool approvals and LangGraph's `interrupt()` pause a run inside one framework for whoever is watching it. Immiscible adds a rule a person wrote outside the code, a named approver reached in the console, Slack or Teams, the same rules and kill switch for every agent whatever its framework, and a signed record of who decided what. The two combine: a framework interrupt can wait on an Immiscible decision. ## What does it not do? - It guards the tools you wrap. A tool the agent can reach unwrapped, or a credential it holds directly, is not governed; for those use the [MCP proxy](https://immiscible.fly.dev/docs/guides/mcp-proxy.md). - `mapToAction` decides what the action is (a payment, a data release, a tool call); a mapper that calls a payment a lookup gets a lookup's rule. --- # How do I allow or deny MCP tool calls by policy? > Put the MCP server behind a proxy that holds its credential and decides each tools/call against a rule. Immiscible's MCP proxy lists only the tools a rule covers, refuses or asks about the rest, and records every call as a digest. Source: https://immiscible.fly.dev/docs/answers/mcp-tool-permissions Put the tool server behind a proxy that holds its credential and decides each `tools/call` against a rule, so the agent has no direct way to the tool. Immiscible's MCP proxy does that: a tool no rule covers is left out of `tools/list` and refused if called anyway, a tool mapped as a payment or an email is judged as one, and every call is recorded as SHA-256 digests in a signed ledger. ## How do I set it up? 1. **Register the upstream** (owners and admins, once per workspace) with its URL, its credential and the tools to allow. The credential is sealed and never returned. See [register the upstream](https://immiscible.fly.dev/docs/guides/mcp-proxy.md#1-register-the-upstream). 2. **Write the rule.** A proxied call is a `tool.call` whose target is the upstream's host; a tool map gives tools their meaning: ```json { "delete_repo": { "type": "repo.delete" }, "send_email": { "type": "email.send", "targetPath": "to" }, "create_payment": { "type": "payment", "amountPath": "amount", "amountUnit": "minor", "currency": "GBP", "merchantPath": "merchant" } } ``` 3. **Point the agent at the proxy** instead of the tool, and remove its direct connection: ```bash claude mcp add --transport http github https://immiscible.fly.dev/mcp/proxy/mcu_6c1d0e \ --header "Authorization: Bearer $IMMISCIBLE_AGENT_KEY" ``` The same address goes in `.cursor/mcp.json` or any MCP client's config. The full walk-through is [the MCP proxy](https://immiscible.fly.dev/docs/guides/mcp-proxy.md). ## What happens when a call needs approval? The proxy answers with an approval link and the agent waits; calling again with the same arguments returns the same answer and does not ask twice. Once a person approves, the call is forwarded once, never twice. See [what the agent sees on approval](https://immiscible.fly.dev/docs/guides/mcp-proxy.md#what-the-agent-sees-on-approval). ## Is this an MCP gateway with an audit log? Yes, for the decision and the record. Each proxied call is one record with digests of the arguments and result (never the credential, argument values or tool output), in a hash-chained ledger anyone can verify offline, exportable to a SIEM as OCSF or OpenTelemetry. It is not a tool catalogue or a hosting platform for MCP servers: it fronts servers you already run or use. ## What if a tool server changes its tools? New or changed tools are hidden until an owner accepts the new manifest, so a server cannot add a tool the rule never saw. Tool descriptions still come from the upstream and reach the model unchanged, so allow only upstreams you trust to describe their own tools. ## What does it not do? - It governs the tools behind it. A tool the agent can still reach with its own credential is not governed. - It does not run local (stdio) MCP servers; it fronts remote servers over HTTPS. For Claude Code's local tools, use [the hook](https://immiscible.fly.dev/docs/answers/claude-code-block-commands.md). - Private, loopback and internal addresses are refused as upstreams, as written and as resolved. --- # How do I stop an AI agent sending data where it should not? > Decide every outbound action by where it goes, and keep personal data out of the agent's hands until a release is allowed. Immiscible refuses destinations a rule does not name, releases personal data from a vault only to allowed recipients, and judges actions on what the agent has read. Source: https://immiscible.fly.dev/docs/answers/agent-data-exfiltration Decide every outbound action by its destination, and keep personal data out of the agent until a release is allowed. With Immiscible, a rule lists the domains an agent may reach and the recipients it may give personal data to; anything else is refused (`recipient_not_allowed`) or asked about, and once the agent has read untrusted content (an email, a web page, a tool's output) its next outbound action is judged with that in mind. ## How do I set it up? 1. **Name the destinations.** An action rule with `domains` such as `github.com` and your own; with a list, everywhere else is closed, or set `newDomain: approve` to ask instead. See [action mandates](https://immiscible.fly.dev/docs/concepts/mandates.md#action-mandates). 2. **Keep personal data in the vault.** The agent asks with `request_personal_data` (MCP) or `requestData` (SDK) naming the fields, the recipient and the purpose; on allow the values come back once, for that recipient. Restricted fields (passport, national ID, bank account, card, health) always need a person unless the rule names that field and that recipient. See [data mandates and the vault](https://immiscible.fly.dev/docs/concepts/mandates.md#data-mandates-and-the-vault). 3. **Gate the routes data can leave by**: the [Claude Code hook](https://immiscible.fly.dev/docs/answers/claude-code-block-commands.md) for shell commands and web requests, the [MCP proxy](https://immiscible.fly.dev/docs/answers/mcp-tool-permissions.md) for tools, and the [gateway](https://immiscible.fly.dev/docs/guides/gateway.md) for model traffic, which records what entered the agent's context. ## What is least privilege for an AI agent? The agent holds only an agent key, which can ask but never approve, widen a rule or lift a freeze. Its authority is a set of written rules, each naming what it may do and where; with no rule for an action, the answer is `deny`. It starts as an intern and earns more on evidence, with sign-off from people who carry the risk; see [autonomy tiers](https://immiscible.fly.dev/docs/concepts/autonomy-tiers.md). ## Is this a policy engine for agent authorisation? In effect, yes, with the parts an agent needs around the policy: a person asked at the right moment, a kill switch, signed receipts and a ledger. The rules are signed objects written for people to read, not a policy language you program; if you already run a general authorisation engine for your application, Immiscible sits beside it for what agents do. ## What does it not do? - It is not data loss prevention: it does not scan file contents or network traffic. It decides the actions it is asked about and the routes it stands in front of. - It cannot see a channel the agent has without it, such as a credential in its environment. Take those away. - Provenance an agent declares is the agent's word. The gateway and the proxy also observe what entered the session, and when the two disagree a person decides (`provenance_mismatch`); without the gateway or the proxy, only the declared word is there. --- # How do I control LLM costs per team? > Send model traffic through a gateway that checks a budget before each call. Immiscible's gateway takes OpenAI-shaped and Anthropic-shaped requests on your own provider keys, and holds monthly budgets per team, person or workspace that nudge, downgrade, ask the owner and then stop. Source: https://immiscible.fly.dev/docs/answers/llm-costs-per-team Send model traffic through a gateway that checks a budget before each call, and give each team its own budget. Immiscible's gateway takes OpenAI-shaped and Anthropic-shaped requests on your own provider keys, records cost per request, task, person and team, and holds monthly budgets that nudge near the limit, route to cheaper eligible models, ask the budget's owner at the allocation, and refuse past a hard ceiling. ## How do I set it up? 1. **Connect the providers**: **Settings**, **Connections**, paste your OpenAI and Anthropic keys. Traffic runs on your own contracts; Immiscible never resells inference. 2. **Change one base URL** in each client, using an Immiscible key instead of the provider's: ```bash export OPENAI_BASE_URL=https://immiscible.fly.dev/v1 # the OpenAI SDK and compatible tools export ANTHROPIC_BASE_URL=https://immiscible.fly.dev/anthropic # Claude Code and the Anthropic SDK ``` 3. **Set a budget for each team.** In the console, or with an admin-scoped gateway key. Amounts are millionths of a US dollar, so `500000000` is $500: ```bash curl -X POST "https://immiscible.fly.dev/v1/admin/budgets" \ -H "authorization: Bearer $IMMISCIBLE_ADMIN_KEY" -H "content-type: application/json" \ -d '{ "scope": "team", "scopeId": "'"$TEAM_ID"'", "baseAllocation": 500000000, "hardCeiling": 600000000, "ownerId": "platform-lead@example.com" }' ``` `scope` is `team`, `principal` (one person) or `org` (the workspace); `scopeId` names the team or person, and `ownerId` is who is asked at the allocation. 4. **Run in shadow mode first.** Every workspace starts there: nothing is blocked, not even an exhausted budget. After a week, read **Assessment**, then switch to **Enforce**. See [shadow mode first](https://immiscible.fly.dev/docs/guides/gateway.md#shadow-mode-first). ## What happens when a team reaches its budget? | Where the team is | What the gateway does | |---|---| | approaching the allocation | adds an `x-immiscible-advisory` header | | near the allocation | routes to cheaper eligible models | | at the allocation | `402 approval_required`, naming the budget's owner | | past the hard ceiling | `429 budget_exhausted` | Each request's estimate is reserved before the call, so two concurrent requests cannot both take the last pound, and settlement uses the provider's reported usage. See [budgets](https://immiscible.fly.dev/docs/guides/gateway.md#budgets). ## Can I set a budget per user or per API key? Per person, yes: a `principal` budget. Budgets attach to a team, a person or the workspace rather than to a key. Your provider's own project limits are worth keeping as well, as a backstop at the provider. ## How do I find spend that does not go through the gateway? [Discovery](https://immiscible.fly.dev/docs/guides/discovery.md) reads the OpenAI, Anthropic and OpenRouter admin APIs for keys and projects used outside the gateway, with their owners and spend, and brings each under control in one click or proposes revoking it (two owners). [Finance dashboards](https://immiscible.fly.dev/docs/guides/finance-dashboards.md) put spend by team, provider and agent where finance already looks. ## What does it not do? - Inference that runs in a vendor's own backend (GitHub Copilot, Cursor's hosted models, Devin) cannot pass through any gateway; it is reconciled from the vendor's API and marked as not governed. See [what the gateway cannot see](https://immiscible.fly.dev/docs/guides/gateway.md#what-the-gateway-cannot-see). - It is not a model host or reseller, and it does not score answer quality. - If you only need routing, caching and cost reports, a dedicated LLM gateway may be all you need; see [compare](https://immiscible.fly.dev/docs/compare.md#ai-gateways). Immiscible can also route through [OpenRouter](https://immiscible.fly.dev/docs/guides/openrouter.md). --- # How do I control what an AI agent pays with x402 or a crypto wallet? > Decide each payment before the wallet signs. Immiscible's SDK reads the HTTP 402, asks with the payment requirements, and calls your x402 signer only on allow; for wallets it decides first and your wallet signs after. It never holds keys. Source: https://immiscible.fly.dev/docs/answers/x402-and-wallet-payments Decide each payment before the wallet signs, against a rule with limits in your own currency. Immiscible's SDK reads an HTTP `402`, asks with the payment requirements, and calls your x402 signer only on `allow`; for any wallet, `decideThenSign` asks first and calls your signing function only after an allow whose signed receipt covers the exact transfer. ## How do I set it up for x402? ```bash npm install @immiscible/sdk npx immiscible init --purpose buys_software ``` ```ts import { Immiscible, x402Fetch } from '@immiscible/sdk'; const pay = x402Fetch(new Immiscible(), { pay: ({ requirements, paymentRequired }) => myX402Client.createPaymentHeader(requirements, paymentRequired), // called only on allow provenance: [{ source: 'user', detail: 'the analyst asked for this report' }], }); const res = await pay('https://api.data-vendor.example/v1/quotes'); ``` x402 versions 1 and 2 are handled. See [x402 payments](https://immiscible.fly.dev/docs/guides/x402.md). ## How do I set it up for a wallet? ```ts import { Immiscible, decideThenSign } from '@immiscible/sdk'; await decideThenSign(new Immiscible(), { asset: 'USDC', network: 'base', amount: '12.50', recipient: '0x...' }, async () => ({ txHash: await wallet.send() })); ``` Amounts are decimal strings, priced at the rate of the moment against limits kept in pounds; lookalike addresses are stopped. Guides exist for [Fireblocks](https://immiscible.fly.dev/docs/guides/fireblocks.md) (the Co-Signer callback), [Turnkey](https://immiscible.fly.dev/docs/guides/turnkey.md), [Privy](https://immiscible.fly.dev/docs/guides/privy.md), [Circle](https://immiscible.fly.dev/docs/guides/circle.md) and [Coinbase CDP](https://immiscible.fly.dev/docs/guides/coinbase-cdp.md). See [crypto payments](https://immiscible.fly.dev/docs/guides/crypto-payments.md). ## How does this relate to agent payment schemes from card networks and payment companies? Those schemes move the money and carry their own controls. Immiscible is the decision before them, held by you: the same rule, approval and record whether the agent pays by x402, a wallet or a card. For cards, the [card rail](https://immiscible.fly.dev/docs/guides/card-rail.md) has the issuer ask before money moves. No card network or payment company endorses or partners with Immiscible; these are integrations built against public interfaces. ## What does it not do? - It never holds keys or signs; your signer or wallet does, after an allow. - A wallet policy engine at the custodian is still worth keeping for transfers people make by hand; see [compare](https://immiscible.fly.dev/docs/compare.md#wallet-policy-engines). - An agent holding a wallet key directly can sign without asking. Keep the key in a wallet service that asks first. --- # How do I stop all my AI agents at once? > Freeze them where every request they make is decided. A stopped agent in Immiscible is refused on every path it enforces, its actions, model calls, proxied tool calls and card authorisations, and only a person can start it again. Source: https://immiscible.fly.dev/docs/answers/kill-switch-for-ai-agents Freeze the agents at the point every request they make is decided, so one switch stops their actions and their model traffic together. In Immiscible a stopped agent is refused from that moment on every path Immiscible enforces, before anything goes upstream, and requests waiting for approval can no longer be approved. ## What exactly stops? | Path | A stopped agent gets | |---|---| | Action requests, and the MCP server at `/mcp` | a `deny` decision (`agent_frozen`) | | Model calls through the gateway, OpenAI and Anthropic shapes, and Anthropic token counts | `403 agent_stopped`; nothing reaches the provider | | Tool calls and tool listing through the MCP proxy, to MCP servers and plain HTTP APIs | `403`, JSON-RPC error `-32003`, `agent_stopped`; nothing reaches the tool | | Card authorisations | declined | | Its receipts and OAuth connections | revoked | The error names the hold and who may lift it, and every refusal is on the evidence ledger. A restart works on the next request, with nothing to restart. The [kill switch guide](https://immiscible.fly.dev/docs/guides/kill-switch.md#what-a-stop-stops) has the detail. ## What about keys that are not an agent's? Stopping one agent leaves a person's gateway key alone. **Stop every agent** (a fleet freeze of `all`) refuses model calls on every key in the workspace too, a person's, a service's and an admin's, because the gateway cannot tell a coding agent on a person's key from the person. It stands until the stop is lifted. ## How do I stop one agent? In the console, **Agents**, **Freeze**, or the link in any approval, or from Slack with `/immiscible freeze `. Name a **kill owner** for every agent that matters: the person who may always stop it, whatever their role. ## How do I stop a whole fleet? With a selector (`all`, `agentIds`, `principalId`, `vendor`, `tier` or `upstreamId`), looking first: ```bash curl -X POST "https://immiscible.fly.dev/v1/admin/freeze" \ -H "authorization: Bearer $IMMISCIBLE_SERVICE_TOKEN" -H "content-type: application/json" \ -d '{ "selector": { "all": true }, "dryRun": true }' # then the same with "reason", "hold" and "confirmCount" set to the count it returned ``` A SOAR playbook can do the same with a service token, and your identity provider can trigger a freeze through Shared Signals when the person an agent acts for is disabled. See [the kill switch](https://immiscible.fly.dev/docs/guides/kill-switch.md). ## Who can start it again? Only a person, in the console, with a reason. Nothing an agent key can do unfreezes an agent. Every freeze and lift, with who and why, is in the agent's history and the evidence ledger. ## What does it not do? - It stops what passes through Immiscible. An agent with a direct credential to a tool, a card or a provider keeps that route; take direct credentials away. - Machines have hourly freeze ceilings, so a looping playbook cannot stop the whole company; a freeze past the ceiling waits for a person. --- # What logging do AI agents need for the EU AI Act? > For a high-risk AI system, Article 12 asks for automatic recording of events over its lifetime, and Articles 19 and 26(6) ask providers and deployers to keep those logs for at least six months. Immiscible records every agent decision automatically in a signed, hash-chained ledger you can export and verify; it supports a compliance file, it does not make anyone compliant. Source: https://immiscible.fly.dev/docs/answers/eu-ai-act-logging For a high-risk AI system, Article 12 of the EU AI Act asks that the system automatically record events over its lifetime, so that risks can be identified and its operation monitored, and Articles 19 and 26(6) ask providers and deployers to keep those logs for at least six months unless other law says otherwise. Immiscible records every agent decision, approval, freeze and change of authority automatically, in a hash-chained ledger whose head is signed, which you can export and verify offline; it is evidence your compliance file can rely on, not compliance itself. This page is not legal advice. Whether an agent is part of a high-risk system depends on what it is used for (Annex III), and the application dates for high-risk obligations were moved by the EU's Digital Omnibus (to December 2027 for the Annex III uses, at the time of writing). Check the current position with whoever owns your compliance. ## What does Immiscible record? Each decision with the agent, the person it acts for, the rule it was judged under, the reasons and signals, who approved and when; every freeze and lift; every change to a rule or an agent's tier; card authorisations; and per-request gateway records (model, cost, data region, budget, routing rule). Prompts and answers are kept as digests by default, not text. See [what is recorded](https://immiscible.fly.dev/docs/concepts/evidence.md#what-is-recorded). ## How do I get the logs out? ```bash # every record, checkpoint and key, as one JSON document (a service token with evidence:read) curl "https://immiscible.fly.dev/api/w/$WORKSPACE/evidence/bundle" -H "authorization: Bearer $IMMISCIBLE_SERVICE_TOKEN" -o bundle.json # check it offline: exit 0 means every record and checkpoint verified node scripts/verify-evidence.mjs bundle.json --keys keys-you-kept.json ``` The verifier and the format are in the [evidence specification](https://immiscible.fly.dev/docs/security/evidence-spec.md). The bundle lists the record-keeping obligations it supports (for example Art. 12 and Art. 26(6)) as references for whoever writes the file. It can also go to your SIEM as OCSF or OpenTelemetry ([SIEM export](https://immiscible.fly.dev/docs/guides/siem-export.md)), and the gateway's evidence pack for a period and risk tier is at `GET /v1/evidence/pack`. See [getting it out](https://immiscible.fly.dev/docs/concepts/evidence.md#getting-it-out). ## What makes the audit trail tamper-evident? Each record carries the hash of the one before it, and the head of the chain is signed with Ed25519 at intervals. Rewriting or deleting a record breaks the chain at that point, and the verifier names the first record that broke. Keep the public keys, or the signed checkpoints, somewhere the operator cannot reach: a bundle on its own proves only that it is consistent with itself. See [evidence](https://immiscible.fly.dev/docs/concepts/evidence.md). ## What are signed receipts for agent actions? Every `allow` carries a short Ed25519-signed token naming the agent, the action, the rule and whether a person approved it. A merchant, an auditor or anyone else can check it with `POST /v1/verify`, with no account, or offline against the published keys. A receipt proves one action was allowed; settling the action records that it happened. See [receipts](https://immiscible.fly.dev/docs/concepts/receipts.md). ## Does it help with human oversight? Article 14 asks that high-risk systems can be overseen by people, including stopping them. Approvals by a named person before consequential actions, the [kill switch](https://immiscible.fly.dev/docs/answers/kill-switch-for-ai-agents.md) and autonomy that is earned on evidence are built for that, and each is recorded. Whether they meet your obligations is for your compliance assessment. ## What does it not do? - It does not certify anything or make anyone compliant. No regulator or notified body has assessed it. - It records what passes through it. Agent activity that never reaches Immiscible is not in the ledger. - Retention is set per workspace within the plan; set it to at least what your obligations require. --- # What kinds of tool control what AI agents spend and do? > Seven kinds, each good at something different: the controls built into the agent, LLM gateways, MCP gateways and proxies, guardrail libraries, card and payment controls, wallet policy engines, and an independent control layer such as Immiscible. Which to use depends on what the agent can do and who needs to see the record. Source: https://immiscible.fly.dev/docs/answers/tools-for-controlling-ai-agents Seven kinds of tool do this, and most teams use more than one: the controls built into the agent itself, LLM gateways, MCP gateways and proxies, guardrail libraries, card and payment controls, wallet policy engines, and an independent control layer such as Immiscible. Pick by what the agent can do (spend, share data, call tools, run up model bills) and by who needs the rules and the record: one team, or finance, security and an auditor across every agent. This page compares categories, not products. Products vary, and many combine more than one category. The longer version, with a table, is [how Immiscible compares](https://immiscible.fly.dev/docs/compare.md). ## The controls built into the agent Claude Code's permission rules and hooks, the OpenAI Agents SDK's tool approvals, LangGraph's interrupts, Cursor's terminal settings, and providers' own project spend limits. **Best when** one team runs one agent and the person watching it is the person approving. **Start here; keep them on.** They are configured by whoever built the agent, and each covers its own agent. ## LLM gateways One endpoint in front of model providers, for routing, failover, caching, rate limits, cost tracking and budgets. **Best when** the question is model spend and reliability and agents take no consequential actions. A gateway sees the prompt, not the payment the agent makes next. ## MCP gateways and proxies One front door for the tool servers agents use, with authentication, tool allowlists and logs. **Best when** the risk is in which tools an agent can call. Most decide by tool name; deciding by what a call does (a payment of this amount, an email to this domain) needs a tool map and a rule. ## Guardrail libraries Checks on what goes into and comes out of a model: prompt injection detection, content filters, output validation. **Best when** the risk is in the text. They run inside the application and do not decide whether an action may happen or ask a person. ## Card and payment controls Limits on a card or account: amounts, merchant categories, single-use cards, freezes. **Best when** people hold the cards, or an agent pays only within fixed limits nobody needs to approve one by one. They see a card and a merchant, not the agent, its rule or what it read. ## Wallet policy engines Rules at the custodian or signer: allowed addresses, amounts, networks, quorums. **Best when** a treasury team moves funds by hand. They govern one wallet's transfers. ## An independent control layer A decision before every consequential act and model request, against rules a person wrote, held outside every agent: allow with a signed receipt, ask a named person, or refuse, with one kill switch and one verifiable record across agents from any vendor. This is what Immiscible is. **Best when** agents pay, share personal data or act on other systems, more than one agent or vendor is involved, or someone outside the team (finance, security, an auditor, a regulator) needs the rules and the record. It works alongside the six above: its gateway can route through OpenRouter, its card rail sits behind the issuer, and its SDKs wrap framework tools. ## Which should I start with? - One coding agent, one person: its built-in permissions, then [the Claude Code hook](https://immiscible.fly.dev/docs/answers/claude-code-block-commands.md) when its actions reach other systems. - Model bills across teams: a gateway with budgets; see [LLM costs per team](https://immiscible.fly.dev/docs/answers/llm-costs-per-team.md). - Agents that pay or share data: a decision with a person in it; see [stop an agent spending without approval](https://immiscible.fly.dev/docs/answers/stop-an-agent-spending.md). - Evidence for an auditor: a signed, exportable record; see [EU AI Act logging](https://immiscible.fly.dev/docs/answers/eu-ai-act-logging.md). ## When is Immiscible not the answer? When you need a model host or reseller of inference, evaluations or answer-quality scoring, or control over inference that runs inside a vendor's own backend, which can be reconciled but not enforced. See [where Immiscible is not the answer](https://immiscible.fly.dev/docs/compare.md#where-immiscible-is-not-the-answer). --- # API endpoints The decision API. Every other route is in https://immiscible.fly.dev/llms-full/api.txt, and each has curl, Node and Python examples at its own address plus .md. ## Authorise an action Source: https://immiscible.fly.dev/docs/api/post-v1-actions-authorize `POST /v1/actions/authorize`. Authentication: Agent key. Ask whether this agent may take a consequential action. The answer is a [decision](https://immiscible.fly.dev/docs/concepts/decisions.md): `allow` with a signed receipt, `approval_required` with an approval to wait on, or `deny` with reasons. A refusal is a `200`, not an error. Send `idempotencyKey` in the body (or the `idempotency-key` header) and a retry gets the same decision; the same key with a different body is `409 idempotency_conflict`. Limited per agent per minute (`IMMISCIBLE_AGENT_RPM`, default 60). Tool calls from the Claude Code hook (`type: tool.call` with `session.client: claude-code`) count in a separate bucket, 300 a minute per agent by default (`IMMISCIBLE_HOOK_RPM`), because the hook asks before every Bash, Edit and WebFetch call and refuses when it is limited. ### Body | Field | Type | Required | Description | |---|---|---|---| | `type` | string | yes | `payment`, `data.release`, `email.send`, `calendar.write`, `account.change`, `tool.call` or your own dotted type; an undotted word that is not built in is refused with a suggestion | | `summary` | string | yes | one sentence a person can read: what and why (at most 500 characters) | | `payment` | object | for payments | `amount` (whole minor units), `currency` (ISO 4217), `merchant` (`name`, `domain`, `category`, `mcc`) | | `data` | object | for data releases | `fields`, `recipient` (a domain), `purpose` | | `target` | object | no | `domain` or `recipient` the action reaches | | `provenance` | array | no | `{ source, detail }` with `source` one of `user`, `agent`, `web`, `email`, `document`, `tool`. Missing means untrusted | | `volume` | object | no | `{ records }` for actions that move data | | `session` | object | no | `{ client, id }`: the inference session, for [observed provenance](https://immiscible.fly.dev/docs/guides/gateway.md#observed-provenance) | | `idempotencyKey` | string | recommended | 1 to 128 characters | A field that is plainly a mistake is refused before anything is decided or recorded: a `400 invalid_request` names it in `param` and `errors`, with `didYouMean` (`summry` gives `summary`; a top-level `marchant` gives `payment.merchant`). Any other field not in this table is ignored, never silently: the decision carries `warnings`, one `{ type: "unknown_field", field, message }` per field. This is the finance agent from [the proof page](https://immiscible.fly.dev/proof): the payee is on its allow list and the amount inside its monthly limit, but an email shaped the request, so a person decides. An allowed payment returns `allow` with a signed `receipt` in the same shape. ## Retrieve an action Source: https://immiscible.fly.dev/docs/api/get-v1-actions-id `GET /v1/actions/:id`. Authentication: Agent key. The current state of an action this agent asked about. After `approval_required`, poll this: it becomes `allow` (with a receipt whose `hum` claim is `true`) when a person approves, or `deny` when they refuse or the deadline passes. Poll every five seconds for the first minute, then every thirty. ## Explain a decision Source: https://immiscible.fly.dev/docs/api/get-v1-actions-id-explain `GET /v1/actions/:id/explain`. Authentication: Agent key. The decision on one of this agent's actions in plain English: what was asked, the rule it was judged under, each reason and risk signal with its label, and what the agent should do next. Read only and safe to call at any time: unlike polling, it never hands over released data, and it never carries the receipt. The MCP tool `explain_decision` returns the same. ## Settle an action Source: https://immiscible.fly.dev/docs/api/post-v1-actions-id-settle `POST /v1/actions/:id/settle`. Authentication: Agent key. Report what actually happened after an allowed action, so the ledger holds the outcome and not only the permission. Settling above the authorised amount is recorded as an incident and alerts the owner. With the [card rail](https://immiscible.fly.dev/docs/guides/card-rail.md), the issuer's clearing settles card payments instead. | Field | Type | Required | Description | |---|---|---|---| | `status` | string | yes | `completed`, `failed` or `cancelled` | | `amount` | integer | for payments | the final amount in minor units | ## Verify a receipt Source: https://immiscible.fly.dev/docs/api/post-v1-verify `POST /v1/verify`. Authentication: Public. Rate limited per address. A merchant needs no account. Check a receipt online. Free, with no account: built for merchants and payment providers. Online verification marks the receipt as seen, so a second check returns `"replayed": true`. To check offline instead, use the [JWKS](https://immiscible.fly.dev/docs/api/get-well-known-immiscible-keys-json.md) and [this verifier](https://immiscible.fly.dev/docs/security/verifying-offline.md#receipts). ## Signing keys (JWKS) Source: https://immiscible.fly.dev/docs/api/get-well-known-immiscible-keys-json `GET /.well-known/immiscible-keys.json`. Authentication: Public. Every public key that signs receipts, checkpoints, profiles and reports, as a JWKS. Cacheable for five minutes; refetch when you meet an unknown `kid`. Keep a copy when you start relying on Immiscible: see [verifying offline](https://immiscible.fly.dev/docs/security/verifying-offline.md). ## Download the Claude Code hook Source: https://immiscible.fly.dev/docs/api/get-downloads-claude-code-hook-mjs `GET /downloads/claude-code-hook.mjs`. Authentication: Public. The `PreToolUse` hook script: one file, no dependencies, Node 22. See [the Claude Code hook](https://immiscible.fly.dev/docs/guides/mcp-proxy.md#the-claude-code-hook). ## OpenAPI (3.0) Source: https://immiscible.fly.dev/docs/api/get-downloads-immiscible-openapi-3-0-json `GET /downloads/immiscible-openapi-3.0.json`. Authentication: Public. ## Power Platform connector Source: https://immiscible.fly.dev/docs/api/get-downloads-immiscible-power-platform-swagger-json `GET /downloads/immiscible-power-platform.swagger.json`. Authentication: Public. ## n8n workflow Source: https://immiscible.fly.dev/docs/api/get-downloads-n8n-ask-immiscible-json `GET /downloads/n8n-ask-immiscible.json`. Authentication: Public. ## Create callback Source: https://immiscible.fly.dev/docs/api/post-v1-actions-id-callback `POST /v1/actions/:id/callback`. Authentication: Agent key. --- # Every other section - [Spend](https://immiscible.fly.dev/llms-full/spend.txt): 8 pages: Route model traffic through the gateway, Route through OpenRouter, Find shadow agents with discovery, The agent inventory, and more - [Approvals](https://immiscible.fly.dev/llms-full/approvals.txt): 9 pages: Govern Claude Code and Cursor, Approvals in Slack and Teams, Personal workspaces, Agents without an API, such as Instinct, and more - [Identity](https://immiscible.fly.dev/llms-full/identity.txt): 3 pages: Identity and access profiles, Security administration, Shared Signals from Okta or Entra - [Integrations](https://immiscible.fly.dev/llms-full/integrations.txt): 19 pages: The Claude Code fleet pack, Setting up Slack, Setting up Microsoft Teams, Hooks for Codex, Cursor, Windsurf and Gemini CLI, and more - [Agent payments](https://immiscible.fly.dev/llms-full/agent-payments.txt): 10 pages: The card rail with Stripe Issuing, Crypto payments, Reconcile Ramp charges against receipts, Rates, and the "never guessed" rule, and more - [Deploy](https://immiscible.fly.dev/llms-full/deploy.txt): 1 page: Deploy and backups - [SDKs](https://immiscible.fly.dev/llms-full/sdks.txt): 8 pages: SDKs, Immiscible SDKs, Claude Code and the Claude Agent SDK, LangChain and LangGraph, and more - [Security](https://immiscible.fly.dev/llms-full/security.txt): 4 pages: Evidence specification, Verifying offline, Threat model, Security reviews - [Changelog](https://immiscible.fly.dev/llms-full/changelog.txt): 1 page: Changelog - [API reference](https://immiscible.fly.dev/llms-full/api.txt): every route (555), with what each does and how it authenticates