Operating autonomous response
The model behind this — the levels, the blast-radius ceilings, and the record a class has to earn — is Autonomous response. This page is the operating manual: what to set, what to watch, and what to do when the engine has acted.
Everything here is in the console under Command Center → Autonomy, and everything the console does is this API.
Availability. On a self-hosted install the engine ships dormant: while
AUTONOMY_EXECUTION_ENABLEDis off (the default) it records what it would have done and executes nothing, whatever your levels say. See Self-hosted deployments.
Permissions
Four permissions, deliberately split so an incident responder can stop the engine without gaining the power to reconfigure it:
| Permission | Grants |
|---|---|
autonomy:read | Read the decision ledger, engine metrics, graduation status, and the kill-switch state |
autonomy:approve | Approve or deny a proposal, and undo an executed action |
autonomy:disable | Engage or release the emergency stop |
autonomy:manage | Reserved for configuring response policy. No endpoint requires it today — the levels below are organization settings behind tenant:manage. It is not inert, though: see the note under Step 1. |
autonomy:approve is not sufficient on its own. Approving an action also requires the
permission to perform that action yourself — session revocation needs tenant:manage — and the
same rule governs undoing one. An approver can never unlock authority they do not have.
Grant these with roles in Access Control, like any other permission.
Step 1 — choose a level per response class
Levels are organization settings, one per blast-radius class,
every one defaulting to manual:
| Setting key | Class |
|---|---|
autonomy.level_contain_source | Contain source |
autonomy.level_contain_non_human | Stop an AI agent |
autonomy.level_raise_assurance | Raise assurance |
autonomy.level_revoke_access | Revoke access |
autonomy.level_disable_principal | Disable principal |
Set one in Settings → Organization, or over the API (requires tenant:manage):
curl -X PATCH -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"value": "recommend"}' \
https://auth.example.com/api/v1/tenants/config/autonomy.level_revoke_access
The accepted values are the ones the class's ceiling permits, so each key offers a different set — you cannot store a level the engine would not honour:
| Setting key | Accepted values |
|---|---|
autonomy.level_contain_source | manual · recommend · graduated · full |
autonomy.level_contain_non_human | manual · recommend · graduated |
autonomy.level_raise_assurance | manual · recommend · graduated · full |
autonomy.level_revoke_access | manual · recommend · graduated |
autonomy.level_disable_principal | manual · recommend |
Anything past the ceiling is a 400 naming the allowed set — full on revoke_access, or
anything unattended on disable_principal, is refused rather than silently reduced.
One cap is still applied at decision time and cannot be seen here. A detection judged attacker-inducible holds its response at
recommendhowever its class is configured (see the inducibility cap). That is a per-detection judgement, not a per-class one, so no setting can express it — a class may legitimately sit atfullwhile one of its detections still only ever proposes.
Start at recommend. It is also the only way to build the record a class needs before it
can act unattended — nothing accrues at manual, because nothing is proposed.
Holding
autonomy:manage,autonomy:disableoragents:killmakes you un-targetable. The engine never acts on a principal who could stop it, so these permissions carry a second meaning beyond what they let you call. Owners are excluded for the same reason: an owner holds every permission, including the off-switch. Grant them to the people who run the engine, not as a way to shield an account from response.
Step 2 — work the proposal queue
At recommend, a firing detection raises a proposal and waits. Read the ledger:
# Everything awaiting a verdict (requires autonomy:read)
curl -H "Authorization: Bearer $TOKEN" \
"https://auth.example.com/api/v1/autonomy/decisions?mode=proposed"
Each decision carries the plan's effect sentence — one line saying exactly what would
happen — plus the detection that triggered it, the target, the blast class, and two flags the
server computes for you: actionable (still awaiting a verdict and inside its window) and
revertible.
Approve or deny:
curl -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"approve": true, "reason": "Confirmed token theft — the session set is not the user's"}' \
https://auth.example.com/api/v1/autonomy/decisions/$DECISION_ID/adjudicate
- The verdict is recorded before the action is attempted, so a decision is never lost to a downstream failure — and your verdicts are the evidence the graduation gate counts.
reasonis optional on approval and worth writing anyway: it is the oversight record an auditor reads. It is required when undoing an action.- A decision that already has a verdict returns
409. Retrying is safe; it will not double-act. - Proposals expire. Past its window a proposal lapses unexecuted rather than acting on facts that have aged out — approving a lapsed one is refused, and the ledger says so.
Nothing pings you when a proposal is raised. The detection behind it alerts on your normal channels; the pending proposal itself does not. Watch the queue — see Is anyone actually driving this? below.
Step 3 — undo an action
Reverting session revocation restores the previous state, so a wrongly-signed-out user's existing devices resume working without signing in again.
curl -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"reason": "False positive — the second device was the user travelling"}' \
https://auth.example.com/api/v1/autonomy/decisions/$DECISION_ID/revert
- Guarded exactly like approval: you cannot undo what you could not have done yourself.
- It refuses (
409, nothing changed) when the effect was superseded — for example if a person revoked the same sessions again after the engine did. Undoing then would silently discard their decision. - A revert is honest about being partial: it restores what this action changed. Sessions ended by something else — a logout, a password change, an IdP deprovision — stay ended.
- Reverting feeds the circuit breaker. Any reversal in the trailing 30 days holds that class below unattended operation until the window is clean. That is intended: a reversal is the signal the engine got something wrong.
Undoing an agent stop
Reverting a stop an AI agent decision releases the agent's kill switch: it authenticates and works again, immediately, with nothing to re-provision. Two things differ from the session case, and both are deliberate:
- Tokens issued before the stop stay dead. The containment cut-off only ever moves
forward, so a token that was already stolen cannot come back to life because the engine
changed its mind. Your agent gets a new token on its next request; the response says so
(
epoch_retained). Read the revert as "the agent can work again", never as "as you were". - Only a person can do it. Undoing a containment restores a non-human principal's
authority, which is the same act as releasing its kill switch by hand — and that has been
people-only since v0.33.0. An AI agent that tries is refused with
403and the attempt is recorded as a security event, whatever permissions it holds.
Watch: is it working, and is anyone driving it?
curl -H "Authorization: Bearer $TOKEN" https://auth.example.com/api/v1/autonomy/metrics
Four numbers matter:
- Decisions by mode — what the engine has been deciding.
- Time to act — decision raised → action executed, mean and p95.
- Age of the oldest pending proposal — the one to alert on. A
rising oldest-age means proposals are not being adjudicated: no record grows, nothing ever
graduates, and
recommendhas quietly become shadow mode with extra steps. Lapsed proposals stay counted; they are the strongest form of the signal, not noise. - Cap utilisation — how much of each class's hourly and 24-hour blast-radius budget is spent. Hitting a cap halts that class and raises a critical detection.
Check what a class has earned
curl -H "Authorization: Bearer $TOKEN" https://auth.example.com/api/v1/autonomy/graduation
Per class: whether it is eligible, every criterion with its numbers, the unmet ones named,
and ≈how many more clean approvals would clear the confidence bar. This is the answer to
"why is autonomy still asking me every time?".
Two things to expect from the numbers:
- Graduation is always about your own organization's adjudications. There is no cross-organization view of a track record, by construction.
disable_principalhas no row. Its ceiling is Recommend, so there is nothing to graduate to, and inventing a row would imply a path that does not exist.contain_non_humanhas a row, and its bar is0.90rather than revoke-access's0.95. It is the only class in which a false positive harms no person — an AI agent stops, and any human holdingagents:killstarts it again in one call.
What one denial would cost
Each class also reports it, as two fields:
| field | meaning |
|---|---|
lower_bound_after_denial | the confidence bound if the very next adjudication is a denial |
recovery_approvals_after_denial | consecutive clean approvals needed after it (0 = still above the bar, null = not recoverable soon) |
The bar is a statistical bound, not a count, so near it the margin is one adjudication wide and the cost is asymmetric: a denial both lowers the observed rate and adds a sample, while the streak that undoes it is long. A class sitting at 80 of 80 against a 0.95 bar reads 0.954 today and 0.933 after one denial — 29 clean approvals to get back. Plan anything that might produce a denial (a deliberate test, an unfamiliar rule, a new on-call rotation) against that number rather than against the impression that one "no" is a small thing.
On an empty record the answer is arithmetically real and practically meaningless — the
bound is already 0, so nothing is at risk. The console omits the sentence there rather than
printing a true number attached to a false story, and an API consumer should do the same:
guard on total > 0 before rendering it.
The emergency stop
# Read the current state (autonomy:read)
curl -H "Authorization: Bearer $TOKEN" https://auth.example.com/api/v1/autonomy/kill-switch
# Stop everything for 8 hours (autonomy:disable)
curl -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"enable": false, "hours": 8}' \
https://auth.example.com/api/v1/autonomy/kill-switch
hoursmust be1,8, or24, and the stop auto-re-arms when it lapses. The real-world failure is the switch nobody turns back on.- Omitting
hoursmeans indefinite, which is refused without areason— the unbounded option is deliberately the awkward one. {"enable": true}releases it.- Every change raises a critical detection. Disabling is both a legitimate emergency action and an intruder's first move; the platform does not guess which, only makes it visible.
Detections and proposals continue while the switch is engaged — what stops is acting.
Self-hosted deployments
Set these in your instance's environment file and recreate the backend and the workers:
| Variable | Default | What it does |
|---|---|---|
AUTONOMY_EXECUTION_ENABLED | false | The global execution gate. Off means nothing executes and nothing is proposed, whatever your levels say — the engine only shadows. Turning it on is a deliberate operator act, never a side effect of deploying. |
AUTONOMY_SHADOW_ENABLED | true | The response pass itself. false stops the engine even recording what it would have done. |
AUTONOMY_PROPOSAL_TTL_SECONDS | 3600 | How long a proposal stays open — the window for a verdict and for the approved action to run. Past it the decision expires unexecuted. |
AUTONOMY_DECISION_RETENTION_DAYS | 90 | How long unadjudicated shadow rows are kept. Anything adjudicated, proposed, executed or reversed is never purged — that is the evidence and the action record. |
Set COMMAND_ALERT_EMAIL and/or COMMAND_ALERT_WEBHOOK_URL before raising any class above
recommend. Unattended executions notify on those channels, and an install with neither set
takes actions nobody is told about. The notifier is fail-open by design — a broken channel
never blocks or rolls back a response — which is exactly why an unconfigured channel is silent
rather than loud.
Next
- Autonomous response — the model, the ceilings, and why autonomy is earned.
- API reference → Autonomous response — the endpoint contracts.
- Tokens & sessions — everything that can end a session, including this.