Agent Delegation
How Medici's autonomous agents act on a user's behalf: two authorization planes, identity-only tokens, and delegation as an on-ledger contract whose bounds Canton itself enforces.
Three questions per request
Every request into the Ledger Service is classified by three orthogonal facts:
| Question | Answered by | Notes |
|---|---|---|
| Who is calling? | Verified JWT → fleet-roster match on client_id |
Yields is this a fleet agent — a boolean about the caller, deliberately not a kind of authority |
| Whose authority applies? | The authorization plane | bearer — the caller's own Canton user rights authorize the action;
signature — the user's signature over the prepared transaction authorizes it |
| What is permitted? | Canton rights + the delegation's scope | Rights are reconciler-owned (see Identity Reconciler); delegation bounds are re-checked on-ledger at execution time |
Tokens carry identity, never authority. Canton resolves rights from the token's
sub via the Canton user record and ignores any actAs/readAs
claims a token carries. Every handler that touches Canton bridges the caller's verified
token (WithCallerToken) so it acts with the caller's rights — skipping the bridge
doesn't 401, it silently acts as the service. Agents additionally hard-fail at boot via
POST /api/v1/auth/verify if any expected right is missing.
Mechanism A — proposal at intent creation
The default, in production. When a user creates a strategy intent, they sign the resulting
SplitRequest proposal in their own authenticated session; the StrategyAgent later
exercises OperatorExecute on that user-signed proposal. The agent holds no standing
rights over any user's party — its authority is one proposal, created by the principal,
consumable once. See
Intent Execution Authority.
Mechanism B — StrategyDelegation (on-ledger power of attorney)
For recurring lifecycle actions (rolls, rebalances, stop-losses) whose parameters can't be
pre-signed, the user signs a single StrategyDelegation contract —
signatory owner, observer operator — granting scoped, time-bounded,
revocable authority:
| Bound | Field | Enforcement |
|---|---|---|
| Markets | allowedTickers | DAML assertion in the choice body |
| Size per action | maxNotional | DAML assertion |
| Action types | allowedActions | DAML assertion |
| Lifetime | expiry | Checked against ledger time (getTime) — not caller-forgeable |
| Total uses | actionCount / maxActions | Sound because every use consumes and recreates the contract |
| Re-delegation | maxDepth | 0 by default — no sub-delegation |
Every delegated action consumes the contract and recreates it with the counter
advanced — stale contract IDs can't be replayed and the counter can't be raced. Only the owner
can Revoke (immediate — the contract is archived) or UpdateScope;
the operator deliberately holds no revocation right.
API
POST /api/v1/delegations # create — owner is resolved from YOUR verified
# token via Canton user lookup, never from the body
GET /api/v1/delegations # list delegations visible to the caller
DELETE /api/v1/delegations/:id # revoke (owner only)
Requires an onboarded Canton user (POST /api/v1/onboard first). Body fields mirror
the scope table above: delegationId, operator,
allowedTickers, maxNotional, allowedActions,
expiry (ISO 8601), optional maxActions, maxDepth.
The signature plane — delegation without custody
External parties (CIP-103) keep their own keys: the service prepares transactions and returns
hashes; the user's key signs; the ledger accepts the signature as the authority. Middleware
enforces the plane invariant before every signature-plane submission: the service must
hold actAs on no external party — one grant is a violation and the request
is refused. The identity manifest is lint-checked so such a grant can't even be declared.
See the CIP-103 flow example.
Oversight
- RiskAgent observes delegation events; raised caps trigger concentration checks and can trip the circuit breaker.
- NotificationAgent notifies the owner on every delegated action and ahead of expiry.
- CircuitBreaker remains the on-ledger backstop — every trading choice asserts it, so a halt stops even correctly-delegated actions.
Further reading
- Strategy Engine — intents, templates, lifecycle
- Identity Reconciler — who owns the rights the bearer plane relies on
- Whitepaper: Delegation without custody — full design, threat model, and comparison to API keys, session keys, and OAuth on-behalf-of