Skip to content

Lifecycle & autonomy

An agent isn’t a fire-and-forget script. It has a lifecycle the owner controls: it can be paused and resumed, it can park an operation until you approve it, and it can be revoked for good. Every transition except the agent’s own actions is owner-only, and every transition is written to the agent’s event log.


State Meaning
pending Created but not yet funded. An XRPL agent skips this state: its account is funded when it is created, so it starts active.
active Funded and operational. The only state in which the agent can act.
paused Halted by the owner. The state in which you can change the rules config.
awaiting_approval An operation was above the owner-approval threshold and is parked until you approve or reject it.
revoked Permanently disabled. You can then delete it.
stopped Listed in the status type. Nothing sets it today.

stateDiagram-v2
    [*] --> active: two-phase create (XRPL)
    [*] --> pending: synchronous create
    pending --> active: fund() (first funding)
    active --> paused: pause() (owner)
    paused --> active: resume() (owner)
    active --> awaiting_approval: op above approvalThreshold
    awaiting_approval --> active: approve() / reject() (owner)
    paused --> paused: updateRules() (owner)
    active --> revoked: revoke() (owner)
    paused --> revoked: revoke() (owner)
    awaiting_approval --> revoked: revoke() (owner)
    revoked --> [*]: delete() (owner)

The platform enforces these rules:

  • An XRPL agent is active when it is created. The two-phase create funds its account, so there is no separate activation. On a synchronous create, the first fund moves the agent from pending to active.
  • Acting requires active. A capability run on an agent in any other state is refused.
  • The rules config changes only while paused. updateRules is refused otherwise. A rules key that starts with $, contains ., or is __proto__, constructor or prototype is refused with 400, at creation as well, because the agent’s record is replicated to the other clusters and they refuse such keys.
  • Revoke is final and owner-only. Revoking again answers already revoked.
  • Status changes reach every cluster, and a revoke always wins. Each cluster applies the newest status it receives. A resume made on a cluster the revoke has not reached yet is overridden when the revoke arrives, and every resume after that fails. If you need a stop that holds on every cluster, revoke.
  • Delete hides the agent; it does not free its slot. delete revokes the agent if needed and hides it from every read; a deleted id answers 404. The agent keeps its slot in your agent limit until its reserve is reclaimed. Delete does not touch the chain: the agent’s wallet entity stays listed under its owner, and its funds move only as far as its rulebook allows.
  • Reclaiming the reserve frees the slot. After a delete, baas.agents.reclaimReserve(agentId) closes the XRPL account and sends its whole balance to whoever funded it, read from the ledger, never from the request. The slot is released once, after the ledger confirms the account is gone. See Identity & wallets.
  • Approve and reject resolve the parked operation. approve checks the operation against the rulebook once more before releasing it and returns the agent to active. reject discards it and returns the agent to active.

Operation Caller Resulting transition
two-phase create (XRPL) Owner (payer) → active
synchronous create Owner → pending
fund Owner pending → active (first time)
capability run The agent, through its triggers stays active, or → awaiting_approval
withdraw Owner none
pause / resume Owner active ↔ paused
updateRules Owner none (must be paused)
approve / reject Owner awaiting_approval → active
revoke Owner → revoked
delete Owner → revoked, then hidden from every read
reclaimReserve Owner none (after delete)

The agent does exactly one class of thing: run its own declared capabilities. A capability run is accepted only from the agent’s own wallet, and no person holds that wallet’s key, so in practice the agent acts through its triggers. A call to execute or trade from your app or your owner wallet is refused with 403. Everything else is the owner’s.

Creating an agent is gated too. Each agent counts against the app’s agent limit and, for an agent created for a customer, that customer’s limit, checked before the DKG runs. Past a limit the create is refused with 400. See Deploy your first agent (Step 2) for the defaults.


“Autonomous” doesn’t mean “unsupervised by rules”. It means no human in the request loop for routine operations. The agent acts on triggers you put in its rules config, and every action ends at the same fail-closed rule check.

  • Schedule triggers fire a capability every intervalMs milliseconds. The runner polls every 30 seconds by default and reads the triggers fresh on each poll. The schedule is kept in memory: after a restart every schedule starts counting again from that moment. It is best-effort timing, not a durable scheduler.
  • Event triggers fire a capability on each message on a subject inside the agent’s own app namespace: app.<appId> or app.<appId>.<anything>. Besides your app’s own events, the platform publishes your app’s deploy, billing, data-write and messaging events there; the subjects are listed in Deploy your first agent. Event triggers are subscribed when the platform host starts, so a new or changed event trigger takes effect after the host’s next restart.

Only an agent that belongs to a Smart App fires triggers. A trigger fires only a capability that is both declared on the agent and named in its triggers. A trigger cannot name a chain, so on-chain capabilities (trade, payout, token) are fired through Path A.

  1. A schedule or event trigger fires function-invoke with your function’s id.
  2. Your function runs as the agent, metered to the agent, and decides what to do.
  3. It returns a decision and up to eight intents, each naming a capability, a chain and params.
  4. Each intent is checked like a direct action: declared capability, then the rulebook, before any validator signs.

Submitting the resulting signed transaction to the ledger from a trigger is planned. Until then, on-chain intents are checked and signed but have no on-chain effect.

Path B — the agent’s own trigger runtime (no HTTP call)

Section titled “Path B — the agent’s own trigger runtime (no HTTP call)”

A trigger can also fire an off-chain capability directly, with no function and no HTTP call:

// on the agent's rulesConfig
triggers: {
schedule: [{ capability: 'read', intervalMs: 60_000, params: { url: 'https://api.example.com/price' } }],
events: [{ capability: 'notify', subject: 'app.<appId>.deposit', params: { url: 'https://hooks.example.com/x', message: 'got a deposit' } }],
}

Every URL must be on the agent’s offchain.allowedUrls. An event subject that names a platform channel, a bare wildcard such as > or app.*.>, or another app is refused when you set the rules and again when the agent subscribes, so an agent can only be woken by its own app’s events.

Both paths converge on the same check:

sequenceDiagram
    participant Trig as Trigger (schedule or event)
    participant Fn as Your function (Path A)
    participant Agent as Agent capability run
    participant Gate as Rule check (fail-closed)

    Trig->>Fn: function-invoke
    Fn-->>Agent: intents (capability, chain, params)
    Trig->>Agent: off-chain capability (Path B)
    Agent->>Gate: check
    alt within the rules
        Gate-->>Agent: signed by the validators
    else above the approval threshold
        Gate-->>Agent: parked → awaiting_approval
    else breaks a rule
        Gate-->>Agent: denied, nothing signed
    end

Whatever decides, a function or a model it calls, has no signing authority. The worst a compromised or hallucinating decision can do is propose something that gets denied. For large moves, approvalThreshold brings you back into the loop by parking the operation in awaiting_approval.