Yolo profiles
An unattended run stops at every tool call that needs approval, and waits for a person who is
not there. Bare --yolo fixes that by approving every call the config does not deny. That is a
single bit, and it is blunt for a run let loose on a real repository.
A yolo profile is that bit taken apart. You give a set of approval rules a name in yolo.toml,
then pass the name when you launch. Some calls run unprompted, some still raise the ordinary
approval prompt, and some are refused outright.
lev yolo init # write an example yolo.toml to start from
lev run coder --yolo=careful # run under the profile named "careful"The equals sign is required. lev run --yolo careful still means "run the agent careful
under bare --yolo", so the name has to attach to the flag.
Tip
Want a walkthrough rather than a field list? See Write your first yolo profile.
Where the file lives
yolo.toml sits beside config.toml, under the data root. When LEVIATH_CONFIG_PATH points
somewhere else, yolo.toml follows it. lev yolo init writes the file and tells you the path.
Each top-level table is a name you can pass to --yolo=. default is reserved, because bare
--yolo is not configurable. Names are letters, digits, _ and -.
What a profile looks like
This is the example lev yolo init writes, with its comments trimmed. The
live copy is the one the tests check.
[careful]
default = "ask" # allow | ask: a call no rule below names, when the config would ask
questions = "ask" # ask | auto: ask_user_*, present_for_review, edit_document
checkpoints = "ask" # ask | auto: stage checkpoints
gate = "auto" # ask | auto: taint-gate prompts, where taint tracking is on
[careful.tools]
allow = ["@builtin"]
ask = ["web_fetch", "install_tool"]
deny = []
[[careful.shell.allow]]
command = "cargo *" # leading words, one glob per word
[[careful.shell.allow]]
command = "rm -r*"
args = ["target/**", "-*"] # every remaining word must match one of these
[[careful.shell.ask]]
command = "git push*"
[[careful.shell.deny]]
command = "curl"
[build-only]
default = "ask"
[build-only.tools]
allow = ["read_file", "read_files", "list_dir"]
[[build-only.shell.allow]]
command = "cargo build*"The keys
| Key | Values | What it decides |
|---|---|---|
default |
allow, ask |
A call no rule names, when the config would ask. Required |
tools |
allow, ask, deny lists |
Verdicts by tool name, glob or group |
shell |
allow, ask, deny rule tables |
Verdicts by shell command line |
questions |
ask, auto |
Tools that wait on a person. Defaults to auto |
checkpoints |
ask, auto |
Stage checkpoints. Defaults to auto |
gate |
ask, auto |
Taint-gate prompts. Defaults to auto |
default has no fallback on purpose. It is the key that says what the profile is for. A file
that omits it fails at load, rather than quietly becoming bare --yolo.
There is no deny value for default. A profile that refused everything it did not list
would be a denylist by omission. A call you want refused goes in a deny list, where you can
read it.
How a verdict is reached
Rules are checked in this order, and the first match wins.
| Order | Condition | Outcome |
|---|---|---|
| 1 | The config denies the tool | Refused. No profile lifts a deny |
| 2 | A tools or shell deny entry matches |
Refused |
| 3 | An ask entry matches |
The ordinary prompt, unless --allow named the tool |
| 4 | An allow entry matches |
Runs unprompted |
| 5 | The config already allows the tool | Runs unprompted |
| 6 | Nothing matched | The profile's default |
Two consequences are worth reading twice. Rows 2 and 3 sit above row 5, so an ask or deny
entry tightens even a tool the config allows. Row 3 yields to --allow on the command line,
because the flag is the most specific thing you said, but row 2 does not.
lev yolo test reports the verdict and names the rule behind it:
lev yolo test careful --tool shell --command "rm -r target"Tools lists
An entry in a tools list is one of three things. A tool name in any of its spellings, so
bash covers shell. A glob, when it contains *, ? or [. Or one of the groups
@builtin, @subagent, @scripts, @mcp and @all, described in
tool groups.
A glob is the way to name a set of MCP tools, since those are always spelled
<server>__<tool>. A tracker server's tools all match tracker__*.
Shell rules
Shell rules refine the shell tool, so one profile can run your build and still stop at a
push. Each [[<name>.shell.<verdict>]] entry takes a command and an optional args.
command is the leading words of the line, one glob per word. rm -r* covers rm -rf, and
cargo * covers every cargo subcommand. args is a list of globs that every remaining word
must satisfy. Leave args off to accept any remaining words, or set it to [] to accept none.
A word starting with - is matched as text. Every other word is treated as a path, and is
matched only as the path it really names. Leviath expands ~, joins the word to the run's
workdir, folds .., and follows symlinks as far as the path exists. So ~/scratch/../.ssh
does not pass a ~/scratch/** pattern, and neither does a link pointing out of that tree. A
relative pattern means "under the workdir", and an absolute one means what it says.
A line is judged one command at a time and takes the verdict of its strictest command. So
cargo test && curl x is only as free as curl. A command no rule names takes the tool-level
verdict for shell. A command that redirects to a file also answers to whatever the profile
says about write_file.
Some lines cannot be matched by an allow rule at all, because their words do not decide what
runs:
| Line | Why no rule can let it through |
|---|---|
PATH=x cargo build |
It binds a variable in front of its program |
rm -r $DIR |
One of its words is an expansion, so its real target is unknown |
trap "..." EXIT, alias, function, eval, source |
The program is one no rule can name |
cargo build `whoami` |
The line cannot be parsed ahead of time |
The first three report that the rules cannot vouch for the command. The last reports that the
line cannot be read at all. Either way they take the tool-level shell verdict when the profile
has no shell rules. When it has any, they ask, because the rules cannot be checked and a person
can.
An explicit rule does not rescue them. A profile with command = "trap" in its allow list
still asks about trap "..." EXIT.
The human knobs
questions, checkpoints and gate decide whether a mechanism reaches a person. All three
default to auto, because a profile is a kind of --yolo until it says otherwise.
questions = "ask" keeps the tools that wait on a person advertised, and their calls come to
you. Those are ask_user_*, present_for_review and edit_document. Under auto they are
not offered, and a stray call is answered for the model.
checkpoints = "ask" opens every stage checkpoint. Under auto they approve themselves,
apart from any the blueprint marks unattended = "ask". gate is the same choice for
taint-gate prompts.
When a profile is read
A profile is read when a run spawns under its name, and again when that run resumes. It is
never re-read mid-batch. An edit therefore reaches your next lev run, and a parked run you
lev resume, but never the run that is already going. A rule an agent somehow changed cannot
apply to the run that changed it.
questions, checkpoints and gate are decided when the run is built, so they reach the next
run only.
A name the file does not have fails the spawn before the daemon is asked, and lists the names
the file does have. Sub-agents, fan-out workers and recovered runs inherit the profile by name,
so a child of a careful run is careful rather than bare --yolo.
What a profile can never loosen
A profile adds holds. It never removes one that is already there.
| Hold | Who sets it |
|---|---|
| A denied tool | tool_permissions in the config |
required_tools |
The blueprint |
unattended = "ask" |
The blueprint, per interaction point |
With [security] lock_permission_files on, which is the default, no run's tools may write
yolo.toml. See configuration for that setting.
The other surfaces
| Command | What it does |
|---|---|
lev yolo list |
Every profile in the file, with a summary of each |
lev yolo show <name> |
One profile as TOML, plus what it holds |
lev yolo test <name> |
The verdict for a call, and the rule behind it |
lev yolo init |
Write the example file |
Full flags are in the CLI reference. The same four questions are on the API.
On the new-run screen in lev dash, Ctrl-Y steps through off, plain
--yolo, then each profile in file order, then off again. Switching it on asks you to confirm,
because that step is the one that removes prompts. Every step after it adds them back, so none
of those confirm.