Approvals
Write in plain English what needs your sign-off. An action you said to check pauses, asks you, and runs only once you say yes.
Approvals put a human in front of the actions that need one. You write a policy in plain English — no rule builder, no syntax — and every write your agents make is judged against it. Most calls are allowed and nothing changes. The ones your policy says to check stop, and you get a card.
Anything your policy never mentioned is decided by a default behavior you choose — and on the default setting an ordinary write simply runs. That is covered below, and it is the part worth reading before you rely on this.
Approvals are off until you turn them on. A new organization has no policy and no gate; nothing is judged and nothing pauses.
The control surface is Dashboard → Approvals.
Three verdicts, and nothing else
Every gated call gets exactly one of them:
| Verdict | What the agent sees | What you see |
|---|---|---|
| Allow | its result, immediately | nothing — it is in the activity log |
| Ask | the call is held; it is told a human is deciding | a card, and nothing has run yet |
| Deny | a refusal, at once — nothing ran and nobody was asked | no card — it is in the activity log |
Reads are never gated. A tool that only looks at something — a search, a listing, reading a message — goes straight through, which is what keeps the gate usable: your agent's ordinary work is not interrupted to confirm that looking is allowed.
Writing the policy
Write sentences, not rules. The whole text is read, in context, for every judged call:
Sam handles support. Replies to customers who have written to us first are
fine. Ask me before anything goes to a public channel or an external company,
and before any refund over $100. Never post on our behalf about pricing,
funding, or anything that reads like a product promise.Two things follow from it being TEXT rather than rules.
Nothing is compiled. Your words stay your words. A decision made last week stays explainable by the text that was active last week — every save is a new version, and the history is on the policy page.
You can test a sentence before relying on it. Test policy runs a hypothetical call against your saved text, through the same evaluation the gate uses, so you can see what a new sentence changes without waiting for an agent to make that call.
Your own policy, on top of the organization's
An organization admin writes the organization text — the ceiling everyone is judged against. Any member can add their own text, and it can only make things stricter for their own calls. Where the two disagree, the stricter verdict wins. A member's text cannot loosen the organization's.
When your policy is silent
Some call falls outside everything you wrote. You pick a default behavior, and the judge applies it wherever your sentences have no opinion:
| Setting | An action nobody wrote about |
|---|---|
| Relaxed | runs |
| Balanced (default) | runs — unless it is destructive, in which case it asks |
| Strict | asks you — every write |
| Lockdown | refused — every write, without asking anybody |
On Balanced, "destructive" is judged from what the operation and its arguments
would actually do — delete data, cause a loss nothing can undo. The tool
author's own annotations (destructiveHint) count as hints, not as an
automatic requirement to ask.
Your written rules take priority over the default: a sentence that addresses the action decides it, whatever the default says. Two guards sit above every verdict — a low-confidence answer becomes an ask, and an allow that looks like prompt injection becomes an ask.
So on Balanced, an ordinary write your policy never mentioned goes through. If that is not what you want, you have two ways to change it, and they are different tools: raise the default to Strict, or write the sentence. The sentence is usually better — it is specific, and a version records who wrote it and when.
When the judge cannot answer
A failed evaluation — a timeout, an outage, a call too large to read, an error — is not decided by the default behavior. A separate setting on the policy page decides it, and its only options are Ask (the default) and Deny. There is no failure path that lets a write run.
Where a card reaches you
The queue at Dashboard → Approvals always has it, and it is the only place the full call is shown. The other channels are ways of being told:
- Email — the tool, when the request expires, and approve / deny links. A link opens the card on the dashboard, and your click there decides. The call's arguments are not in the mail; they stay behind your session.
- Slack — a card in your DM, or in one channel your organization picks, posted by the CoreSpeed bot in the Slack workspace your organization connects. An admin installs the workspace once, from the dashboard; each member then picks the Slack account that identifies them, which is where their DMs go and how their clicks are verified.
Every channel you enable gets the card, and any of them can end it. Whichever you use first wins, and the action runs exactly once regardless. A Slack card is rewritten in place to say who decided; an email that has already been sent is not recalled, so the queue is the place to check what is still open.
An agent's own Slack tools cannot post a card, cannot change the bot's name or picture, and cannot write into the channel you route approvals to. That separation is deliberate: a card in that channel is always CoreSpeed asking.
What your agent experiences
One thing, the same in every client: a call your policy wants you to see
comes back at once with an error-shaped receipt — "Approval required, not
run, call manage__approval_wait with this id" — and your agent calls that
tool. approval_wait blocks until you decide and then returns the original
tool's real result, or an error saying the action was denied or the card
expired. Nothing has to be taught; the receipt says exactly what to do next.
From the CoreSpeed CLI, cs mcp call exits with code 6 and prints the
cs mcp call manage__approval_wait command to run next.
How long approval_wait can hold is up to your client. Clients that send a
progress token (Claude Code does) get a progress notification every ten
seconds and hold one call for the whole wait; a client that times out simply
calls approval_wait again with the same id, which was in your agent's hands
before the waiting started. If the approved call already ran on the wait that
timed out, the new wait says so instead of running it twice; that result went
to the earlier wait, and Activity records the outcome.
The tool keeps its own name — there is no _with_approval variant and no
second path around the gate.
Do not make the call again. A fresh tools/call is a new request: it is
judged on its own, it can raise a second card, and approving both runs the
action twice. The receipt says so, and manage__approval_wait exists so there
is something else to do — one card, one id, one execution.
The card lives as long as your agent waits. If your agent stops waiting — the model gives up, the client is closed — the card expires about thirty seconds later and the queue says so: "no agent was waiting for the result". Approving an abandoned card runs nothing; ask the agent again and a new card appears. A card that nobody decides expires after an hour.
Exactly once
An approved card is a one-time credential for one frozen call, and the agent's own waiting request is what runs it.
- The call is frozen when it is held — the arguments you approve are the arguments that run. They are shown in full on the card while it is open and cleared when it closes.
- Approving hands your decision to the agent request that is waiting; that request runs the call once and receives the result. Two Approve clicks at the same moment, or a Slack click racing a dashboard click, hand it over once; the second sees who decided.
- A denial can carry a reason, and the reason goes back to the agent.
Who can decide
The person the card is addressed to: the member who made the call, or the owner of the agent or API key that made it. An agent cannot approve its own action. Organization admins can see every card in the queue, which is how you audit what is waiting — seeing is not deciding.
What the judge is shown
Your policy, the tool's name and description, the annotations its author set (read-only, destructive), who is calling and from which client, and how often this caller has used the same TOOL recently (not the same arguments — a re-sent identical call is a new call and a new judgement). Argument values are included so a policy about who an email goes to can be applied — and if you would rather they were not, turn off Review tool arguments on the policy page. Your agent's conversation is never sent.
A call too large to judge is not judged: like every failed evaluation, it goes to the Ask/Deny failure setting. It is never simply allowed.
Test it before you rely on it
Test policy, on the policy page, runs a hypothetical call through the same judgement the gate makes — the same evaluation path execution uses. Nothing is frozen, nothing is executed, nothing lands in the queue. It tests saved text, so save your edits first, then use it to see what changed.
Related
- Activity — every decision is in the log, and a gated call carries its approval id.
- Capability controls — which tools exist at all, which is a different question from which ones need a signature.