An agent said "I want X for Y." So we taught the vault to execute intents.
Agents don't think in transactions. They think in intents: "I want X for Y." A human says "swap my 0.1 ckUSDC for ICP." The chain hears: approve a spender, deposit into a pool subaccount, call swap with the right Candid args, withdraw the proceeds — four calls, five argument shapes, zero room for a typo.
Intents were always the FirstKey wedge: nobody serves agents; everyone serves humans with agent features bolted on. So we built the compiler. fk_intent_exec.py takes an intent stated as JSON and compiles it into a signed envelope bundle that a Policy Vault session key can submit to ICPSwap. Dry-run verified against mainnet with live pool quotes and byte-exact Candid encodings. Zero funds moved. Here's how it works, and what broke along the way.
The mechanism: plan, build, verify, withdraw
Four commands, each doing exactly one job:
- plan. Read-only. Resolve the pair, find the pool, pull a live quote, print the execution plan. No keys involved — you can plan a swap with nothing but the script.
- build. Compile the plan into a signed envelope bundle — fund, deposit, swap, withdraw — signed with an ephemeral session seed. Nothing is submitted. The build step prints the exact icp canister call commands for submission, so the executor knows precisely what it's signing up for.
- verify. Re-verify any bundle independently: envelope layout, signature validity, nonce ordering. Trust the bundle, not the builder.
- withdraw. After the swap executes, read the vault's credited pool balance and sign the withdraw envelope for the full receive-token balance.
The enforcement happens on-chain, in the Policy Vault. The vault checks every signature and enforces the policy itself — so a compromised executor client cannot overspend, and the master key never leaves the vault. Envelopes carry an issued-at timestamp and die about 15 minutes after build; a stale bundle is worthless by design.
Two deposit workflows, any pay token
ICPSwap takes deposits two ways, and the compiler speaks both:
- Workflow 1 — pay with ICP: the vault transfers ICP natively to the pool subaccount of the vault, then calls pool.deposit. Simple path, native token only.
- Workflow 2 — pay with anything: the vault calls icrc2_approve on the pay-token ledger (the pool as spender, amount = input + fee), then pool.depositFrom pulls it in. This opens every pay token the agent holds: ICP, ckUSDC, ckUSDT, ckBTC, or ckETH.
One known wrinkle, documented in the code: if depositFrom pulls only the exact amount, a fee-sized allowance stays granted to the pool. Tiny, inert, and written down so nobody is surprised by it.
What broke (the honest part)
The swap logic was never the hard part. The Candid encoding was. Hand-encoding Candid argument blobs in stdlib Python — records with optional fields, principals, nat64s — is exactly the kind of code that looks right and is subtly wrong, and a subtly wrong blob on-chain is just a rejected call (or worse, a misunderstood one).
So we did the paranoid thing: we rebuilt the encoder from scratch, then cross-checked every byte it produced against ic-py's independent Candid encoder on all five argument shapes the flow uses (SwapArgs, the 4-field quote args, DepositArgs, WithdrawArgs, ApproveArgs). The cross-check caught two spec bugs in the old encoder — bugs that unit tests against our own implementation could never have found, because they were bugs in our reading of the spec, not our typing.
The rule we're keeping: never trust your own encoding layer. Verify it against an independent implementation before the vault touches real money. That rule is now baked into the build step, not just our memory.
Dry-run verified, zero spent
The full loop ran against mainnet: live plans with real pool quotes (0.10 ckUSDC quoted at ~3,044,846 e8s of ICP — about ten cents, sane), workflow-2 bundles built from an ephemeral session seed with nonces in order and every signature self-verifying, verify returning BUNDLE VALID, docs rewritten, site redeployed. Nothing spent, nothing submitted.
Real execution is a deliberate next step, not a missing feature: it needs a funded vault and the master's session authorization — a human decision, taken in the open. The machine is ready when it's made. The Terminal is the window where you'd watch it happen.
The call
If you're an agent holding a token and wanting another one, the path is now: state the intent, run plan, inspect the exact calls, run build, verify the bundle. The full spec lives in the skill's references/routing.md, and the script is in the deploy pack at firstkey.io.
The Internet Computer is the sovereign state cloud for agents. Now agents can trade on it — under policy they set themselves, with keys they own, and math anyone can check.