Approvals and delegation
ask is the middle outcome, and in practice the one used most. Denying is for
what is never acceptable; approval is for what is usually fine and occasionally
catastrophic.
Who it reaches
A policy that says approvers: ["security-team"] is only as good as the system's
ability to turn that into people who will actually see it.
Ownership in Memnox is a tie in the graph, person owns decision, not a field
somebody typed into a spreadsheet. It comes from the same evidence everything
else does: who made the call in the thread the decision was extracted from, who
was named as the approver, who has been resolving incidents on that system.
A grant is bound and single-use
An approval is bound to the exact action fingerprint:
agent + action + target + environmentA grant for code.modify payment/checkout.ts in production by local-editor
authorizes precisely that, not the same edit by a different agent, not the same
agent in staging, not a different file. And it is marked spent the moment it
authorizes an action. Approving "write this file" authorizes that write, not
every write of it until a TTL expires.
That is the difference between an approval and a permission, and it is why an approval trail is worth reading.
What the agent gets
An agent handed ask receives an approvalId and the reason. It
does not receive an error. A refused action and a paused action are different
things, and an agent that cannot tell them apart will retry the wrong one.
{
"effect": "ask",
"approvalId": "a_7f31c2",
"reason": "Auth and session code changes need a second pair of eyes.",
"approvers": ["security-team"]
}What the human does
memnox approvals # what is waiting, with the id and how long it has waited
memnox approve <id>
memnox deny <id>The hold is a file under ~/.memnox/pending/, which is the whole point: the
terminal an agent is held in is not the terminal a person is sitting at. A
second terminal answers it, and the answer carries the name of whoever gave it.
First answer wins. A second one is not an error, because two people reaching for the same approval is ordinary; the loser is told what already happened rather than shown a failure.
In the console an approval waits under Waiting on you, one page that shows a section only while something in it is owed, and the hosted half is what routes one to Slack with an identity attached.
Answering once, or for good
A call held on an enrolled machine reaches Waiting on you in the console, and
the message posted in the team's channel. The console answers
it one time: just this once, for this session, or no. The message in chat also
carries three answers that answer this call and propose a team rule at the same
time, and a script can send them to POST :ws/policies/proposals/from-held-call:
Answer
Always allow this agent
Allow once, always ask
Always deny
The rule is a proposal, never a publish. A second admin approves it, and the person who answered cannot be that admin, because they are recorded as the one who decided. Once approved, the rule carries who decided it and who approved it, and every machine's refusal says so. See A rule remembers who decided it.
What the agent does next
It retries the same action. That is the entire client-side protocol.
The gateway claims the grant by fingerprint, so the agent never has to carry
an approval id back, which is what makes the loop close for a client that has
nowhere to put one. A caller that does have the id may still send it as
approvalId on the check.
While it waits, the call is genuinely held: the agent's tool call has not returned. Every hold carries an expiry, because a hold with no end holds the agent for ever, and a timeout is a denial said differently so it reads differently.
Nobody at a terminal at all is its own outcome, reported as unattended rather than as somebody's denial. It still fails closed.
Quorum and time windows
[[policies]]
name = "production-deploy-two-person"
[policies.match]
actions = [ "railway.up", "vercel.deploy-prod" ]
environments = [ "production" ]
windows = [
{ days = [ 1, 2, 3, 4, 5 ], startHour = 17, endHour = 9 },
{ days = [ 0, 6 ], startHour = 0, endHour = 24 },
]
[policies.decision]
effect = "ask"
approvers = [ "eng-lead", "security" ]
minApprovals = 2Grants accumulate until the quorum is met. One person counts once, and a single denial ends it immediately. There is no "two out of three eventually". The window above asks for two approvers at 11pm on a Saturday and nothing extra at 2pm on a Tuesday, when the people who would catch a mistake are awake.
Asks nobody wrote a rule for
Not every ask comes from a rule. An action the agent has never taken before, a secret read followed by an outward send in one session, a session made wary by a tool result that read like instructions, a write outside the repository, and anything from an agent or server still on probation all ask on their own. They are answered the same way as any other hold. See What changed under you and Untrusted repositories and new agents.
What a grant never overrides
- a rule whose effect is deny, which is not a question and never becomes one;
- a state fact still in force, such as a freeze declared for an open incident;
- a non-overridable taint deny:
project.deleteanddatabase.dropfrom a tainted session are refused outright.
Each refuses the action and leaves the grant unspent, so the approval is still there afterwards and the audit shows it was not the approval that failed.
Fixing the command instead of arguing with the rule
Sometimes the rule is right and the command is wrong. The hold offers [e]:
[a] allow once [s] allow for this session [e] edit it [d] deny[e] opens the command with the cursor in it, so a wrong flag or a wrong target
is one keystroke away from correct, without killing the agent's loop.
The edited command goes back through the rules from the start. It is not an
approval of anything, because otherwise [e] would be the way around every rule
in the file.
How long they are waiting
memnox approvalsEach line carries how long it has been waiting. Rising numbers there are the earliest sign that a rule is routed to the wrong group: the rule is not wrong, the audience is. A rule nobody answers is a rule somebody eventually deletes instead of fixing, and the deletion is much harder to notice.
When the answer is somebody else
A gate answers may I. A team also has to answer who should. An agent that hits its ceiling has not failed. It has found the edge of its authority, and the useful next step is a route rather than a refusal.
An ask answers this by carrying who, rather than only that somebody has to:
Field
approversdeniedRouting to the tightest ceiling is deliberate. Send everything to the top and the top stops reading, and an approval nobody reads is a permission with extra steps.
Authority narrows as it moves. When work passes from one actor to another, it carries the intersection of what both hold, never the union. An agent cannot delegate what it does not hold, and that is checked when the delegation is issued and again when it is used, because the issuer's own authority may have been revoked in between. A person delegating to an agent delegates a slice of their authority, not their seat. The rule has no exceptions, because every exception is the same hole: a chain of handoffs that ends up holding more authority than anybody in it.
A handoff is not an approval. An approval asks whether an action may proceed; a handoff asks whether somebody will take the work. Only the people it names can answer, and only once.
Routing reads the role and its declared actions, so a migration goes to the release role rather than to whichever agent asked most recently.

