Unattended Approvals
A risk-tiered step waits for a human. But when a run is unattended — a daemon tick, a schedule, a fleet loop — nobody is there to answer inside the next few minutes. MUR parks the request instead of burning a timeout against it.
mur channel approve fleet-builder hitl-01J... # approve, whenever you get to it
mur channel approve fleet-builder hitl-01J... --deny
mur fleet run builder # re-run: it continues from there
What happens when nobody answers
The gate writes a durable request into the run's signed channel and returns immediately:
- the step is blocked — not failed, so nothing is reported as broken and no
on_failurehandling fires; - steps that do not depend on it still run to completion, so the run gets as far as it legitimately can;
- the channel stays
input-required, so every surface keeps showing that a human still owes it an answer; - a
--looprun stops rather than paying for another iteration that would arrive at the same unanswered question.
mur job status and mur fleet status report the run as blocked. That is deliberately not a terminal state: an approval resumes it.
Approving later
Approve from Hub's Needs You card or with mur channel approve <channel> <hitl_id>. Then re-run — the next fleet run, loop iteration, or schedule tick picks up where the last one stopped, and steps that already completed are skipped.
This works because an approval is matched on the action's content hash, not on the request id. The id is minted fresh every call, so an id-keyed lookup could never connect your answer to the next run.
Two consequences fall out of that, both intended:
- Change the action and the approval no longer covers it. Different bytes, different hash — nothing you did not see gets executed on the strength of something you did.
- A denial sticks. It settles the action for the same window rather than being re-asked every iteration.
Approvals and denials count for 7 days. The hash bounds what was approved; the window bounds how long ago.
Declaring a policy in fleet.yaml
hitl:
mode: defer # defer | wait | deny
auto_approve_tiers: [read, write] # standing responsibility, capped at write
mode — what an unanswered gate does. Absent, MUR decides by whether a terminal is attached. State it explicitly when that is a poor proxy for somebody is watching:
mode | Use it when |
|---|---|
defer | nobody is watching in the next few minutes (the unattended default) |
wait | the run is headless but a person is on Hub — poll for the answer |
deny | this fleet must never reach for a human; fail immediately and legibly |
deny is a floor, not a preference. It refuses before consulting anything, so neither an older approval for the same action nor a stray --yes can lift it.
auto_approve_tiers — risk tiers whose actions this fleet's owner has pre-approved, so they run without asking.
It is an explicit list rather than a ceiling, and it is capped at write. spend, destructive, privileged and network-egress cannot be granted here at any tier: their cost is not something noticing afterwards can undo. mur fleet run refuses a fleet that lists one, rather than ignoring it quietly — a config line you believed took effect is worse than one that errors.
Three things stay true whatever you grant:
- Your explicit no to an action outranks a standing grant for its tier. A decision someone actually made beats a blanket one made in advance.
- Auto-approvals are still audited. The gate writes the request and its response to the channel with
surface: policy, naming the tier it came from — so what did this run do without asking me? always has an answer. - There is no blanket auto-approve.
--yesexists for interactive runs and is not reachable from an unattended fleet path.
See also
- Fleet Loops — what ends a loop, and the guards around an unattended one
- Agent CLI — approving a tool call mid-conversation, which is a different gate