DocsWhat may it doApprovals and delegation

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.

approval.loopsingle use, claimed by fingerprint
Agent actscheck returns ask
pauses
Approval raisedbound to the exact fingerprint
waits for
A named humanCLI, console, or chat
on grant
Agent retriesgrant is claimed, then spent
the agent retries the same action; there is no approval id to carry back

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 + environment

A 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.

json
{
  "effect": "ask",
  "approvalId": "a_7f31c2",
  "reason": "Auth and session code changes need a second pair of eyes.",
  "approvers": ["security-team"]
}

What the human does

bash
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

Allows this one, and proposes letting this agent do it without asking. It names only the agent that asked, because widening what one agent may do is the direction a single answer must not stretch.

Allow once, always ask

Allows this one, and proposes that a person is always asked first, for every agent.

Always deny

Denies this one, and proposes that no agent ever runs it.

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

toml
[[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 = 2

Grants 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.delete and database.drop from 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

bash
memnox approvals

Each 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

approvers

Who can, with the tightest sufficient ceiling first, so a write to one database routes to that service's owner and not to the head of engineering.

denied

How much bearing evidence the asker was not entitled to see. An actor that may act but may not know still gets allow, and is told what it missed.

Routing 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.