You are reading Nightly documentation for 0.12.4.dev0+g50bde75.

This documentation may describe behavior that differs from Stable.

Open Stable documentation

Documentation version

0.12.4.dev0+g50bde75 · Nightly

Autonomy and approvals

Autonomy and approvals

Holaryn Agent never assumes it may act on your behalf. Every tool call is classified by its consequence, resolved against your chosen autonomy posture, and — when it matters — held for your explicit yes. This page explains the model, the postures, unattended behavior, policy overrides, and dry-run mode.

The two axes

Autonomy is not one dial. Holaryn separates two independent questions:

  • Control — does the action wait for your yes before it runs?
  • Awareness — are you told that it happened?

Crossing the two gives the four autonomy levels a tool call can resolve to:

Level Waits for yes? Tells you?
Autonomous (silent) No No — trivial, reversible work only
Autonomous + notify No Yes — do it, then report
Ask first Yes — the run suspends until you answer Yes
Never unattended Refused in a scheduled run; held for a foreground session Yes

An unanswered ask is never auto-approved and never auto-failed — the run simply waits.

Consequences

Every tool carries a declared consequence: whether the action is reversible, and a category (fs.read, fs.write, shell, script, memory.write, mcp.*, skill.*, command.script, command.app, and so on). The approval policy resolves a consequence plus the call's arguments to one of the four levels. The default rule is simple:

  • Reversible → act (autonomous + notify).
  • Irreversible → ask first.

Postures

The posture is the run-wide stance that decides how consequences resolve. Four postures exist:

Posture Behavior
ask Everything consequential waits for you.
selective (default) Reversible actions proceed with notification; irreversible actions ask.
allow-all Acts on everything except a residual of hard-floor categories (shell, script, command.script, command.app by default — configurable via HOLARYN_ALLOW_ALL_RESIDUAL), which still ask.
unrestricted Acts on everything; only external-app launches (command.app) still keep a human in the loop.

Both allow-all and unrestricted are gated behind explicit confirmation ceremonies in the UI — you cannot drift into them.

Set the posture:

  • CLI: holaryn run --autonomy ask|selective|allow-all|unrestricted, or the shorthand flags --allow-all yes / --unrestricted yes.
  • Web chat: the mode picker in the composer, or type /mode ask|selective|allow-all|unrestricted in the chat. The mode change applies to the live session immediately. See slash-commands.md.

/mode is a control-plane command: only surfaces that grant the posture capability offer it (the web chat does; messaging channels and the scheduler refuse it by construction, and the CLI REPL bakes its posture in at start).

The approval dialog

When a live Chat or Code run asks first, a focus-trapped dialog moves focus to its title so you
review context before acting. It names the tool, affected resource, one-call scope, consequence,
reversibility, request time, provenance, trust/sensitivity, request identity, summarized fields,
and exact arguments (file edits show the exact diff), then offers:

  • Allow once — permits only this named call.
  • Deny once — refuses only this call; the run continues and the agent is told. Pressing Esc is the same explicit deny — it is announced, never a silent dismiss.
  • More options, a menu with the scoped decisions:
  • Approve and don't ask again for {tool} (this session) — further calls to that tool run with notification instead of asking, until the chat session ends. The grant is never persisted, and the hard-floor categories (shell, scripts) still outrank it.
  • Approve all actions for this session — approves and switches the session to allow-all in the same gesture (the same switch as the arming ceremony; shell and scripts still require approval).
  • Deny and terminate the run — refuses the call and stops the in-flight run cleanly. The transcript notes "run terminated by the operator" and the chat is immediately ready for your next message.

Every decision is announced to screen readers the moment it lands ("Approved {tool}", "Denied {tool}", "Session switched to allow-all", "Run terminated").

Unattended and scheduled runs

Nothing auto-proceeds in an unattended run:

  • A job scheduled with allow-all or unrestricted is downgraded to ask-first at dispatch (with a visible warning), so consequential actions suspend instead of running unwatched. The one exception is dry-run jobs — staging is already the safe mode.
  • When a gated action fires unattended, the job's --on-gated setting decides: hold parks the approval and suspends the run; cancel denies it. Either way the decision is yours — a suspended run never times out into approval and never silently fails.
  • Parked approvals, operator questions, and post-restart recovery decisions all land in the
    Approvals workspace (Ctrl+0 by default), which shows a pending count from anywhere. Answer
    from there, or from the CLI with holaryn question and holaryn resume.

See scheduler-and-automation.md for how jobs are defined.

Policy profiles: per-category and per-tool overrides

For routine automations, a hand-editable TOML policy profile pre-authorizes (or tightens) specific categories and tools instead of flipping one big switch. The file uses four level aliases: silent, notify, ask, never.

[selective.categories]
"fs.write" = "notify"     # let the selective posture write files without asking
"team.message" = "ask"

[selective.tools]
"run_shell" = "never"     # this tool never runs unattended

[allow-all.tools]
"generate_image" = "silent"
  • [selective.categories] and [selective.tools] refine the selective posture.
  • [allow-all.tools] refines allow-all per tool. There is deliberately no allow-all.categories table — the allow-all residual is controlled only by HOLARYN_ALLOW_ALL_RESIDUAL, and the residual is a hard floor a profile cannot weaken.

The active profile is resolved in this order: the --policy <file.toml> flag (on holaryn run and holaryn schedule) > the HOLARYN_POLICY_FILE environment variable > ~/.holaryn/policy.toml if it exists. Inspect the active profile with holaryn policy show, or edit rules graphically in Settings → Autonomy & Permissions.

Threshold rules ("purchases under $20 allowed, ask above"; "internal recipients silent, external ask") are also supported by the policy engine for categories that expose a numeric or recipient field.

Dry-run: propose a plan, approve in batch

Dry-run mode is how a new automation earns trust. With dry-run on, irreversible tool calls are staged into a reviewable plan instead of executing; reversible actions (reads, recalls) still run so the agent can actually plan.

Turn it on:

  • CLI: holaryn run --dry-run "reorganize my downloads" — orthogonal to the posture flags.
  • Web: the Dry run checkbox in the composer tray, or /dryrun on|off in the chat.
  • Scheduled jobs: a dry-run flag on the job stages the run's consequential actions for later review. In this one case the unattended allow-all downgrade is skipped — previewing what allow-all would do unattended is exactly the point.

Review and execute:

  • Web: when a dry-run task finishes with staged actions, a plan card appears above the composer listing every action with its consequence category. Approve & execute runs exactly the recorded calls, in order — the task is never re-run. Discard drops the plan.
  • CLI (works for web- and scheduler-staged plans too):
holaryn plan list                 # reviewable plans
holaryn plan show <plan_id>       # per-action tool, args, category, state
holaryn plan approve <plan_id>    # execute the remaining staged actions, in order
holaryn plan discard <plan_id>

Plans are durable (staged-plans.sqlite3 in the state directory). Execution stops at the first failure; the remaining actions stay staged, so re-approving retries only what never ran. Two honest caveats shown in the review surfaces: arguments were computed against world state at dry-run time and may be stale, and batch approval deliberately replaces the per-call gates — which is why every call's category (including shell) is shown before you approve.