Permission
What a coding agent may run without asking you
An agent that can run commands has to be told what counts as a question and what counts as ordinary work. Get that line wrong one way and it prompts on every file read until the plan is unusable. Get it wrong the other way and it has quietly been allowed to do whatever it likes for an hour. The design worth looking at is not the prompt. It is the scope, the mode, and what a decision is worth once you have made it.
Disclosure: I build Ordewell, so this is the policy it applies rather than a survey of the field. The tool is free and Apache 2.0. The mechanics are worth arguing about whether or not you install it.
The unit of approval is a scope, not a command
The obvious design is a queue of exact commands: the agent wants to run
ls /tmp/foo, you say yes, and next time it runs
ls /tmp/bar it asks again. That design fails on contact with a
planning loop. A planner doing real research reads forty things in a row,
and forty prompts turn a useful feature into a wall of dialogs.
So the decision is keyed on a scope instead. Approving a read of
/tmp/foo/a.log grants /tmp/foo/*. Approving
az group list grants az group. The grant is
broader than the thing you approved, deliberately, and it is remembered for
the rest of the session so the same class of call never asks twice. Saying
yes once covers everything under that path or command family, including
arguments the agent has not thought of yet.
Three modes, and a floor under all of them
Underneath the scope there is a mode, and it is what you would set for a run you are not sitting next to.
- ask consults the human channel, and if there is no channel it denies. Headless and web runs have nobody at the other end, so in ask mode they refuse an out-of-envelope call rather than guess.
- allow grants everything the tier system did not already refuse. The tier rules, not this setting, are what is still holding the line.
- deny grants nothing beyond the list you approved up front.
The pre-approved list is honored under every mode, including
deny. That ordering is on purpose: it is an explicit operator
decision, not a default that a mode setting is allowed to override. In
practice this is the knob that makes an unattended run possible. You write
the handful of things this plan genuinely needs, set the mode to
deny, and walk away.
Every decision is labelled with where it came from, so a log or a UI can say why a call was allowed rather than only that it was:
type ApprovalSource =
'pre-approved' | 'remembered' | 'mode' | 'asked' | 'no-channel'
That list is the honesty of the feature: a call allowed by
mode is a standing rule, and a call allowed by
asked is a person.
A denial is remembered too
Yes and no get the same treatment. If a policy denies a lookup, that denial is kept, so a model that retries the same blocked call burns one tool round instead of putting the same question to you again. The refusal carries back as a normal, actionable tool result: the model is told the answer was no, which is information it can plan around, rather than being left to hang.
A call can touch more than one scope, and a no to all of them together is remembered as one refusal rather than several, because your no may have been about any one of them.
One ask per scope, and a session that forgets on purpose
A parallel tool round can produce several requests for the same thing at once. They collapse into one question: the first goes to you and the rest wait on the same answer, so a burst of parallel calls cannot become a burst of prompts.
Grants live for the session rather than forever. On reset the granted and refused sets are dropped, and an answer still in flight from before the reset may not write into the new state. A stale yes landing a second late cannot silently re-open something you cleared.
There is also an answer beyond yes and no. Where the requester offered a suggestion of a broader grant, you can allow it for this task alone. Where none was offered, that answer degrades to a plain allow: the code will not make a grant the requester never proposed, because a button is not a mandate.
What happens when nobody answers
This is where a research loop and a task runner get different treatment, deliberately.
A planner turn that blocks forever on an unanswered prompt would hang the whole research loop with no visible cause, so a planner prompt expires. The default is five minutes. On expiry the request resolves to denied and the model receives a normal tool result, which means the loop continues and the cause is legible. Nothing sits there holding the process open on a question nobody is watching.
A task runner's request is the exception. That session is parked on the
answer rather than looping, so it waits for a person however long that
takes. The cost is honest: a run left unattended in ask mode
with a runner waiting will sit there until you come back.
In both cases the request is cleaned up when the attempt ends. Every way an attempt can finish reaches the runner as a stop, and open requests are denied before the session goes, so nothing waits on a process that no longer exists. A request the runner withdraws is settled as denied so it comes off every surface showing it. Anything still outstanding is replayed to a surface that connects late, so opening the UI after the fact does not show an empty list.
Limits
- The floor is per session, not per task. A grant you make while one task is running is available to the next one in the same session. If you want task-level separation, that comes from the mode and the pre-approved list, not from the memory.
- Allow mode is only as good as the tier rules underneath it. It grants what those rules did not refuse, so if a command family is permitted there, allow mode lets it run.
- A prefix grant is wider than the example you approved. That is what makes it usable, and it is also the thing to read twice before clicking.
- There is no automatic escalation on a denial. The policy does not back off, retry with a different model, or widen its own scope after a no. The next move is yours.
- Grants do not survive a restart. They are held for the session. Come back tomorrow and the questions come back with you.
Source and design notes
A policy that answers out-of-envelope calls once, remembers the answer, and knows the difference between a standing rule and a person. The code behind that: the approval policy, the request bridge, runner permission requests, and github.com/ordewell/ordewell.