When an action is held
A hold is not an error and not a rollback. The action returned, the value did not move, and the attempt is on the record with the clauses that stopped it. What follows is a person: the officer the owner named on chain, who releases it or refuses it and says why.
Everything else in this documentation is about the moment the guard decides. This page is about the hours after it, which is the part a firm actually operates.
What the agent sees, and what it must not do
The guard returns a verdict rather than reverting, so the agent gets a successful call carrying an action id, the tier, the clauses and the flag word. Through the MCP tool that arrives as a normal tool result with a sentence saying a named authoriser decides and there is nothing to retry.
Retrying is the one thing that must not happen. The record already exists, a human is being asked about it, and a retry sized just under the bound is the composition this system was built to catch. An agent that treats a hold as a transient failure produces exactly the pattern its owner is trying to prevent.
Who is told
A watcher on the deployment pages a held action to the receiver configured in
/etc/amw/paging.env, signed with an HMAC so the delivery can be checked rather
than believed. A hold that has already been resolved is not paged again.
Self-host carries the configuration.
Nothing about the decision depends on that page arriving. The hold stands until somebody resolves it: there is no timeout that releases an action, and no path that lets it settle because nobody looked.
The case file
The console assembles a case file for the held action: what was attempted, what the clauses admit, what the sequence before it did, and what the chain shows. It is written by a supervisory crew whose workers draft language and never originate a finding. The arithmetic and the decision to hold are the contract’s, and they happened before any model ran.
Every claim in it carries a reference that resolves. A claim whose citation does not resolve
does not render, which is why a case file is short rather than confident. The steps of the
assembly are append-only at the database grant level: no UPDATE, no DELETE, not for the
operator and not for postgres.
| Route | What it does |
|---|---|
| POST /api/investigate | Runs the crew for one held action, signed in as that mandate’s owner. Capped at three per owner per day and forty across the deployment: it is the only route here that spends money |
| GET /api/investigate/:actionId | What the crew is doing, or why it stopped. It reads behind the reader, so a case being assembled is visible only to somebody entitled to the instrument |
| GET /api/case/:actionId | The assembled file with every claim’s citation, under the same access rule as the instrument |
| GET /api/case/<action>/export | The whole record for one hold as a single file, behind the same gate |
The console draws the same evidence for a person: Download report, on the record beside the epoch reading, writes a PDF in the browser from what that screen is already showing. The instrument as it was signed, every clause it carries, every action under it with the stopped one marked, the case file with each claim’s citation and every struck claim with the reason it was struck, and the decision if one has been filed. It is written in the page, from the record the reader is entitled to, so it can never carry something that screen would not.
Who may read the detail is custody: the owner who signed it, an officer that owner named, or anybody at all once the owner has published it. The commitment on the topic is public either way, and it carries the existence of the hold and the class of clause without the notional or the counterparty.
Who may decide
Clause 4.2: a named release authoriser, required on any hold. The owner appoints one by signature and the appointment is on chain, so the contract is the authority on who may resolve, not a role in a database and not a session in the console.
No agent can become an authoriser, and no contract exposes a path to it. A fully compromised agent key can ask the guard for actions all day; it cannot release the one the guard stopped. There is no supervisor role in any contract, no admin key and no pause authority.
In the console the decision panel is readable by anybody who may read the instrument, and its inputs appear only for the key the owner named. A form nobody present may submit is an invitation to a dead end, so it says which key is missing instead.
The decision itself
The officer opens the instrument at /console/live, reads the case file, and
either releases or refuses. Two things are recorded with the
act: the name that stands behind it, and a rationale. The rationale is bound on chain by its
hash, so the text kept off chain cannot be edited afterwards into something the chain still
appears to endorse.
// What the officer signs. The officer is named in the message rather than
// recovered from it, because a contract wallet cannot be recovered from.
const message = {
actionId,
officer, // the address the owner appointed
released: true, // or false, which refuses it
rationaleHash, // keccak256 of the text kept off chain
deadline,
};
The signature goes to the relayer, which forwards resolveWithSig and pays the
gas. The officer’s key holds nothing and sends no transaction of its own: an authoriser who
has never touched a faucet can still resolve a hold, which is what makes naming a compliance
officer rather than an engineer a realistic thing to ask of a firm.
A release records a decision; it does not re-run the action. The agent is free to attempt the action again under the mandate as it stands, and it will be measured against the same bounds. Widening the exposure means a new mandate the owner signs, so every widening leaves a signature, an owner and a timestamp.
What the record refuses
The console is not trusted to describe what happened, so filing a decision is checked against the chain that already shows it.
| Attempt | Answer |
|---|---|
| A decision the chain does not show | 409. The transaction is the authority; the filing only annotates it |
| A second filing against the same transaction | 409. One transaction, one decision |
| A rationale filed under somebody else’s name | Refused. The actor is taken from the receipt, never from the request |
| Resolving as a key the owner did not appoint | NotAuthoriser, on chain, before anything is written |
Refusals lists every one of these with the clause behind it.
Afterwards
The decision joins the record: released or refused, by name, with the transaction and the
rationale’s hash. A supervisor handed that record checks it without asking the firm and
without asking AMW, by recomputing the digest against the topic on Hedera’s own mirror node.
The console’s checker at /console/verify does exactly that, and
the evidence spine explains what it is comparing.