Choose an approval mode: ask, trust, auto¶
keryx shell (both the OpenTUI surface and the --no-tui readline fallback)
has three permission modes that decide whether a mutating tool call
(shell_exec, spawn_subagent, or any tool declaring risk destructive)
stops to ask you first. This page is the full reference: what each mode does,
how to set it, and exactly where every piece of state lives on disk. It also
covers the separate, orthogonal read-only /plan toggle, further down, for
when nothing should be reachable at all.
Not the same thing as the policy engine
This is a session-level convenience layer for the interactive shell. It is
a different mechanism from the policy engine
that governs keryx harness run/keryx harness exec/keryx serve —
those keep their existing allow/ask/deny behavior untouched,
including "headless never silently allows." Permission modes never
weaken that. See Scope below.
The three modes¶
| Mode | What happens |
|---|---|
ask (default) |
Every shell_exec, spawn_subagent, and destructive-risk tool call asks first — today's behavior, unchanged if you never touch this feature. |
trust |
Safe calls run without asking. A call still asks when it is destructive — either the tool's own static risk, or a command keryx's classifier recognizes as destructive (rm -rf, force-push, and similar). |
auto |
Commands do not ask, including destructive ones, apart from the hard floors below (credentials, SAC review confirmation, publish lease) and the question floors in Who answers a question — the same shape as Claude Code's --dangerously-skip-permissions or the informal "yolo mode" other CLIs offer. Entering it always requires an explicit one-time confirmation first. |
One thing no mode ever changes: a command that touches keryx's own
credential/permission files (auth.json, permissions.json,
.local/share/keryx, .config/keryx appearing anywhere in the command) is
never auto-approved, in any mode, including auto. That floor exists so
a compromised or confused turn cannot use auto mode to grant itself new
authority.
Round limit per turn¶
The mode also decides how many model rounds one turn may run before it stops. A round is one request to the model and may carry several tool calls.
| Mode | Rounds per turn | At the limit |
|---|---|---|
ask |
40 | Asks "Increase limit and continue / Cancel"; a yes grants 40 more. |
trust |
200 | Asks the same question; a yes grants 200 more. |
auto |
200 | Never asks to extend the limit. Below 200 the turn extends itself and prints a [budget] line; at 200 it stops with a [budget] message. |
| automatic review (any mode) | 200 | Same as auto; see Automatic review. |
KERYX_AGENT_MAX_ROUNDS wins in every mode, and 200 is the ceiling: a larger
value is clamped to 200. In auto the variable can lower the limit, not raise
it; a turn then extends itself in steps of that value up to 200. The mode is
read at the start of each turn, so /mode auto, /mode trust and /mode ask
change the limit of the next turn without a restart. /mode and the /mode
picker show the limit of each mode.
Subagents keep their own limit (10 by default, at most 24 for a max_rounds
request) in every mode. They never ask a person at their limit, so there is no
question to remove.
Who answers a question¶
The mode also decides who answers an ask_user question the model raises.
trust is a statement about actions ("do not interrupt me for a command I would
have approved"), not about judgement, so it does not change this.
| Mode | Who answers |
|---|---|
ask |
You. |
trust |
You. |
auto |
The model picks the recommended option, or the first one, and says so. |
In auto the model is told [auto mode] chose this without asking the user, so
it cannot present the choice as yours, and the shell prints a
◇ answered without asking (auto): line with the question and the chosen
label. Such a question uses no tool-call budget and is not remembered as an answer
of yours. It is not an invocation, so the tool-call budget is untouched and no hook is
consulted for it; the default cap of 3 identical calls still applies. It is not written to the
recommendation journal either, because no person answered it. An automatic review does
not use this path: its own rules decide what is answered and what goes to a person.
The question still goes to you when it decides an irreversible action
(irreversible or action), when the turn holds untrusted external content, when
the turn started in Telegram, including a /goal sent from there (the buttons appear in
the topic, as before), when review-auto is on, KERYX_REVIEW_AUTO_ANSWER=1 is set or an
autonomous answers file is set and the question carries a policy tag, or when it asks for
permission after you refused an action in any mode since the last mode change, /new,
/clear or resume. "Asks for permission" is decided
by the question's wording, so this is a floor for questions phrased that way, not a
guarantee against a differently worded one. An unattended run stops at a question as
before.
A session with no way to show a question (no dock, no host) tells the model "No one was asked" instead of "User cancelled the question (Esc)", so it does not report a cancellation nobody made.
Automatic review¶
An automatic review is a run mode of one turn, separate from the permission
mode: it works the same in ask, trust and auto. It starts when the
operator asks for one in the request ("run an automatic review of PR 12",
"автоматическое ревью") and creates its package with --automatic
(keryx review start|attach|ingest --automatic, recorded as
manifest.automatic: true). A line that did not come from the operator (a
task notification, a bus message, a trigger run) never starts one, and an
unattended trigger runner keeps the 40-round limit.
While a turn belongs to an automatic review:
- Round limit 200, no question. Reaching the limit extends the turn
without asking; at 200 it stops with the same
[budget]message asauto. The mark does not carry over to a turn that does not mention an automatic review. - The review's own commands run without a prompt, for that package only.
These
keryx reviewcommands, written as plain words with no pipe,;,&&, redirect or substitution, are approved:start,attachandingestwith--automatic(once per run);status <this review>andcomplete <this review>;ledger build <dir> --package <this review>,severity-audit,context-collect,compare-referenceandmutate --test "bun test …", each with--package <this review>;retry-plan --manifest <this review>/…; andscopeandblast-radius, which write only inside the package. Anapply_patchis approved when every target is inside the package directory (.metaproject/reviews/<id>/). A command that names another review or path, or any otherkeryx reviewsubcommand, is asked as usual. The hard floors (destructive commands, credential files, content from an untrusted origin, a hook that asked, a publish lease, read-only mode) are never lowered. - Questions are answered with their default. An
ask_usercall gets its recommended option, or the first one, and the answer is counted. A question that decides an irreversible action (irreversible/action) or carries a reviewpolicytag is not defaulted: it goes on to the host (the review arbiter or the operator), and is counted as manual. - It stops to ask only in an emergency, a closed list:
- a command outside the review's package or outside the review commands;
- a destructive action;
- a provider authentication failure;
- a gate refusal that needs the operator's quote (a waiver);
- a decision on a reduced reviewer set;
- a provider error after the retries.
Cases 3 and 6 end the turn with the error instead of a question. Cases 4 and
5 reach the operator only after review complete has actually refused: an
earlier claim is answered with the default.
- No gate bypass. review complete and its refusals are unchanged. This
mode removes prompts, not checks.
- A disclosure counter. The turn ends with a count of inputs the operator
gave by hand against inputs the shell answered itself, in
automatic-run-inputs.json inside the package and as one line in the
session. Flow 419 reads it.
The sidebar Mode row shows · automatic review while such a turn runs, and
/mode prints the same rule and the emergency list.
Trusting one MCP tool for the session¶
An MCP tool called through use_tool always counts as destructive, so it asks
even under trust. In trust mode the prompt therefore also offers T (the
TUI dock: "Trust this tool (this session)") next to approve and deny. It skips
future prompts for that exact tool name, for this interactive session only —
never a wildcard, never a whole server, and never written to disk. The model
cannot add to it, and /new, /clear and resuming a session clear it.
Three rules keep the grant from becoming a way around the approval gate:
- Bound to the tool's definition. The grant remembers a fingerprint of the tool's name, description and input schema as the server reported them when you said yes. If a later call finds a different definition (or the tool is gone), the grant is dropped and the call asks again.
- Not offered, and not honoured, next to untrusted content. When the turn
already holds external content (a web result or any MCP tool result, including
one from the trusted tool itself), the call asks even for a trusted tool, and
the prompt does not offer
T— it says why instead. The grant applies again from the next turn. - Never offered for a tool its server marks destructive. If the tool's
catalog entry says
destructiveHint: true,Tis not offered (dock and prompt alike) and the prompt says why. The check reads the live catalog on every call, so a grant made while the tool looked harmless is dropped, and the call asks again, once the server starts reportingdestructiveHint: true. A tool with no annotation, ordestructiveHint: false, is treated as it always was and is still offeredT. The hint is advisory and comes from the server, and the MCP default for a missing hint is "destructive", which would withhold trust from nearly every server; keryx only acts on an explicittrue.
Seeing and revoking grants¶
A tool that holds a grant carries a [trusted] marker: on its approval lines
(when it still has to ask, for example next to untrusted content), on the line
printed when it runs without asking, in the /mcp server view (a per-server
count), and in the list below.
/mcp trust list # every trusted tool, by full name and server
/mcp trust revoke <server__tool> # revoke one; it asks again on its next call
/mcp trust revoke all # revoke everything
list shows (will ask again: changed | destructive | gone) instead of [trusted]
for a grant the next call would not honour: the definition changed, the tool now
reports destructiveHint: true, or it left the catalog.
Revoke takes the full server__tool name exactly as list prints it. An
unknown name is reported and changes nothing. These work while the agent is
busy, and in the readline shell as well as the TUI.
Setting the mode¶
One-shot, from the command line:
keryx shell --trust # or --ask / --auto
keryx shell --permission-mode trust # equivalent, explicit form
Inside a running session, the /mode command works the same way in both
the TUI and --no-tui:
/mode # show the current mode (TUI: opens a picker)
/mode trust # switch for this session only
/mode trust save # switch AND remember it as this project's default
/mode clear # forget the stored project default
Switching to auto always stops for an explicit confirmation first —
typing yes in the readline shell, or a Confirm/Cancel choice in the TUI.
There is no flag or setting that skips that confirmation; the mode can only
ever be changed by you, directly, in the running session. Nothing a tool or
the model outputs can set it — that is a deliberate boundary, not an
oversight.
Review system¶
The review system decides who answers the approval questions of a review run. By
default it inherits the permission mode: in trust it asks you only now and then,
for example when a reviewer is about to be spawned after the agent has read external
content (the page the agent read cannot approve that call; you do).
Switch it to review-auto and the shell answers those questions itself:
or say it in the session: "сделай автоматическое ревью", "run an automatic review" or
mention review-auto. Only your own line counts; a subagent's task text and a message
from a peer or a tool result never switch it on. KERYX_REVIEW_SYSTEM=review-auto does
the same for an unattended shell, and ask_user menus take their recommended option.
What review-auto still asks you about: destructive commands, credential access,
publish leases, calls a hook tightened to "ask", and any call other than a reviewer
spawn or a routine command (below) that follows untrusted external content. Each
automatic answer is recorded in the transcript like any other auto-approval.
A refusal sticks. When you deny a call, review-auto remembers it for the rest of the
shell. The same call, in any spelling that normalizes to the same fingerprint
(keryx harness wave --spec x and keryx harness wave --spec=x), is not shown again:
the agent gets "refused earlier: choose another path" and no modal opens. An ask_user
menu that asks for permission (allow, deny, consent, access, "разрешить", "запретить")
is no longer answered with its recommended option. It reaches you instead, and the
transcript says so. An irreversible question was never auto-answered. /clear, /new,
resuming a session and changing the permission mode clear the memory. The per-call memory
is scoped to review-auto; under inherit a denied call is asked again. Only the
permission-question floor above is kept after a refusal in every mode (until the next mode
change, /new, /clear or resume), and only a person's "no" counts: an unattended run, a
superseded turn or an interrupted one does not.
Routine commands: no prompt after untrusted content¶
In trust and in review-auto, the commands below run without a prompt even after a
web or MCP result entered the turn. They are read-only or local and write nothing
outside the working tree. This table is the single list; the code reads it from
src/lib/trust-routine-command.ts.
| Command | Notes |
|---|---|
keryx help |
|
keryx <command> --help |
up to four words, no other flags |
keryx harness list, status, --help |
|
keryx agents external list --no-probe |
only with --no-probe |
keryx review attach, start, scope, slice, dispatch-check, retry-plan, ledger, floor, blast-radius, budget, tier, status, reviewers, required, ingest, complete |
|
keryx flow status, list, check, next |
|
keryx gdgraph find, context, affected, cycles, orphans |
|
keryx ctx rg, read, diff |
|
slate_note, slate_trail (tools) |
the session's own notes |
bun test is routine only while all of these hold, because a test run executes project
code: no untrusted content has entered the turn; the command runs in the project root,
judged after following the chain's cds (a cd whose target cannot be known makes it
ask: an option, -, a target the shell may expand, a relative name while CDPATH is set,
or a directory reached through a symlink); its arguments are only --bail, --only, --todo, --rerun-each N
and --timeout N with a plain non-negative integer; and the project's bunfig.toml is
byte-for-byte the file the process saw at its first turn for that project, with no
preload anywhere in it. The baseline is per process and first-wins: a later session in
the same process, on the same project, is judged against the file as the first one saw
it, not as it is now. A bunfig.toml that appears, changes, cannot be read (it is not a
regular file, or is over 256 KiB) or does not parse makes bun test ask again.
The path flags of keryx review slice (--diff, --out), retry-plan (--manifest,
--result, --state) and dispatch-check (--manifest, --payload) are routine only
when they land inside the project root by their real path, symlinks resolved. - is
standard input for --diff, --payload and --result only. The command refuses an
outside path itself, too, and opens the path it judged.
A command outside the table still asks. So does a command that chains with ;, pipes
to a command that is not listed, redirects outside the working tree, or uses a
subshell, $(…) or backticks: every segment has to be in the table. The hard floors
are not lifted by the table: destructive commands, credential access, human
confirmation (flow confirm, SAC review, scheduler control), publish leases and a
hook that says "ask".
Why am I being asked?¶
Every approval modal, the readline prompt and the Telegram relay carry one line that names the cause, from a closed set:
| Cause | Line |
|---|---|
destructive |
why: destructive command — no mode lifts this |
credentials |
why: touches credentials or needs your confirmation — no mode lifts this |
publish |
why: a git-publish lease applies — no mode lifts this |
hook-ask |
why: a policy hook asked about this call |
untrusted-content |
why: this turn holds untrusted external content |
not-in-routine-list |
why: untrusted external content in this turn, and the command is not in the routine list |
mode-ask |
why: the permission mode asks before this call |
Long runs in the shell¶
A review of a large pull request is hundreds of tool calls. The shell is built to carry one to the end without you typing anything after the task.
- Round limit. A turn may use 40 tool rounds in
askandauto, and 200 intrust. The limit follows the mode in force when the turn starts, so/mode trusttakes effect on the next turn.KERYX_AGENT_MAX_ROUNDSstill overrides both. When the limit is hit, the "reset" choice adds the limit of the current mode. - No prompt for routine commands. In
trustandreview-auto, a short&&chain ofcd, the commands in the routine table,keryx ctx run --, read-onlygitsubcommands andbun testruns without asking, even after the turn has read untrusted content (web or MCP results), exceptbun test, which is routine only under the conditions in the routine table. Destructive commands, credential access, SAC and publish gates, and every other command still ask. - Open plans do not end the turn. In
trust, a turn that would end with open plan items continues on its own, at most 8 times in a row, and stops earlier when the model makes no progress. - Answers are kept. What you answer to a question the agent asks (counterpart, model plan, budget, scope) is stored in the session slate, so it is not asked again after compaction or in a later turn. Questions marked irreversible are always asked.
- Waves of 10. Up to 10 subagents run at once, each with a round budget of 40 (at most 200 per subagent).
- Slash commands are queued, not dropped. A command typed while a turn runs is
shown as
queued as qNand runs when the turn ends;/queuelists, edits and removes. Each queued message shows its state and why it waits — "waits for the current turn", or "waits for approval of:" when an approval modal is open — in the panel and in /queue, and one line explains[Force]: it interrupts the current turn and runs the message now./new,/clearand/resumeare refused while busy, because running them later would swap the session under the questions queued behind them. - Ctrl+C cancels the turn. During a turn, one Ctrl+C cancels it and returns to the
prompt. At an idle prompt, the first Ctrl+C shows a hint and a second one within 2
seconds exits;
/exitalways exits. Exiting a shell started with/remote-controlends its Telegram topic.
For the review side (keryx review slice, dispatch-check, retry-plan,
ledger, keryx ctx run --raw) see the CLI reference.
Read-only mode: /plan¶
readOnly is a separate, orthogonal toggle — not a fourth mode. Where
ask/trust/auto decide how much confirmation a mutating call needs,
readOnly decides whether mutating tools are reachable at all. The two
combine freely: trust + readOnly still confirms nothing extra, but every
non-read action is refused outright regardless.
/plan # show the current state (TUI: opens a picker)
/plan on # deny every non-read tool call for this session
/plan off # back to whatever the current mode allows
When readOnly is on, a denied call gets an immediate refusal — never a
confirmation prompt, and never auto-approved, even under auto. It is a hard
floor: no mode lifts it.
Scope of what "read" means today. shell_exec is denied entirely under
readOnly — there is no per-command allowlist (git diff/git log/git
status are unavailable through the agent while it is on). The agent still
has read_file, list_dir, get_cwd, and search_code. This is a
deliberate v1 scope, not an oversight — a read-only git surface may follow if
it proves painful in practice.
Not persisted. Unlike the mode's save flag, readOnly is always
in-memory, always starts false on a new session. There is no
/plan on save and no config file for it.
Where it's stored¶
| What | Where | Notes |
|---|---|---|
| The mode for the current session | In memory only | Set from the CLI flag, or the project default below, or ask if neither is set. /mode changes it live; nothing is written to disk unless you use save. |
| Your project's remembered default | permission-mode.json, in the shared keryx config directory (see below) |
Written only by /mode <mode> save or /mode clear — never automatically. |
| The shared keryx config directory | macOS/Linux: ~/.local/share/keryx/ (or $XDG_DATA_HOME/keryx if set) · Windows: %APPDATA%\keryx\ |
The same directory that already holds auth.json (provider credentials) and projects.json (the project registry). One file per machine, keyed internally by each project's resolved path — not one file per project. |
permission-mode.json looks like this (path examples shortened):
{
"schemaVersion": 1,
"projects": {
"/Users/you/code/api-server": "trust",
"/Users/you/code/scratch-experiments": "auto"
}
}
The key is the project's resolved real path (symlinks followed), so the
same project reached through two different symlinked paths still shares one
entry. You will not normally edit this file by hand — use /mode <mode> save
and /mode clear — but it is plain JSON if you ever need to check or fix it
directly.
What you'll see when something is auto-approved¶
Whenever trust or auto lets a call through without asking, the shell
prints a line for it anyway — never silently:
That line is intentionally not dimmed, unlike the ordinary "remembered
shell pattern" auto-approve line you may already know from answering
[y/N/A=always] — this one was never approved action-by-action, only the
mode itself was chosen once, so it stays visible enough that you would
actually notice it scroll by.
Scope¶
Permission modes apply to the interactive keryx shell session only:
keryx harness run/keryx harness exec/keryx servekeep using the formal policy-profile engine described in the harness page — completely unaffected by this feature.- The MCP server (
keryx mcp) does not consult permission modes at all; an inbound MCP tool call from another agent is a separate code path. - No remote or headless caller can set
trust/autofor you. The mode is local, in-session, human-set state.
See also¶
- The agent harness — the policy engine these modes sit above.
- Run an agent without giving it your machine — the OS sandbox, an independent containment layer: it bounds what a command can touch regardless of whether it was asked about.
- Review a pull request with nobody at the keyboard — the
review-autonomousprofile:trustmode underreview-auto, with every question answered by a recorded safe default.