Human in the loop (HITL) and approvals¶
When an agent wants to do something consequential, it can stop and ask. The
request appears in the Approvals section of the dashboard Overview with the
exact action described, and the agent waits for your decision (up to
security.hitl_timeout_seconds, default 300).
Approval is decided in three layers, each overriding the one before:
1. The profile (security.profile) sets the baseline:
| Profile | Meaning | Always asks before |
|---|---|---|
developer (default) |
Trusted — act freely | money, writing secrets, running commands in another agent's workspace |
safe |
Supervised — ask before writes | the above, plus external writes, shell commands, reading secrets |
production |
Locked down — strictest | the above, plus filesystem writes and delegating to subagents |
2. Toggles add categories on top: security.hitl_write_tools: true adds
external writes; security.hitl_shell: true adds shell commands.
3. Per-tool overrides win over everything:
security.tools.<tool_name>.approval set to "always" or "never".
security.hitl_enabled: false is the master off switch — full autonomy, no
approvals at all. All three profiles enable it by default.
One thing is always individually approved, in every profile: running a command in another agent's workspace. There is no configuration that turns that off.
security.hitl_auto_approve_low_risk (default true) lets clearly-safe actions
through without asking, so the queue stays meaningful.
Cortex has its own approval setting. cognition.auto_approve decides whether
background work skips the queue, but cognition.approval_required — default
["external_send", "irreversible", "identity_edit"] — always needs a human,
whatever auto_approve says. Anything that sends something outside, cannot be
undone, or rewrites who the agent is, gets a person in the loop: the item waits
in Pulse → Approvals instead of being applied, and a background run that
tries such a step pauses on it the same way. Fully Autonomous still handles
everything else on its own — new skills, memory, procedures, tidy-ups. An owner
who really wants an agent to act unattended on all of it sets
approval_required: [] for that agent; one who wants only identity protected
lists just ["identity_edit"].
Approvals from WhatsApp. When someone talks to an agent on any of the three WhatsApp connectors, the outcome of a risky action depends on who they are:
- An owner has risky actions approved without being asked, unless Ask
owners in chat before risky actions is on in the connector's Behavior
section (
chat_approvals: true). Owners are the numbers in Owner numbers; on the QR connector the numbers in Allowed phone numbers count as owners too, while on the Twilio and Meta connectors Allowed sender numbers is a list of customers and makes nobody an owner. Then the agent posts the action and its details in the chat with a four-character code and waits; the owner repliesYES 4F2Ato let it run orNO 4F2Ato stop it. Only the owner whose message started the request can answer, and a reply without the code does nothing. With no answer beforesecurity.hitl_timeout_secondsthe action is rejected, and so is one whose prompt could not be delivered. - Anyone else never decides: their risky actions wait in the dashboard's Approvals list for someone with access to the agent.
The approval record. Every approval decision is written to an append-only
file, audit/approvals.jsonl in the Olano home: the action and its details,
when it was asked, the outcome (approved, rejected, timed out, or abandoned when
the server stopped before anyone answered), who decided (a dashboard user as
human-<username>, a messenger sender as <platform>:<id>) and through which
surface. Actions an owner's messages had approved without asking are recorded
too. Read it on the Overview page under Approvals → Approval history,
which can be narrowed to one agent. Admins see the whole record; a member sees
the rows for agents they have access to.