Fund-moving confirm
Every tool that moves money is gated preview then execute. Why it exists, what it covers, and the one exception.
You tell the agent: "swap 100 USDC to ETH." The agent doesn't just do it. First you see a confirm card with the trade details:
Sell: 100 USDC (Ethereum)
Buy: ~0.0286 ETH (estimated, 0.5% slippage)
Route: Uniswap V3
Gas estimate: ~$1.20You read it, decide it matches what you meant, and accept. Only then does the agent broadcast the transaction.
Why doesn't the agent just do it? Because LLMs misread. "100 USDC" can become "1000 USDC", an address you typed can drift one character, the chain can quietly hop from Ethereum to Base. Once broadcast, none of that is reversible. So every money-moving tool is structurally two-step: preview first, execute second.
The contract
Every fund-moving tool declares controlPolicy.confirm on its
ToolEntry. The control-policy gate at the tool-call veto point
runs preview, collects consent, and only then passes the call to
an execute-only handler. This is enforced by the harness, not by
the prompt, and not inside the handler.
Why a hard contract instead of a polite prompt? Because LLMs can "forget" prompt-level rules under prompt injection, sleight-of-hand from a malicious skill, or a hostile tool result that the LLM treats as instruction. The model calls the tool once with the action parameters. Without confirm evidence the gate returns a preview envelope, never a transaction. The client renders the card; the model never sees the preview and there is no second tool call.
The policy lives in
tool-control-policy.ts.
The hooks that interpret it live in
tier-gate.ts.
Every new fund-moving tool must declare controlPolicy.confirm;
reviewers reject PRs that don't. A fund-moving entry with no
declared confirm path fails closed.
Which tools this covers
Tools are marked isFundMoving: true in the tool registry. The
list, grouped by purpose:
Token trading
swap_tokens— DEX swap across chainsbuy_token/sell_token— explicit buy / sell sidetransfer_token— wallet-to-address transfer
Perpetual futures
open_perps_position/close_perps_positionminara_perps_wallet_sweep— move balance between sub-accountsminara_perps_wallet_transfer— transfer perps balanceminara_wallet_fund_perp— fund the perps account from spot (USDC deposit)
Autopilot — turning autonomous trading on or off
minara_autopilot_enable/minara_autopilot_disable
The autopilot toggle is fund-moving because flipping it on is the operator authorizing the agent to trade on its own. Once enabled, individual autopilot trades inherit that authorization — they don't re-prompt for every trade.
Workflows
workflow_activate/workflow_deactivate
Same idea: activating a workflow is the moment you say "this automation is approved to run on its own."
Strategy studio — your own strategy
- the strategy-codegen skill's
deploy.sh
Deploying is a one-time authorization — see Strategy deployment is a one-time authorization below.
Running another creator's strategy live
minara_top_strategies_apply_start— run a featured Top Strategies strategy These start or stop autonomous trading with the user's funds and have no author-time approval moment, so they run the same confirm gate as autopilot (apply_start/sub_startareMANUAL_ONLY;sub_stopisALWAYS_CONFIRM).
Direct exchange placement
- All
hyperliquid_*place / cancel / withdraw endpoints
If you're adding a new fund-moving tool, mark it
isFundMoving: true and declare controlPolicy.confirm. Code
review will block the PR if you don't.
The flow in practice
A walkthrough, from the agent's call to your accept:
LLM: swap_tokens({
sell_token: "USDC",
sell_amount: 100,
buy_token: "ETH",
chain: "ethereum"
})
→ gate: no confirm evidence
→ runs the declared preview (simulate), blocks with a
minara-confirm envelope
→ client renders the preview card; the model never sees it
You accept on the card
→ the same call proceeds to the execute-only handler
→ handler signs + broadcasts
→ returns {tx_hash: "0x...", executed: true}A caller-supplied confirm: true still counts as interactive
consent evidence. The check is lenient (true, "true", 1,
"yes", "on") because some tool-call protocols stringify
booleans. The UI path does not depend on the model passing that
flag.
Strategy deployment is a one-time authorization
Strategy deployment works differently. The strategy-codegen skill's
deploy.sh runs a single two-step confirm at deploy time. Without
--confirm it only prints the full deployment preview (symbol,
interval, leverage, sub-account, drawdown cap). You see it and decide
once, then the same command re-runs with --confirm.
After that, the strategy's lifecycle scripts (start.sh, stop.sh,
reconfig.sh) each run the same preview then --confirm two-step, and
the stop default market-closes open positions. They're scheduling
operations on an already-authorized strategy, not new money
movements.
Running another creator's strategy is different: there is no author-time
authorization, so the Top Strategies minara_top_strategies_apply_start
DOES run the confirm gate, like enabling autopilot.
Why this exception? If every algorithmic trade required a human confirm, the strategy is just a manual trading script. The point of strategy-studio is to delegate within constraints you set upfront. Deploy is the moment you authorize the envelope; after that, the strategy is bounded by drawdown caps, allocation limits, and the symbol allowlist baked into its deployment config, not by a per-trade prompt.
This exception applies only to strategy-studio. Don't try to mimic the pattern elsewhere. The rest of the tool surface uses the unified confirm gate.
MINARA_SKIP_FUND_CONFIRM — the operator-level bypass
There is one escape hatch for non-interactive contexts. The env
var MINARA_SKIP_FUND_CONFIRM=1 (or the safety.skipFundConfirm
preference) lets the confirm gate treat the call as already
evidenced. When to use it, when never to:
Reasonable uses (a handful of legitimate cases):
- Backtests. The caller is a Python harness; there's no human to read the preview, and the run is sandboxed against historical data anyway.
- Workflow engine execution. The workflow was already approved at activate time. The execution loop is dispatching on an authorized workflow, not asking the user to re-confirm each step.
- CI smoke tests. The whole point is to drive the fund-moving path and verify the wire call.
Never use it for:
- Interactive REPL sessions. This bypasses the last line of defense protecting you.
- "It's annoying to confirm every time" on a production box. That's turning the safety off.
- Shell rc files. Don't drop
export MINARA_SKIP_FUND_CONFIRM=1into.zshrcand forget about it. The next time you start the agent it's running unguarded.
The full env-var reference is at Environment variables → MINARA_SKIP_FUND_CONFIRM.
Layer 3 vs Layer 4 — who catches what
The most common confusion: when is the gate Layer 3 (this page) versus Layer 4 (the script-risk gate)? Quick reference:
| You ask the agent to… | Caught by |
|---|---|
| "Swap 100 USDC to ETH" — direct trade request | Layer 3 — the control-policy gate previews, then confirms |
Run a Python script containing subprocess.run(["minara","swap",…]) | Layer 4 — script-risk gate sees fund-moving CLI, prompts YELLOW |
Run cast send 0x… 1ether from terminal | Layer 4 — gate inspects the shell command, prompts YELLOW |
Patch a file so it ends up containing minara swap | Layer 4 — gate scans the post-apply content, prompts YELLOW |
Deploy a strategy via the codegen skill's deploy.sh | Script-level two-step — preview without --confirm, execute with it; backend risk layer backs it |
Short version: Layer 3 catches direct fund-moving tool calls from the agent. Layer 4 catches fund-moving code inside the scripts and commands the agent runs. They cover different parts of the same threat and work in parallel.
Operator checklist
- ✅ When you see a preview, actually read it. Verify the destination, the chain, the amount, and the slippage match what you asked for. The two-step exists so you can catch your own mistakes too.
- ✅ When deploying a strategy, audit the deployment config carefully. It's the only time you'll be asked.
- ✅ When activating a workflow, read the workflow definition end-to-end before clicking yes. Subsequent execution of that workflow won't re-ask.
- ❌ Don't set
MINARA_SKIP_FUND_CONFIRM=1in an interactive REPL session. Don't put it in.zshrcor.envrc. - ❌ Don't accept a preview you haven't read. The confirmation is your judgment, not the agent's.