Passive and Agent modes
Fixed at launch, permanently#
Mode is written by init_policy and by nothing else. set_policy can change the model, the splits, the risk, the target asset and the leverage, but it cannot change the mode, and neither can any other instruction. A launch sold as Passive can never quietly become an Agent launch.
Both deployment instructions check the mode and refuse to run in the other one: passive_deploy requires Mode::Passive and agent_fund_venue requires Mode::Agent, each failing with WrongMode. There is no path where both are live on the same policy.
Passive mode#
No agent, no inference, no model, no discretion. The policy's model_id is not even read: init_policy skips model validation entirely when the mode is not Agent, and the client passes the program's own id in place of a model account to signal "none".
On a schedule, a permissionless keeper:
- calls
claim_fees, which credits the measured delta in both escrows, - calls
route, which applies the fee split, - calls
passive_deploy, which moves bankroll funds to the venue account registered for the policy's owntarget_asset, - and stops when the risk slider's deployment cap is reached, at which point the instruction reverts with
RiskCapExceeded.
Pinned to the target asset#
passive_deploy requires that the venue account's asset_mint equals the policy's target_asset exactly, otherwise it fails with NotTargetAsset. A policy can have several registered venue accounts, but a Passive keeper can only ever fund the one holding the asset the deployer picked. There is no discretion to point it anywhere else.
Agent mode#
A model from the on-chain registry trades the bankroll inside the mandate. The choice is on chain so buyers can see what is trading the fees, and init_policy validates it: the supplied Model account must derive to the PDA for that model_id, its id field must match, and it must be enabled. A disabled model fails with ModelDisabled.
The agent holds a key that can place orders at the venue and move bankroll funds to registered venue accounts through agent_fund_venue. On Solana it cannot choose a destination, so the program can never pay an address it picks. At the venue the key is a different key under different rules: on Hyperliquid, where an approved agent key has now filled a real order, whether that key can also withdraw is unverified. That split is the whole subject of the safety model.
The agent also calls note_credit to record its own inference spend. That instruction moves no funds at all, which is why it is safe to let the agent write to it: there is no way to turn a counter into a withdrawal. See compute costs.
Side by side#
| Passive | Agent | |
|---|---|---|
| Deployment instruction | passive_deploy | agent_fund_venue |
| Who may call it | Anybody | Only the key recorded in Policy.agent |
| Model account read | No, never | Yes, at init_policy and set_policy |
| Destination | Only the venue holding target_asset | Any registered venue account |
| Risk cap enforced | Yes, identically | Yes, identically |
| Leverage cap | 5x | 5x |
| Inference cost | None | Paid out of the vault, metered by note_credit |
| Can be paused by the guardian | Yes | Yes |
| Auditable in advance | Completely. The policy account determines everything. | Partly. The mandate is on chain, the decisions are not. |
| Explains itself | Nothing to explain. The rule is the policy account. | The public feed, posted by the agent key and readable at /coins/:mint/posts |
What "deploys" actually means on chain#
on chain: bankroll ──transfer_checked──▶ registered venue token account
│
off chain: ├─▶ bridged and traded at the venue
└─▶ equity read back and attestedThis matters for how much the chain can enforce. It enforces where money may go, how much of the vault may be out at once, and what leverage an attestation may report. It does not enforce what trade is placed, and it cannot: Solana cannot see Hyperliquid or Derive. What fills that gap, and how weak it is, is on remote equity and attestation.
For a Remote venue account, both instructions also increment VenueAccount.bridged_out by the transferred amount, so the gap between what was sent out and what is claimed to be there stays visible on chain.
The risk cap, which both share#
Both instructions compute the cap identically, from the same helper, and both require the caller to supply every registered venue account so that "how much is already out" cannot be understated.
deployed = sum of every registered venue account equity = deployed + bankroll.amount after = deployed + amount cap = equity * risk / 100 require(after <= cap) # else RiskCapExceeded
Risk 0 is therefore a bankroll that accumulates and never trades: the cap is zero and any deployment fails. Risk 100 allows the entire vault to sit at venues. See risk, origins and asset classes for the second thing the slider does, which is gate which asset classes are reachable at all.
"mode": "Passive",
"model": { "id": 0, "name": null, "provider": null, "enabled": null },
"risk": 40,
"targetAsset": "76qTBkCgruzX6sW5KWurXLoYBUXLJMrDaiGbFq64ykXW",
"targetLeverageX": 3,
"venueAccounts": [
{
"tokenAccount": "8AivDCLoTBYVakuUbc3GM3hozBjivMNKBx82Tw82WrpZ",
"kind": "Local",
"assetClass": 8,
"assetMint": "76qTBkCgruzX6sW5KWurXLoYBUXLJMrDaiGbFq64ykXW",
"revoked": false,
"local": { "value": "666000000", "source": "chain:getTokenAccountBalance" }
}
]Note that model.id is 0 and every other model field is null. In Passive mode that is correct and not missing data: the field exists on the account and is never read. Note also that assetMint equals targetAsset, which is the condition passive_deploy enforces.
Which to pick#
The specification itself says Passive is the honest default and, given the evidence on LLM trading performance, likely the better product for most launches. This documentation repeats that rather than softening it.