Skip to content

Agents

Every agent call in a ritual is one bounded piece of work: resolve this conflict, make this task pass, write tests for these lines, answer this thread. Every agent has a shell, and cabinet denies it no command.

The sandbox is the only boundary

A shell can run anything: mise exec or sh -c gets round any list of command prefixes. Nothing bounds paths either: Read, Grep and Glob reach any file the user can read. The prompts that carry other people's text (review threads, issue bodies) fence it as untrusted and ask the agent to stay in the repository, but a prompt is an instruction, and the text it fences is written by other people.

The sandbox the cast runs in, such as fence, is what bounds both the paths an agent reaches and the commands it runs. Run every cast, attended or not, inside one.

Permission modes

A cast is attended unless it runs under vekna cast --unattended, with --attended false, or under [defaults."cabinet:<ritual>"] attended = false in .vekna.toml. Attended, the mode is auto, with somebody there to be asked. Unattended, every agent call runs under Claude's dontAsk permission mode, so a cast at 3am never hangs on a prompt. A cron job must pass --unattended, or its agents run in auto with nobody to answer.

Roles

role may use
reader Read, Grep, Glob, Bash
writer reader + Edit, Write, MultiEdit

A reader gets no edit tools, but it is not read-only: its shell writes. The role says what the call is for. It limits which tools the agent holds, never which paths they touch, so it is no substitute for the sandbox.

What the ritual owns

The ritual commits, pushes and runs the long tasks, and makes every forge call through vekna's shell medium in the links adapters, including identify's labels, sub-issues and links. Its agent reads the backlog and hands back what each issue is, and the ritual puts that on. After every repair attempt the ritual re-runs the task the runner named as broken and hands the agent the output. The prompt tells the agent all this.

may_not_run

An agent that runs one test file, or one linter over one file, before it stops makes an attempt more likely to count. An agent that runs a sweep spends minutes on an answer the ritual is about to give it. So the prompt names the long tasks and asks the agent to leave them alone: the project's gate, coverage and fast_coverage, and every prefix in may_not_run:

[cabinet.agent]
may_not_run = ["mise run lint:py", "mise run test:py"]

The list is advice in the prompt, not a lock.