Stop the action. Ask a human.
Agents should not need a person for everything. They should need one for the small number of actions where judgement is the control - and when that happens the action stops rather than slows down.
Not a queue you can wait in.
A hold is not a deferred allow.
The most dangerous misreading of Anubis is “approval required means wait, then proceed”. Stated exactly, because every surface behaves this way:
An agent that could approve itself would make the control meaningless.
| Operation | Who may call it |
|---|---|
| POST /api/runtime/evaluate | An agent runtime credential, scope runtime:evaluate |
| POST /api/runtime/approval | A signed-in human with the security_lead role |
| GET /api/runtime/events/{id} | A signed-in human, organization-scoped from the session |
Approval resolution is a control-plane operation behind a browser session. An agent runtime credential can never call it, and there is no configuration that creates one that could.
There is also no agent-facing route to read an approval's state. So no client in the product offers poll(), wait_for_approval(), resume() or consume() - not the SDKs, not the adapters, not the CLI. Building a client for a call that cannot be made would be worse than not having it.
What actually happens.
What a correct integration does
Stop the run, report the approval_id to the operator, and - if the work should continue later - re-enter it as a new run, which issues a new evaluate call and gets a fresh decision.
What it never does
Never poll evaluate waiting for the answer to change. Every accepted call writes an evidence row, so a loop turns one held action into a flood of them - and it converts a review control into a delay the agent eventually wins.
Every surface stops the same way.
| Python SDK | decision.approval_required is true and approval_id is set. raise_for_decision() raises. |
|---|---|
| TypeScript SDK | Same shape. throwIfNotAllowed() throws. |
| Framework adapters | Raise at the tool seam. The tool does not run. |
| CLI | Prints the decision and exits 4. |
| REST | HTTP 200 with approval_required. Your branch must refuse. |
None of them waits. None of them retries.
Private access
Decide which actions deserve a human.
The hard part is not the integration. It is agreeing which actions belong in each of the three answers.