GuidesStanding rules
Standing rules#
How does an agent stop asking for the same request every time?
On this page
A standing rule lets one shape of request through without asking, for 1, 7 or 30 days. The agent proposes it; a person turns it on with their passkey on the vault page, and nothing changes before that. A rule covers one credential, the one key that asked (the machine's own, or one workload's), one method, one host, one path or path pattern, the same query parameter names and, for GraphQL, the same mutation fields. Every other request on that credential still meets its policy as before.
Standing rules are off until the vault switches them on for your workspace, and never on in a managed workspace. Where they are off, mehen policy propose, preview and ls answer forbidden, "Standing rules are not switched on for this workspace yet.": ask for each request as usual (When a request needs approval). The commands on this page need mehen 0.3.12 or later (mehen version --json says which you have); mehen update --json installs the latest release.
When to propose one#
Propose a rule when the same request shape keeps needing approval: the human has approved the same kind of Linear comment three times today and will be asked again. Do not propose one for a request you made once, for a request the human denied, or to get round a refusal.
Propose a rule#
From a request the vault held or approved, by its approval id (from exit 3 or mehen wait):
mehen policy propose --from apr_7Hq2 --reason "Post review comments on Linear issues while this sprint runs" --jsonThe rule serves the context that request was asked in: its project and environment, or none, and whether it came through mehen fetch or a mehen run placeholder.
From an example request, when there is no approval to point at:
mehen policy propose --item linear-work --method POST --url https://api.linear.app/graphql --content-type application/json -d @comment.json --reason "Post review comments on Linear issues while this sprint runs" --jsonThe vault checks an example as its relay would check the real request, and sends nothing. Without --project <prj_id> --env <name>, the rule covers only requests made outside any project, and mehen says so; --no-context asks for that explicitly.
--pathpicks one of the vault's path choices: the request's own path (the default) or a pattern with*in place of one id segment.--for 1d,7dor30dsets how long it lasts; 7 days is the default.--reasonis required. The person approving reads it as the agent's words, not checked by the vault.
mehen policy preview takes the same flags, needs no --reason and creates nothing: it prints the terms, the path choices and, when the request cannot become a rule, the reason it asks every time. The MCP tool is vault_policy with action: "preview" or "propose", and from, or item, method and url (with body or bodyBase64, contentType and context), plus path, for and reason.
propose returns an ask titled "Allow from now on". Write the block as for any ask (the check is the binding code), then mehen wait <pol_id> --timeout 60 --json. mehen policy status <pol_id> reads it once without waiting, and mehen policy cancel <pol_id> withdraws it. A proposal lasts a day. Proposing the same terms with the same reason again while one is pending returns that proposal; a different pending proposal for the same credential is a conflict, so cancel it first.
What the human sees and signs#
A person meets a rule in one of two places:
- The approval page of a held request, when the request could become a rule, offers Approve once and Approve and allow this from now on. When the request cannot, a muted line says why ("This request sends mail or a message, so it asks every time.").
- The proposal page,
/w/<slug>/p/<pol_id>, shows the agent's reason quoted under "Written by the agent. The vault does not check it.", with Approve with passkey and Deny.
Both show the same panel, "From now on, without asking you":
| Line | What it says |
|---|---|
| Credential | The slug, and the account it acts as |
| Request | The method and host, and for GraphQL "GraphQL mutation commentCreate only" |
| Path | The vault's path choices, narrowest first |
| Who | "This machine, sam-laptop" or "Only task name on sam-laptop": always the key that asked |
| Context | The project and environment, when the request named them |
| How long | 1 day, 7 days (the default) or 30 days |
| Still asks | "Anything else on this credential, and any request that sends, pays, shares or deletes" |
| Ends sooner | "if access changes or this credential is edited" |
The vault builds these terms from the request it stored, never from the agent's description. The page checks its copy against the vault's before Approve turns on, and the passkey signs the digest of the terms with the path and duration chosen.
Narrowing. The person may pick a narrower path (the exact path in place of a pattern) or a shorter duration than the agent proposed. Neither can be wider or longer than the proposal.
Who turns it on#
The machine's owner approves a rule for their own machine; for a team's machine, anyone who may approve that machine's ask does. An owner or admin never turns a rule on for another member's machine.
Co-sign. When the person approving also manages the credential's home (an owner or admin for a workspace credential, a manager of the project for a project's, the owner for a private one), one passkey is enough: Sam approving for their own machine in their own workspace makes the rule at once. When they do not, their approval only proposes it. On the approval page the button reads Approve once and ask a manager to allow it from now on: this request is approved once and a proposal opens for the credential's home. A manager of that home, other than the person who approved, co-signs it on the proposal page. mehen policy status <pol_id> says which stage your own proposal is at. For one opened from an approval page, mehen wait <apr_id> gives its link and code, and names the rule once it is co-signed.
A guest's machine cannot propose a rule, and nobody can make one in a managed workspace, where the platform decides every held request.
How long it lasts, extending and stopping#
A rule ends at its deadline, which is never more than 30 days away. It ends sooner when:
- someone stops it;
- access changes under it: the person who approved or co-signed leaves, changes role or loses the right to decide, the machine is revoked or rescoped, the workload ends, or a protected environment starts to reference the credential;
- the credential is edited.
The credential's page and the machine's page list rules under "Allowed without asking": who, the request, who approved, uses, last use and end, with Stop and Extend. Stopping only narrows, so it needs no passkey; the machine's owner, the person who approved or co-signed, any manager of the credential's home, a manager of the project the request was asked in, and an owner or admin may stop a rule, as may a member of the team for a team's machine (never a guest). The agent stops its own with mehen policy revoke <rul_id> (MCP vault_policy revoke); mehen policy stop is the old name.
The machine's owner may Extend a rule with a passkey, which sets its end to 1, 7 or 30 days from now. When they do not manage the credential's home, a manager of it co-signs the extension, as for a new rule.
How the agent knows#
- A request a rule allowed answers as usual, plus the response header
Mehen-Rule: rul_....mehen fetchadds "allowed without asking by rule rul_..., ends ..." on stderr;mehen fetch --jsonandvault_fetchaddrules: [{ id, endsAt }].endsAtis null when the vault could not say;mehen policy lshas it. mehen policy ls(MCPvault_policyls) lists the rules of the key that asks, live first: the request, the context, which key, the end, uses, last use and status, with an ended rule's reason (expired,revoked,access-changedorcredential-changed).--item <slug>narrows it to one credential, andmehen policy show <item>adds that credential's live rules.- After Approve and allow this from now on,
mehen wait <apr_id>names the rule ("Allowed from now on by rule rul_... until ..."), or the proposal's link and code while it waits for a co-signer. - When a rule ends between the vault matching it and sending the request, the answer is
conflict, "The rule that allowed this request has ended; send it again to ask for approval.", and nothing was sent. Send it once more: it is held for approval as usual. Do not retry in a loop.
What never becomes a rule#
The vault refuses a rule, and the request asks every time, when:
- the credential holds a live key (a Stripe
sk_live_orrk_live_secret); - the request sends mail or a message, pays or moves money, shares or publishes something, changes credentials or access, or sets up something that keeps running;
- it deletes or removes something: any
DELETE, or a GraphQL mutation whose fields name deleting, archiving or revoking, such asissueDelete; - the credential's policy denies it, or the credential's own rules ask for it every time;
- the vault cannot read what it does: its path names something sensitive (a token, a key, a webhook, a member, a role), its body is not sent as
application/json, or is a batch, a JSON-RPC call, a method override or encoded, its query string cannot be pinned down, or a GraphQL body is not one mutation the vault can prove field by field; - it goes anywhere but a standard HTTPS address, uses a method other than
GET,HEAD,POST,PUTorPATCH, or reaches wider than the credential's binding lets a rule name.
A rule also never applies to a lease or a reveal, to a protected credential, environment or run, or to anything the policy denies. It only turns a request the policy would hold into one that goes through.
Example: Linear comments for a week#
An agent reviewing a sprint posts comments on Linear issues with linear-work, a Linear API key. Linear takes every write as a GraphQL mutation to POST https://api.linear.app/graphql, and the Linear template holds every mutation for approval. The agent keeps the mutation text the same and puts what changes in variables:
{
"query": "mutation CommentCreate($input: CommentCreateInput!) { commentCreate(input: $input) { success comment { id } } }",
"variables": { "input": { "issueId": "ENG-412", "body": "Reproduced on main; the fix is in the linked PR." } }
}- The first comment is held (exit 3,
apr_7Hq2). The human approves it once, and it goes out. - The second and third are held too. The agent proposes a rule from the last one:
mehen policy propose --from apr_9Kd4 --for 7d --reason "Post review comments on Linear issues while this sprint runs" --json, and writes the block. (Sam could equally have picked Approve and allow this from now on on any of those approval pages.) - Sam, who owns the machine and the workspace, opens the proposal page. It reads
POST api.linear.app/graphql, "GraphQL mutationcommentCreateonly", "This machine, sam-laptop", 7 days. Sam ticks the code and approves with a passkey. - For the next 7 days, a
commentCreatefrom sam-laptop with the same fields (success,comment,id) goes through without asking, whatever the issue and the text. Each response carriesMehen-Rule: rul_..., and each use is counted on the credential's page. - The agent then tries
issueDelete. That mutation is notcommentCreate, so the rule does not cover it, and it is held for approval as before. It could never become a rule either: it deletes.