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.

A hold consumes no budget. Window state is untouched when the flags are non-zero, so the epoch a sequence was working inside is exactly where it was before the held attempt.

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.

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

AttemptAnswer
A decision the chain does not show409. The transaction is the authority; the filing only annotates it
A second filing against the same transaction409. One transaction, one decision
A rationale filed under somebody else’s nameRefused. The actor is taken from the receipt, never from the request
Resolving as a key the owner did not appointNotAuthoriser, 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.

A hold is the product working. The failure mode this system exists to remove is an exposure nobody authorised assembled out of steps that each passed, and the visible cost of removing it is that somebody has to say, in their own name, that this one may proceed.