Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.usecompassai.com/llms.txt

Use this file to discover all available pages before exploring further.

The agent does not hold the user’s USDC. It holds a session key — an additional signer the Diamond authorizes to call a specific set of functions under a specific set of policy constraints. Every call the session key signs is validated on-chain before USDC moves.
This page covers the session key mechanism. For the broader policy model the session key enforces, see Policy engine. For how the session key fits into the Diamond, see Diamond account.

What a session is

A session is a record stored on the user’s Diamond. Each user’s Diamond has its own session table; there is no global agent registry.
FieldTypeMeaning
agentaddressThe session-key signer.
expires_atuint64Unix timestamp. Session is dead after this time.
allowed_selectorsbytes4[]Function selectors the agent may call.
The session table lives in the Security facet’s storage and is read on every validateUserOp call.

Registration — owner-only

A session is created by the owner calling the Security facet:
SecurityFacet.registerSession(agent, expiresAt, allowedSelectors)
                ↑ onlyOwner
Three properties matter:
  • Owner-only. Only the user’s EOA can grant or modify a session. The agent’s own session key has no permission to call registerSession.
  • Bounded expiry. expiresAt is required. There is no “permanent” session — every session naturally expires and must be renewed by the owner.
  • Selector whitelist. allowedSelectors enumerates which functions the agent may call from the Diamond. Selectors not in this list are unreachable by the agent.

Validation — every UserOp

Every ERC-4337 UserOp the agent submits passes through validateUserOp on the Account4337 facet. The validator checks the call against two layers: the session key whitelist (this page) and the on-chain policy (policy engine). The full check sequence for an agent-signed UserOp: session keys diagram
  1. Signature recovery. Recover the signer address from the UserOp signature.
  2. Owner short-circuit. If the signer is the owner EOA, allow anything.
  3. Session lookup. Look up the signer in the session table:
    • Reject if no session exists.
    • Reject if block.timestamp >= expires_at.
    • Reject if the UserOp’s entry selector is not in allowed_selectors.
  4. Policy check. Decode the call arguments and run them against the on-chain policy stored on the Diamond:
    • Is the (target_protocol, target_chain) pair in protocol_whitelist?
    • Is the target chain in chain_whitelist?
    • Does the target protocol’s risk score satisfy the user’s risk_band?
    • Does the amount fit per_route_cap_usdc?
    • Does the rolling 24-hour total stay under daily_cap_usdc?
  5. EntryPoint prefund. Pay the EntryPoint gas prefund from the Diamond if any is owed.
Any failure in steps 3 or 4 reverts the UserOp before execution. The session key cannot produce a call that bypasses any of these checks — the Diamond facet itself enforces them.

On-chain and off-chain — same rules, two places

The policy attached to the user’s Diamond is the single source of truth. Both the off-chain deterministic loop and the on-chain session key validation read from it:
  • Off-chain (deterministic loop). Reads a cached copy of the policy from the indexed Diamond storage. Used to filter out plans before they ever become UserOps. This is a performance optimization — it saves gas and gives the dashboard structured rejection messages.
  • On-chain (session key). Reads the policy directly from Diamond storage on every UserOp. This is the actual safety boundary. Even if the off-chain layer is compromised or bypassed, the on-chain check is what physically prevents an out-of-policy call from executing.
If the off-chain cache is ever stale relative to on-chain, the on-chain check is what wins. The system is fail-closed: a UserOp that the off-chain layer would have rejected, but somehow reached the chain, will revert at validateUserOp rather than execute.

What the session key cannot do

ActionWhy it’s impossible
Withdraw to a non-owner address.Transfer selectors that move USDC to external addresses are never included in allowed_selectors. Only the owner can sign for them.
Grant itself more permissions.registerSession and all other Security facet mutations are onlyOwner.
Survive past expires_at.Every UserOp re-checks expiry against block.timestamp. A session is dead the moment the timestamp passes.
Reach a non-whitelisted protocol.Two gates: (1) the protocol’s facet has to be registered on the Diamond, and (2) the session has to include that facet’s selectors. Both are owner-only.
Exceed a policy parameter.Even calls to whitelisted selectors are checked against risk_band, caps, and whitelists on every UserOp.
Upgrade the Diamond.DiamondCut selectors are never in allowed_selectors. Upgrades go through the separate authority model.
The selector whitelist alone would be insufficient — an agent could otherwise call a whitelisted supply function with any amount, on any chain. The on-chain policy check is what makes the whitelist meaningful: it ties each call to the user’s specific rules.

Revocation — the kill switch

The owner can revoke a session at any time with a single transaction:
SecurityFacet.revokeSession(agent)
                ↑ onlyOwner
Effect:
  • The agent’s session is removed from the session table.
  • All future UserOps signed by that agent address fail at step 3 of validation.
  • The Diamond’s USDC and existing positions are untouched. Revocation affects future calls only.
Revocation is final for the revoked agent address — a new session has to be registered (with a new key) to delegate again.

Two independent kill switches

Compass exposes two ways to stop an agent from acting on your account:
LayerEffectRecovery
Off-chain pauseThe deterministic loop’s scheduler skips your account; no new plans are dispatched.Resume from the dashboard.
On-chain revokeSessionThe session key is invalidated at the contract level. UserOps revert at validation.Owner registers a new session.
The off-chain pause is convenient — one click, instant — and is sufficient under normal operation. The on-chain revoke is the hard stop: it works even if the Compass backend is offline, compromised, or simply ignoring your pause request. If you have any reason to distrust the off-chain layer, use the on-chain revoke. It does not depend on Compass infrastructure.

Next steps

Policy engine

The full rule set every UserOp is checked against.

Authority & upgrade model

The separate path for shipping new facets — and how to revoke it.

Diamond account

The smart account architecture this session key lives on.

Trust & security model

The big picture in plain language.