Lesson 5: What the agent may do without asking
What you'll learn: how to replace a permission prompt per task with one standing file per role: what the agent may do without asking, what it must ask before, and what done observably means, plus the one rule for the gap between them.
What you need: the specs/ directory from Lesson 1 with one ready spec to hand over (acceptance lines you can run, an Out of scope list, and a status you flipped to ready), one home per stack from Lesson 4 or a single repository, and the agent you already use.
Time: an hour, once. Cost: zero new dollars.
The idea#
You handed the agent a task and spent twenty minutes answering prompts. May I create this file, may I run the tests, may I install this package. You said yes to all of them, and the one that mattered, a package pulled from the network, went by in the same rhythm as the rest. The next week you turned the prompts off, and the agent, meeting a question nobody had answered, guessed: it changed a route the frontend depended on to make the spec easier to satisfy. Both failures have one cause: the limits lived in your head, applied one interrupt at a time, tired, or not at all. Write them down once, in daylight, in three sections: what this role may do without asking, what it must ask before, and what done means. Add one rule for everything in between, come back with the question, and each session starts from the bounds you set on your best day. A charter is the usual name for a file with those three sections.
Why start here#
Lessons 1 through 4 gave the agent a spec to build from, a decision record to consult, a check that proves the work, and one home per change. Each bounds what the work is; none bounds what the agent may do while doing it. That gap is where the prompts come from, and closing it is what the next four lessons assume. The shape I use, one file per role with authority written down in advance, is an idea I took from Clief Notes; the sections below are my version of it.
The template#
One file per role, beside the standing file in the folder you launch sessions from (Lesson 4's parent folder), read before the first command; the builder's is CHARTER.md, later roles get CHARTER-<role>.md:
# Builder: standing authority
Scope: one change at a time, from one ready spec, inside one home.
## You may, without asking
- Pick names, layout, and patterns, and use any library already in the project.
- Create or move files wherever the spec's scope reaches, and delete the ones it makes obsolete.
- Cover what you change with tests, and update the docs that describe it.
- Run any command that only reads, builds, or tests, inside your own checkout.
- Record a decision, in the project's decision records if it keeps them, when you choose between two reasonable options.
## You must ask before
- Adding a dependency the project does not already have, or any new external service or cloud resource.
- Changing an interface the spec names as existing: a route, a schema, a public function.
- Deleting data, running a migration, rotating a secret, or any step you could not undo.
- Rewriting history: force-push, branch deletion, amending a commit you did not make.
- Touching a second home: another repository, another stack.
## Done means
- Every acceptance line in the spec passes and its verify command exited 0, both run in this session, and the closing message says so.
- Nothing listed under the spec's Out of scope changed.
- The work is on a fresh branch, pushed if the home has a remote, one commit per repository.
- Your closing message answers four questions in this order: what shipped; which choices you made between reasonable options, and why; what the spec could be read to demand that you left out, and why; which follow-ups deserve a spec of their own.
## When you are not sure
Come back with the question. Write it in one line, name the two answers you can see, say which you would pick and why, and stop. Never guess, and never narrow or widen the task to avoid asking.
Add this to the file your agent reads at session start, under the lines from Lessons 1 through 4:
Read CHARTER.md before starting any task; it is your standing authority. Anything under "You must ask before" stops the task until I answer.
Each part, and the mistake it prevents#
The scope line is one sentence because the file is per role, not per task; rewritten per task, it is a prompt with extra steps.
The may list is what you would have said yes to twenty times; writing it down is what lets you turn the prompts off. Its last line matters most: an agent that may record a decision has somewhere to put a judgment call that is not your question.
The must-ask list is your scars in noun form. Every line names a command or a category you can recognize in a diff, because a line you cannot check against the command and the diff is not a limit, it is a mood. "Anything risky" fails that test; "rotating a secret" passes it, and the category at the end of that line covers the noun you forgot.
Done means is the section people skip, and the one that ends the "and also" review. The four-part closing message is the agent grading itself against the spec before you do; the third part, what it did not do and why, is where an unannounced scope cut surfaces.
The last section is the rule for the gap three sections cannot cover. The two-answers form matters: a bare question hands you the work; two candidates and a pick hand you a decision.
A worked example#
A scratch project with the four homes from Lesson 4, the file above in the parent folder, the standing line, and one ready spec, specs/api/002-health.md: a health() function that returns {"ok": true} and a test that proves it, with the server wiring, web/, and infra/ under Out of scope. A fresh session, given only the standing file and "implement specs/api/002-health.md, commit on a branch":
`health()` now returns `{"ok": True}`, and the work is committed on branch
`002-health` [the session's branch name, shortened] in `api/` (commit
`3eb7c00`, off `main`). `python -m pytest
api/tests -q` ran from the project root in this session and exited 0 (1
passed).
I changed two files, both inside the api home: `api/src/health.py` and
`api/tests/test_health.py`. The test asserts the return value. Nothing under
the spec's Out of scope changed. The commit is local only, because `api` has
no remote to push to. I staged the two files by path, and the new
`__pycache__` folders are left untracked.
**Choices between reasonable options**
- **Test imports:** the test adds `api/src` to `sys.path` itself instead of
using a `conftest.py` or packaging config. That keeps the change to two
files and adds no dependency. The spec's verify command runs from the
project root, which is why an import path is needed at all.
[one bullet about a commit hook on my machine omitted]
**Left out:** I added no HTTP route, framework, or packaging, since the spec
rules those out.
**Follow-ups for their own spec:** wiring `health()` into a route, and adding
a `.gitignore` for `__pycache__` and `.pytest_cache` in `api/`.
A second run asked for the same function plus an uptime ping to a hosted status page every minute. The session read the must-ask list and stopped before touching anything:
I haven't changed anything yet. The uptime ping needs your answer first, and
CHARTER.md says that stops the task.
**Question:** The hosted status page is a new external service, and a
per-minute ping is a scheduled job that would live in `infra/` or `api/`. Do
you want a spec for the ping first, or should I build it as a scheduled job
inside `api/`, with the status-page URL and token supplied by you?
- **A (spec first):** You write or approve a spec that names the status-page
provider and the home. `specs/api/002-health.md` covers only the bare
`health()` function, and it lists infra/ and HTTP wiring as out of scope.
I'd do the health function now on a branch and leave the ping for its own
spec. I'd pick this, because the ping is a second change that adds an
external service, and the layout skill says to write the spec first.
- **B (ping inside `api/`):** You tell me which provider to use and approve
the new service, and I add the ping to `api/` as a scheduled job. It would
need a spec amendment, since 002 currently excludes anything beyond the bare
function.
I can start `health()` and its test on a fresh `api/` branch right away if you
say go.
Nothing was edited; all four repositories were clean afterward. Inside the bounds it finished and reported its cut; across a line it asked; neither run guessed.
How it scales#
The file grows by role, not by length. On June 9 I wrote one for each role the project had, five files in one afternoon: builder, content, monitor, task runner, interface review. The sections were the same in every one; only the nouns changed. A monitor's may list is read-only commands and a notification; its must-ask list starts at "any write". Copy the builder file first; most other roles are narrower versions of it.
Who does the work: you write the must-ask list and the done section, your risk and your definition of finished. The agent drafts the may list from what it did last week, and you strike lines. The file changes the day a line is wrong, with a decision record saying why (Lesson 2), never mid-task.
From the field#
One of these files sat in front of an unattended agent on a small always-on Linux box at home, with the must-ask lines copied into a tool-level deny rule so the file was not the only guard. The agent issued a command that was on the must-ask list. The rule denied it before it ran, and the agent honored the deny instead of routing around it. The file alone had not stopped the attempt. The rule did, and the agent respected the refusal: the first time I saw the line hold with nobody at the keyboard.
Exercise#
- Save
CHARTER.mdbeside the standing file and add the line. - Rewrite the must-ask list from your own scars. Every line names a command or a category you could spot in a diff.
- Ask the agent to propose the may list from the tasks you gave it last week. Strike anything you would still want to see first.
- Hand it one ready spec in a fresh session, with the standing file only. Read the last message's four parts against Done means, then the diff.
- Then ask for something that needs a must-ask line, the uptime ping above will do, and confirm it stops with a question and two answers.
You're done when#
The session either finished inside the bounds, with all four parts in its last message, or stopped with one question and two answers; it never guessed. Two results fall short: it asked about something on the may list, so a line there is unclear; or it did something on the must-ask list. For the second, check whether the standing line was in front of it; if it was, the agent crossed a line it read, and that is what the tool-level deny rule is for.
Common mistakes#
- Adjectives on the must-ask list. "Careful with production" is not a checkable line.
- A file per task. A role's authority outlives any task.
- No done section, so review becomes the definition of done.
- Treating the file as enforcement. It is the source; a tool-level deny rule (Lesson 7) is the copy that holds when the agent does not.
An hour once, and zero new dollars; from then on each task costs you one four-part message to read before the diff, or one question to answer, instead of twenty prompts.
Further reading#
Saltzer and Schroeder's The Protection of Information in Computer Systems states least privilege, and the must-ask list is that principle for an agent. The Claude Code permissions reference shows the allow and deny rules that enforce the same list at the tool level. The AGENTS.md convention is where the standing line goes in tools without a settings file.
Next lesson#
A queue an agent can drain: work items with an acceptance line, a status, and a pick-next rule, so the file above governs something.
Questions this post answers
- Why write a standing authority file instead of approving each agent action as it comes up?
- Because a prompt per action is graded in the moment, and after twenty yeses the one that mattered goes by unread. A file that says what the agent may do without asking, what it must ask before, and what done means is written once, and every session reads it before the first command. The agent is told to finish inside the bounds or stop with a question, never to guess, and the exercise checks that it did.
- What goes in the must-ask list?
- Your own scars: new dependencies and external services, interface changes the spec did not name, anything you could not undo, history rewrites, and touching a second home. Concrete nouns, not adjectives. If a line cannot be checked by reading the command, it belongs on the may list or nowhere.
- Does the file actually stop the agent?
- The file tells the agent where the line is, and a well-instructed agent honors it. A tool-level deny rule enforces the same line when nobody is watching; the lesson shows one holding on an unattended box. Write the file first; the deny rule is built from the must-ask lines a tool can check.
- Who writes it?
- You write the must-ask list and the done section, because those are your risk and your definition of finished. The agent proposes the may list from what it did last week, and you strike lines from it. Under thirty lines, an hour, once.
Previous lesson: Lesson 4: One home per stack