The product
What is this?
You say what you want to happen. It works out how. Doing this by hand means knowing which chain the cheapest rate is on, which bridge to cross with, which lending market has room, and in what order any of it has to happen, then holding six browser tabs open and being there when each step finishes. That is the part that keeps DeFi to the people who already know it. Here you describe the strategy once, in a sentence. Which chain, which market, which pool, what to approve and what order to do it in are worked out for you, and then it runs: on an account only you own, for as long as it takes, whether or not you are watching. A limit order might fill this afternoon, or halfway, or never. You are not waiting on any of it. → How it worksWhat can it actually do today?
58 operations across Ethereum, Base and Arbitrum. The largest families are levered liquidity positions through Revert Lend (21) and Uniswap liquidity (10); the rest is lending on Aave v3, Aave v4 and Morpho, moving a loan between them, swaps, property and rent, and a set of one-call exits. Cross-chain is the one to ask about specifically. Moving USDC works today and goes through Circle’s CCTP, which takes roughly fifteen minutes and is final on the source chain before anything arrives on the far side: plans are built around that, so a step on the destination cannot start early. The faster routes, and assets other than USDC, are not live: the second bridge in the catalogue is a fork-only stand-in and says so in its own description. Treat everything cross-chain as USDC over CCTP until that changes. Coverage is nearly uniform now, and was not. Six operations run on one chain only: buying and selling property, and the four rent ones, all Base. Everything else that names a chain runs on all three. That is worth stating because it changed: the levered family was Arbitrum-only until Revert Lend’s other two deployments were verified and configured, and a plan that would have been refused on Base a week ago compiles today. The catalogue is generated from the build and carries the commit it came from, so it is the answer rather than this paragraph. So “can I do this?” is never a question about the operation. It is a question about the operation on that chain, and answering the first as though it were the second gives a plan that validates and then cannot run. → the catalogueDoes it keep running, or do I press go every time?
It keeps running. A plan can be started by a clock, by a price, or by another plan finishing: claim the rent on the first of every month, buy if the price comes back to a level, or buy the property and then start the monthly loop. Nothing is signed in advance. Each firing compiles the plan fresh and quotes it against the market at that moment, so a schedule armed in March is not carrying March’s prices into June. Arming is a separate, deliberate press for the same reason pressing Run is: from then on it spends unattended. → Running it without youWhat is the actual sequence: what do I do?
Say what you want. A pipeline is compiled from it, which is a document of versioned operations with the amounts written out. You read it and approve it. Then it runs, and it keeps running across restarts until it settles or stops for a decision. The document is the product, not the conversation. It is what you approved, what executed, and what is replayed if anything is disputed. → How it worksMy run stopped. Did it fail?
Probably not. Parking is a normal outcome, not an exception. Automatic retry is deliberately minimal: a fee bump for a stuck transaction, and a couple of attempts for a revert whose cause can clear by itself. Beyond that the system stops guessing and waits, because most real failures need a decision rather than another attempt at the same thing. To continue, callrun_pipeline. It is the one verb: it works out which of
three things you meant by reading the run: nothing has run, so it starts; the
run stopped, so it resumes from the steps that already settled and does not
replay them; the run completed, so it repeats as the next revision. You do not
choose, because a model holding three spending tools picks the wrong one.
Can it wait for a price?
Yes, but not as a pipeline step: asplace_order, which is a tool. There
is no adapter that waits.
It is to a price what a schedule is to a clock: it points at a pipeline that is
already saved, holds no signature, and compiles a fresh plan each time it fires.
That is why one can rest for days without going stale, why it does not hold the
owner’s run lock while it waits, and why a deploy in the middle of its life costs
nothing; there is no in-flight workflow to version. →
Running it without you
Why does a saved schedule never fire?
Because saving is not arming. A schedule can be saved and left disarmed, and a disarmed schedule is unfinished work rather than a job that runs. Arming is one of the five tools that spend, since it makes a pipeline fire unattended, for ever, with nobody present at the time. → Running it without youCustody and control
The questions an operator asks before the technical ones, answered in the terms the contracts actually enforce rather than in the terms a landing page can use.Do you ever hold my funds?
No, and there is no deposit step to skip. The operating account is a smart account derived from your own address: a CREATE2 address whose salt hashes a domain, your owner address and an index, so it is the same address on every chain and it exists as an address before any code is deployed to it. There is no vault, no pooled contract and no address of ours that funds pass through. The owner key is the root authority and there is nothing above it. Neither the agent key nor the relayer key can hold a token. → KernelHow do I get my money out?
You move it yourself, from the operating account, with your own wallet. The account answers to your signature, so a withdrawal is an ordinary transfer rather than a request to us: no agent, no relayer, and no approval from anyone. Nothing about the recovery path depends on this system being up.What exactly may the agent do?
Only what is already installed on your account: named operations, named assets, and a cap per asset per epoch written when the adapter was installed. The agent signs an intent; the adapter checks it against that installed authority and refuses anything outside it. Two properties do the work, and both are structural rather than policy. The agent supplies parameters, never calldata: the signed type is a closed struct with no destination field, so “call this address with this payload” is not expressible. And the destination of anything that produces value is a named role resolved from your owner address, not an address the plan may choose. → AdaptersWhat if your agent key is compromised?
It cannot take your account. Owning it, changing who owns it, naming a destination and supplying calldata are not things the agent is trusted not to do, they are things the type it signs has no field for. Beyond that it is capped: a per-asset spend limit fixed in your account when it was created, which the agent cannot raise. Bounded is not harmless, and the guard that backs this up is a denylist rather than an allowlist, so it stops an agent taking control and does not stop every way value can move. The long version, including the drain vectors we have proven against ourselves, is under Security below and on Non-custodial, and unilateral control. You do not have to wait for us either way: revoking is next, and your own exit never depended on the agent at all.How do I revoke it?
One transaction from your own wallet, and there are two different revocations worth telling apart. Yours, per account. The account callsappoint with the zero address and it
holds no agent from that moment. It needs nobody’s cooperation, there is no
ticket, and anything already signed fails its check on arrival rather than
landing late.
The guardian’s, globally. revoke(agent) marks an agent key dead for every
account at once. It is guardian-only and exists for a key believed stolen; it
is not the path an ordinary owner uses, and it cannot reach into an account to
change who that account appointed.
Can I use a protocol you have not integrated?
Not by writing one into a plan, and that is deliberate. The set of operations is closed: an operation exists because an adapter was written, deployed, whitelisted on that chain and installed on your account. A plan naming anything outside the set is refused at compile time rather than attempted. What is cheap is adding to the set. A new venue is an adapter and a whitelist entry, not a change to the plan language, so plans written before it existed keep their shape and gain nothing they did not ask for. The closedness is the security property; the speed of extending it is a separate fact and does not weaken it. → AdaptersHow do I know what a plan will do before it runs?
Because you approve the compiled steps, not the sentence. Validation returns every step with the protocol it calls, the chain it lands on, the amount in both raw and human units, and where the value goes next, and running it is a separate decision from saving it, taken by a person pressing Start. A saved plan has run nothing. → the tool serverThe infrastructure
What actually runs?
Five Go binaries built from one image, with a variable choosing which one runs, so they share every compiled constant, which is why they are deployed together and never singly:
Around them: postgres (replay guard, submission idempotency, audit log),
temporal (workflow histories), redis, an OpenZeppelin relayer (key R,
one account per chain), a swap router, a transaction simulator, three
anvil forks, a Next.js console, and caddy: the only container with a
published port. → How it works
Why Temporal, and not a queue and a cron?
Because the thing being modelled is a wait, not a task. A CCTP crossing is fifteen minutes of nothing followed by an attestation; an order can rest for days. A worker that is redeployed halfway through has to come back knowing exactly where it was, and a queue plus a status column reconstructs that by guessing. The consequence to hold on to: a workflow that waits for a person is in flight across every deploy you do, which is why operations carry a pinned version. → How it worksWhat survives a restart, and what does not?
Survives: the audit log, the replay guard and submission idempotency, because they arePRIMARY KEYs in Postgres rather than something a process holds; and
every workflow history, in Temporal.
Does not survive, on a fork: the world the fork was built on. Restarting an
anvil container brings back the container, not the operating accounts, the
bridge’s token float, the filler’s gas or any balance ever set. None of it is
announced, and each piece resurfaces later as a different unexplained hang.
→ How it works
Is real money moving?
Not on staging. The chains there are anvil forks pinned to a block, so a run spends fork money while settling against real mainnet state. Forked mainnet rather than a testnet because testnets lack the dependency set this needs: bridges, lending markets, DEX depth. That is a property of this deployment, not of the code. The same build against real RPCs spends real money. → How it worksWhat stops a bad transaction reaching the chain?
Four things, in order:- The plan is checked against the catalogue. An operation that is not in it cannot be named, because there is no escape hatch to an unregistered call.
- The amounts are stated twice: once as the document’s raw base-unit integers, once restated in decimal with the symbol. Every amount is a raw integer and nothing records what it was meant to be, so a plan whose every amount is a thousandfold too small is perfectly self-consistent and passes every internal check there is. A place-value error cannot be present in both statements, which is the only anchor from outside the plan’s own numbers.
- Every non-bridge step is simulated, through
eth_simulateV1, before it is broadcast. - The relayer is handed typed parameters, never calldata.
Why does every operation have @v1.1 on it, and why is a bare name refused?
Because a pipeline can be in flight for a long time while operations change
underneath it. Without pinning, a job that survives a deploy silently starts
executing different behaviour halfway through.
A newer minor satisfies a pin, since minors are additive by definition. A
newer major does not: the plan was approved against the old semantics. And
resolution returns the lowest compatible version, not the newest, because
running on the newest available substitutes code the plan never saw.
→ Adapters
Security
What are the keys, and what can each one do?
The guardian and the upgrader must be different keys, and the constructor
refuses one key for both: otherwise the whitelist’s three-day activation delay
is decoration, because whoever can replace the registry’s implementation can have
it hand out whatever the new code returns. → Kernel
What happens if the agent key is stolen?
It cannot own, install or uninstall modules, change the root validator, upgrade the implementation, choose a destination, or supply calldata. It never holds funds and never pays gas. What bounds it: no adapter has a field for an attacker-controlled destination; a per-asset per-epoch outflow cap, fixed in the account’s init data at install and un-raisable by the agent; and revocation as a kill switch. With one important caveat, which is the next question. → KernelIs the agent guard airtight?
No, and this is the most important answer on the page. It is a denylist, not an allowlist. It blocks self-calls, delegatecall, calls to the registry, native value, a revoked agent, and exactly six selectors:transfer, approve, transferFrom,
changeRootValidator, onInstall, onUninstall. Everything else, any target
and any other selector, passes.
So it fully blocks control takeover: the agent can never become owner or hand
the account away. It does not block value movement by a selector it did not
name, and four drain vectors are proven on a Base fork in the production
shape.
This is reachable only in unrestricted mode, or where a directly-held key
bypasses the signer. In default adapter mode the signer never produces the kind
of operation that reaches it. That is a real mitigation and it is not the same
thing as the hole being closed. → Kernel
What happens if the relayer key is stolen?
Gas, censorship, and arbitrary calls of its own, not your money. Both live submission paths move value through the account, leaving the relayer as sender of the outer transaction only. Measured on a long-lived fork, the relayer holds no catalogued token and no allowance to any venue: native gas balance and nothing else. What a stolen relayer key still buys, said plainly: nothing constrains what that key signs once held, and the relayer service’s config carries no destination allowlist. It can spend gas, refuse to submit, and sign calls of its own. It cannot spend the account’s assets. → KernelCan the model choose where money goes?
No, and not because a check refuses it, because the type cannot express it. The thing the agent signs is a closed struct of typed fields. There is noTo,
no Data, no route blob. A destination is written as a named role:
owner_account or owner_wallet: resolved from the account, never from the
plan. Adding a field capable of carrying an address would defeat the whole
design. → Kernel
How is the agent key actually stopped, if it is compromised?
Onerevoke call on the registry stops that key for every account on that
chain, immediately, with no redeploy and no migration. Each chain has its own
registry, so a full revocation is one transaction per chain: three today.
The thing to check before you need it: AgentRegistry.guardian is write-once
at initialisation. A registry deployed with no guardian answers every other
question perfectly well and cannot revoke anything, and the only fix is
redeploying it. → Kernel
What stops a step reaching the chain some other way?
Every call the relayer makes isact(parameters, signature) against an adapter
that checks the signer against the registry. The relayer builds calldata from
typed parameters and is never handed calldata; there is no input to that
function capable of expressing an arbitrary call.
That is also why every catalogued step must be an adapter. A step reaching the
chain any other way is one the agent’s signature does not gate and the account’s
installed-operations list does not bound. → Adapters
Has this been audited?
No. There is no external audit; every assurance is our own tests, and that is the honest state. The two alternative root validators: a Safe as owner, and a passkey: are newer than the rest and are unaudited and not the default. The signing service also refuses to start under a production environment, deliberately: it holds a raw key in memory, and a KMS-backed signer is the work between this and real user money. → KernelTrust
Whose account is it?
Yours. It is an ERC-4337 smart account whose root authority is the owner, authenticated by a root validator named in the account’s own address. There is no administrative authority above it:upgradeTo on the account is gated on the
EntryPoint, the account itself or the root, so no key of ours can replace your
account’s code, move its funds, or pause it.
That is a property of the contract rather than a promise, which is the only kind
worth stating on a page like this. → Kernel
Can you move my money without me?
Only through an installed adapter, and only within what that adapter forces: the owner is read from the account rather than trusted from the request, the recipient is forced to the account, the venue is a curated name and never an address, and there is a per-epoch cap. A valid agent signature is not proof the right account was meant, which is why the owner-binding is checked independently by every adapter rather than once somewhere central. → KernelCan I get my money out without you?
Yes, if you hold the owner key, and that condition deserves reading twice. There is a tool that reads who may drive your account and what it holds, then builds the transactions you send, showing them to you first. It uses no relayer, no bundler, no agent key and no adapter: every one of those is a thing that could be broken on the day you need to leave. It prints by default and only broadcasts if you ask it to, because a tool that moves everything an account holds should not do it as a side effect of being run. Two caveats, and the tool states the first:- A sweep moves balances. It does not unwind a loan, does not move Uniswap liquidity positions, and cannot move what has not arrived yet. Unwind first, or the debt outlives the exit.
- An owner minted by the console holds no key; it is twenty random bytes with the address kept and the bytes discarded, which is enough for the agent to spend through and enough for nothing else. Exiting unilaterally means owning the key, so connect a wallet you actually hold and prove it on the Accounts page. Until you do, the exit path exists in the contract and you are not the one who can walk it. → Kernel