An agent runtime, without changing the agent
A firm whose agent runs on Claude, OpenClaw or any MCP client does not integrate AMW into the
agent. It replaces the tool the agent already has. @amw/mcp is that tool: five
functions over stdio, four of which are reads.
The tool list is the security argument
A tool list is a capability grant, so what is missing from this one is the point. There is no tool that signs a mandate, classifies a venue, names an authoriser or releases a hold. Those are four acts of authority, each a signature the contracts check, made by the owner or by an officer the owner named on chain. A model runtime holding any of them would make every other guarantee here decorative.
| Tool | What it does | |
|---|---|---|
amw_request_action | write | Puts one action to the guard and returns a verdict. The only way this agent can move value, and the only way it can move at all |
amw_headroom | read | What is left in the trailing window, read from the guard rather than counted |
amw_mandate_terms | read | The bounds the agent is working inside, with the clause each one is |
amw_venue_class | read | How the owner classified a venue. Unclassified is a tier 1 refusal, not a neutral |
amw_explain_flags | read | A flag word turned into the clauses a supervisor would cite, with no chain call |
What the firm actually does
Three things, and only the third touches the agent.
- The treasury signs a mandate. Once, from wherever the treasury signs things, which can be a Safe. The owner key never goes near the machine the agent runs on.
- The guard is approved as spender, and the agent key is left empty. Zero balance beyond gas, zero allowance. This is what makes the next step safe.
- The agent's swap tool becomes this server. The agent needs to know nothing about AMW: it calls a tool and reads what comes back.
Configuration
{
"mcpServers": {
"amw": {
"command": "node",
"args": ["/path/to/amw/packages/mcp/src/index.ts"],
"env": {
"AMW_AGENT_PRIVATE_KEY": "0x…",
"HEDERA_RPC_URL": "https://testnet.hashio.io/api"
}
}
}
}
AMW_AGENT_PRIVATE_KEY is the agent's. The server refuses to start without
it and says so in those words, because an owner key here would work perfectly and quietly
remove every guarantee this documentation makes. The process announces which key and which
guard it is bound to on stderr at startup, so an operator can see it without a debugger.
A hold is an answer, not an error
The one thing to get right, and the reason amw_request_action is described to the
model the way it is. A breach returns; it does not revert. So a held action comes back
as a successful tool result carrying an action id, the tier, the clauses and a sentence:
{
"actionId": "9",
"held": true,
"tier": "tier 2",
"clauses": ["3.4", "3.7"],
"flags": "0x140",
"flagNames": ["AGGREGATE", "VENUE_CONC"],
"summary": "Held before settlement on tier 2, clauses 3.4 and 3.7. The value did not
move and the action is on the record with its flags. A named authoriser
decides whether it is released or refused; nothing to retry."
}
An agent told this was an error would retry it, and retrying is the one thing that must not happen: the record already exists, and a human is being asked to decide about it. Only a call that did not happen at all — a malformed argument, an unreachable relay — comes back as an error.
What this does not protect against
Nothing about this server constrains what the model reasons, says or plans. It constrains what moves. An agent that is fully compromised through its prompt still holds a key approved for nothing, and still has one way to touch value: a contract that derives the venue class and the notional itself and refuses what the owner did not sign for. The security model states the ceiling as a number.
And the limit that applies to all of AMW applies here: the hold exists where AMW is the spender, which is on-chain ERC-20 movement through the guard. For card and fiat rails AMW can observe and score, and cannot stop. Limits says so at greater length.