Zado Agent Trust Protocol v0.1
The consumer-side policy layer for AI agents that spend real money.
| Field | Value |
|---|---|
| Version | 0.1 |
| Status | Public draft |
| Date | 2026-04-25 |
| Author | Evolving Intelligence AI, LLC |
| License (spec) | CC-BY 4.0 |
| License (reference implementation) | See project LICENSE |
| Canonical URL | https://zadofi.ai/protocol/zado-agent-trust-protocol-v0.1 |
Provenance
This specification describes behavior that is shipped and operating in the
Zado backend as of the publication date. It is not aspirational. Every
normative claim traces to a backing source file in
backend/app/{models,services,mcp,core}/.
The protocol was first exercised end-to-end on 2026-04-03, when an
OpenClaw agent registered a Zado spend-scoped token, called check_budget,
called authorize_purchase(43.20, "groceries", "Whole Foods") and received an
authorized: true response with a transaction id and remaining envelope
balance. The agent had no access to the user's bank credentials, account
numbers, or transactions outside its bound category. That run is the existence
proof for v0.1.
Items labeled "v0.2+ roadmap" are explicitly NOT shipped today. Integrators MUST NOT depend on roadmap items.
1. Introduction
Zado is a policy and permission layer for AI agents that need to make purchases on behalf of a human. It sits above a person's real bank accounts (connected via Plaid) and below the agent-payment rails being built by the card networks.
When an AI agent wants to spend money, three questions must be answered:
- Who said it could? — identity and scope (Zado:
agent_token.scope) - How much can it spend, on what? — policy and budget (Zado: envelopes, caps, binding, pacing)
- How does the money actually move? — settlement (Zado: today, a ledger entry; in v0.2+, optionally a Stripe Issuing one-time virtual card)
Most existing agent-payment work focuses on question 3. Zado focuses on questions 1 and 2 — the policy gate that any settlement rail can consult before money moves.
The spec is intentionally compatible with, and complementary to:
- Google AP2 (Agent Payments Protocol) — Zado tokens map to AP2 Intent
Mandates;
authorize_purchasesuccess can produce a signed Cart Mandate. - Stripe Machine Payments Protocol (MPP) — Zado is the consumer-side policy layer an MPP-scoped agent can consult before invoking MPP-routed payment.
- Visa Intelligent Commerce / Mastercard Agent Pay — Zado is the consumer-side budget gate that complements network-side spend controls and tokenization.
- Model Context Protocol (MCP) — Zado exposes its policy surface via four MCP tools.
What Zado is not:
- Zado is not a money transmitter. It does not move funds.
- Zado is not a card issuer in v0.1. (v0.2+ may issue one-time virtual cards via Stripe Issuing — Stripe remains the regulated entity.)
- Zado is not a replacement for AP2, MPP, Visa IC, or MC Agent Pay. It is a complement to them.
2. Positioning
Zado occupies a layer that, before this spec, had no public name.
| Player | Layer | Primary Consumer | Spend Authority | Settlement |
|---|---|---|---|---|
| Zado Agent Trust Protocol (this spec) | Consumer policy / permission gate | End user (with ADHD-friendly UX) | Envelope balance + agent token scope + caps + binding + pacing | None today (ledger entry); Stripe Issuing virtual card in v0.2+ |
| Google AP2 | Open agent-payment protocol (rails-agnostic) | Merchants + agents + payment processors | Intent Mandate + Cart Mandate (cryptographic VDCs) | Pluggable (cards, wallets, instant rails) |
| Stripe MPP | Agent-payments protocol on Stripe rails | Stripe-integrated merchants | Stripe-issued credentials, scope-bound | Stripe rails |
| Visa Intelligent Commerce Connect | Network-side AI payment platform | Visa-accepting merchants | Tokenization + spend controls per agent | Visa network |
| Mastercard Agent Pay | Network-side agentic payment program | All Mastercard cardholders | Mastercard Agentic Tokens, consumer-set scopes | Mastercard network |
The pattern: card networks and protocol-level players answer how money moves once authorized. Zado answers what an agent is allowed to spend, on behalf of which envelope, against which budget, in the first place. A Visa-Intelligent-Commerce-routed payment authorized through MPP can ask a Zado-equivalent policy layer "is this purchase permitted by the user's budget?" before it ever touches the network. Today that consultation is done inside each card-network's own platform; Zado is the first open, MCP-native, real-bank-account-backed implementation of that consumer-side gate.
The single-sentence positioning: Zado is the consumer-side budget gate for AI agents — the layer that says "yes" or "no" to a spend before any settlement rail moves money.
3. Core concepts
This section is normative. Each term is defined once. Every term cites the source file that implements it.
3.1 Envelope
A named pool of money allocated to a spending category for a calendar month.
"Groceries: $400 budgeted, $123.50 spent, $276.50 remaining." The envelope
balance is the hard ceiling for any agent or human spend in that category.
Source: backend/app/models/envelope.py and backend/app/services/envelope.py.
3.2 Envelope-as-Permission
The fundamental design pattern of this protocol: the envelope balance IS the
spending limit for any agent bound to that envelope. No separate
"agent budget" exists. There is only the user's envelope, which the agent may
draw against subject to scope and guards. When an agent asks "can I spend
$43?", the answer is computed from the same envelope balance the user sees in
the app. There are no parallel ledgers and no agent-only allowances.
Source: backend/app/services/agent_spending.py (the authorize_purchase
flow checks the live envelope via SpendingService.check_can_afford).
3.3 Agent Token
A SHA-256-hashed bearer credential issued to a single AI agent on behalf of a
single user. Token plaintext is shown once at creation and never stored.
Tokens carry: id, user_id (FK), name, scope, is_active, spending
guards, optional allowed_category_ids, optional pace_multiplier, optional
expires_at. Tokens are revocable individually or in bulk (the kill switch).
Source: backend/app/models/agent_token.py.
3.4 Scope
A token has exactly one of two scopes:
read— May call read-only tools (check_budget,list_envelopes,get_daily_status). Cannot authorize purchases.spend— May call all read-only tools ANDauthorize_purchase. Subject to all spending guards.
Scope is checked server-side as the first gate in authorize_purchase.
Source: backend/app/services/agent_spending.py.
3.5 Envelope Binding
A token MAY be bound to a specific set of envelopes via allowed_category_ids
(a JSON array of stable category UUIDs). When bound, the agent can only
authorize purchases against those categories. An attempt to spend in any other
category is rejected with envelope_not_bound. A null binding means the agent
may spend in any of the user's envelopes (subject to all other guards).
Legacy allowed_category_slugs is honored for backwards compatibility.
Source: backend/app/services/agent_guard.py (check_envelope_binding).
3.6 Per-Transaction Cap
A hard maximum dollar amount per single authorize_purchase call,
configurable per token (default $50). Exceeding it returns
per_transaction_cap_exceeded. Source: backend/app/services/agent_guard.py.
3.7 Session Cumulative Cap
A rolling total spending limit across all of an agent's purchases since the
last session reset (default $100). Exceeding it returns
session_cap_exceeded. The session auto-resets after 24 hours of
inactivity to prevent permanent lockout from a one-time spike.
Source: backend/app/services/agent_guard.py (SESSION_RESET_SECONDS).
3.8 Rate Limit
A maximum of three authorize_purchase calls per minute per agent. Exceeding
it returns rate_limited with a retry_after_seconds field. The minute
window is sliding, anchored to last_transaction_at.
Source: backend/app/services/agent_guard.py (AGENT_RATE_LIMIT).
3.9 Budget Pacing
A guard that rejects purchases consuming budget faster than a sustainable
daily pace. Computed as daily_pace = envelope_remaining / days_remaining,
then pace_limit = daily_pace * pace_multiplier. Default
pace_multiplier = 3.0. Exceeding the pace returns exceeds_budget_pace
with the computed daily_pace, pace_limit, and days_remaining.
Source: backend/app/services/agent_guard.py (check_budget_pace).
3.10 Kill Switch
A single user-initiated action that revokes ALL of a user's agent tokens
simultaneously (POST /agents/revoke-all, exposed in the desktop app under
Settings → Agent Access → Freeze All Agents). Revoked tokens immediately
return 401 on next use. Designed for the "something is wrong, stop everything
right now" moment. Source: backend/app/api/agents.py.
3.11 Human Approval Gate (v0.2+ roadmap)
A future capability where an agent's authorize_purchase request can be
deferred to an explicit human approve/deny in the desktop UI before
proceeding. Not implemented in v0.1 — today, authorize_purchase is
synchronous and decided fully by the guards above. Roadmap.
3.12 Audit Context
Every mutation initiated by an agent is automatically tagged with
actor_type="mcp_agent" and actor_details containing the agent's id, name,
scope, and current session_spend_so_far. Capture is automatic via
AuditMiddleware — no per-route plumbing required.
Source: backend/app/core/audit_context.py and backend/app/middleware/audit.py.
4. MCP tools
This section is normative. Zado exposes four Model Context Protocol tools via
a local stdio MCP server (backend/app/mcp/server.py) that bridges to the
Zado HTTPS API (backend/app/mcp/client.py). All authentication, scope
enforcement, guard checks, audit logging, and database access happen
server-side at zadofi.ai. The local MCP process is a pure transport layer
and cannot bypass policy.
AI Agent → MCP stdio → ZadoAPIClient → HTTPS Bearer → zadofi.ai → policy + envelopes
4.1 check_budget
Scope required: read
HTTP backing route: GET /api/spending/category/{category}
Returns the budgeted, spent, and remaining amounts for a single envelope.
Request schema:
| Field | Type | Required | Description |
|---|---|---|---|
category |
string | yes | Category slug (e.g., "groceries", "dining", "fun-money") |
Success response schema:
| Field | Type | Description |
|---|---|---|
category |
string | Human-readable envelope name |
remaining |
number | Available balance in dollars |
budgeted |
number | Monthly budgeted amount in dollars |
spent |
number | Amount spent so far this month in dollars |
percentage_used |
number | spent / budgeted * 100 |
Example request:
{ "category": "groceries" }
Example success response:
{
"category": "Groceries",
"remaining": 276.50,
"budgeted": 400.00,
"spent": 123.50,
"percentage_used": 30.875
}
4.2 list_envelopes
Scope required: read
HTTP backing route: GET /api/envelopes/summary?month=YYYY-MM
Returns all envelope balances for the current calendar month.
Request schema: No parameters. The MCP tool implicitly uses the current
month in YYYY-MM form (UTC).
Success response schema:
| Field | Type | Description |
|---|---|---|
month |
string | YYYY-MM of the month being reported |
total_budgeted |
number | Sum of all envelope budgets |
total_spent |
number | Sum of all envelope spending |
total_available |
number | total_budgeted - total_spent |
envelopes |
array | Per-envelope detail (see below) |
Each envelopes[] entry:
| Field | Type | Description |
|---|---|---|
name |
string | Envelope name |
budgeted |
number | This envelope's budget |
spent |
number | This envelope's spending |
remaining |
number | This envelope's available balance |
percentage_used |
number | spent / budgeted * 100 |
status |
string | Envelope health status (e.g., "on_track", "warning", "empty") |
Example success response:
{
"month": "2026-04",
"total_budgeted": 2400.00,
"total_spent": 1820.30,
"total_available": 579.70,
"envelopes": [
{
"name": "Groceries",
"budgeted": 400.00,
"spent": 123.50,
"remaining": 276.50,
"percentage_used": 30.875,
"status": "on_track"
},
{
"name": "Dining",
"budgeted": 200.00,
"spent": 198.00,
"remaining": 2.00,
"percentage_used": 99.0,
"status": "warning"
}
]
}
If the agent token has allowed_category_ids set, the response is filtered
to only those envelopes (server-side, via
backend/app/services/agent_guard.py:filter_by_binding).
4.3 get_daily_status
Scope required: read
HTTP backing route: GET /api/spending/status
Returns today's spending posture across all envelopes — total available, daily allowance, and any active alerts.
Request schema: No parameters.
Success response schema:
| Field | Type | Description |
|---|---|---|
total_available |
number | Sum across all envelopes (or bound envelopes) for current month |
daily_allowance |
number | Sustainable daily spend rate for the rest of the month |
days_remaining |
number | Days left in the current month (including today) |
alerts |
array | Active spending alerts |
Each alerts[] entry:
| Field | Type | Description |
|---|---|---|
category |
string | Envelope name the alert targets |
type |
string | Alert type (e.g., "envelope_empty", "pace_warning", "upcoming_bill") |
message |
string | Human-readable alert text |
Example success response:
{
"total_available": 579.70,
"daily_allowance": 96.62,
"days_remaining": 6,
"alerts": [
{
"category": "Dining",
"type": "pace_warning",
"message": "You're spending faster than the envelope allows for the rest of the month."
}
]
}
4.4 authorize_purchase
Scope required: spend
HTTP backing route: POST /api/agents/purchase
Authorizes and logs a purchase if and only if the envelope has enough funds AND all guards pass. This is the load-bearing tool of the spec — it implements the envelope-as-permission pattern end-to-end.
Request schema:
| Field | Type | Required | Description |
|---|---|---|---|
amount |
number | yes | Purchase amount in dollars (e.g., 43.20) |
category |
string | yes | Category slug — must match an envelope and (if bound) the token's allowed_category_ids |
vendor |
string | yes | Merchant or vendor name (free text, e.g., "Whole Foods") |
Authorization flow (from backend/app/services/agent_spending.py):
- Scope check. Token must have
scope == "spend". Otherwise reject withinsufficient_scope. - Envelope binding check. If the token has
allowed_category_ids, the request's category must resolve to one of them. Otherwise reject withenvelope_not_bound. - Guards. In order: per-transaction cap, session cumulative cap, rate limit, budget pacing. Any failure rejects with the corresponding code.
- Envelope balance check. The envelope must have enough remaining funds
for the purchase. Otherwise reject with
envelope_empty. - Atomic commit. Guard state update + transaction creation + envelope-balance update commit together. Any exception rolls back all three.
Success response schema:
| Field | Type | Description |
|---|---|---|
authorized |
boolean | Always true for this shape |
transaction_id |
string | UUID of the created transaction |
amount |
number | Echoed authorized amount |
category |
string | Echoed category slug |
vendor |
string | Echoed vendor name |
envelope_remaining |
number | Envelope balance AFTER the purchase |
Example success response:
{
"authorized": true,
"transaction_id": "8f2a1e3c-7b40-4d8e-9c71-c1d2e3f4a5b6",
"amount": 43.20,
"category": "groceries",
"vendor": "Whole Foods",
"envelope_remaining": 233.30
}
Rejection response schema:
| Field | Type | Description |
|---|---|---|
authorized |
boolean | Always false for this shape |
reason |
string | One of the rejection codes below |
detail |
object | string | Code-specific context (see below) |
Rejection codes:
| Code | Triggered by | Context fields | Recommended agent retry |
|---|---|---|---|
insufficient_scope |
Token has scope != "spend" |
detail (string explanation) |
Do NOT retry. Re-register the agent with scope: "spend" and a fresh token. |
envelope_not_bound |
Request category not in token's allowed_category_ids |
category, bound_category_ids (array) |
Do NOT retry the same category. Either rebind the token to include this category, or use a different category that IS bound. |
per_transaction_cap_exceeded |
amount > token.per_transaction_cap |
limit (the cap, in dollars) |
Do NOT retry the same amount. Split the purchase into multiple smaller calls (each ≤ cap), or raise the cap. |
session_cap_exceeded |
session_spending_total + amount > token.session_spending_cap |
limit (cap), session_total (current spent) |
Do NOT retry. Wait for the 24-hour session reset, or have the user raise the session cap. |
rate_limited |
More than 3 calls in the last 60 seconds | limit (3), retry_after_seconds (int) |
Wait retry_after_seconds then retry. |
exceeds_budget_pace |
Purchase consumes more than pace_multiplier × daily_pace of the envelope |
daily_pace, pace_limit, days_remaining, envelope_remaining, pace_multiplier |
Either retry with a smaller amount ≤ pace_limit, or have the user raise pace_multiplier. |
envelope_empty |
Envelope remaining < amount after all prior guards passed |
detail (recommendation string) |
Do NOT retry. Either pick a different category or have the user fund the envelope. |
A transport-level error (network failure, malformed response from the API)
returns reason: "api_error" — this is not a policy decision and the
agent SHOULD treat it as a retryable infrastructure fault.
Example rejection:
{
"authorized": false,
"reason": "exceeds_budget_pace",
"detail": {
"allowed": false,
"reason": "exceeds_budget_pace",
"daily_pace": 17.16,
"pace_limit": 51.49,
"days_remaining": 6,
"envelope_remaining": 102.97,
"pace_multiplier": 3.0
}
}
4.5 Concurrency and atomicity
authorize_purchase uses a row-level FOR UPDATE lock on the agent token
row (backend/app/services/agent_guard.py) to prevent TOCTOU race conditions
between guard evaluation and guard-state update. On SQLite (which does not
support FOR UPDATE), the underlying connection-level write serialization
provides the same guarantee. Concurrent calls on the same token are
serialized; concurrent calls on different tokens for the same envelope race
on the envelope row, with the loser receiving envelope_empty if the
balance flipped between check and commit.
5. Audit log
Every mutation initiated by an agent is automatically tagged with an audit
context capturing who acted, what they did, and when. Coverage is
automatic via AuditMiddleware — no per-route plumbing required, so future
endpoints inherit audit coverage without code changes.
5.1 Actor model
The audit system supports six actor types
(backend/app/core/audit_context.py):
| Actor type | Source |
|---|---|
user |
A human using the desktop UI with a session cookie |
assistant |
The Zado in-app AI coach |
rule |
An automated rule (categorization, recurring detection) |
sync |
Bank sync operations (Plaid / Teller) |
system |
Internal system operations |
mcp_agent |
An external AI agent via an MCP token |
For agent-initiated requests, actor_type is set to mcp_agent and
actor_details is populated with the agent's identity and current spend
posture (backend/app/api/deps.py populates the audit context as part of
get_user_context / get_agent_context).
5.2 Audit context schema
For an agent-initiated mutation:
| Field | Type | Description |
|---|---|---|
actor_type |
string | Always "mcp_agent" for agent calls |
actor_details.agent_id |
string (UUID) | The agent token's id |
actor_details.agent_name |
string | Human-readable agent name (e.g., "Claude Code", "OpenClaw") |
actor_details.scope |
string | "read" or "spend" |
actor_details.session_spend_so_far |
string (decimal) | Cumulative session spending at the moment of the action |
batch_id |
string (UUID) | Optional, groups related changes for bulk undo |
Example audit entry for an authorize_purchase success:
{
"id": "9b7f8c2d-1a4e-4f6b-83c1-d5e6f7a8b9c0",
"actor_type": "mcp_agent",
"actor_details": {
"agent_id": "1d73176f-5a24-4c8e-b21f-3a4b5c6d7e8f",
"agent_name": "OpenClaw",
"scope": "spend",
"session_spend_so_far": "47.50"
},
"action": "transaction.create",
"entity_type": "transaction",
"entity_id": "8f2a1e3c-7b40-4d8e-9c71-c1d2e3f4a5b6",
"before": null,
"after": {
"amount": 43.20,
"category_slug": "groceries",
"vendor": "Whole Foods",
"agent_token_id": "1d73176f-5a24-4c8e-b21f-3a4b5c6d7e8f"
},
"occurred_at": "2026-04-25T18:42:11.382Z"
}
5.3 Coverage and exportability
- Coverage: All POST/PUT/PATCH/DELETE requests are audited via
backend/app/middleware/audit.py. Read-only requests are NOT audited (they do not mutate state). - Exportability: The audit log is exportable by the user. The export
shape is a stable JSON list of audit entries; the export endpoint is part
of the data-export surface (
backend/app/api/export/).
6. Security model
6.1 What agents CAN see
For a token with the read scope:
- Envelope names and balances (budgeted, spent, remaining, percentage used,
status) for the user's envelopes — filtered to bound categories if the
token has
allowed_category_ids. - Daily allowance, days remaining in month, and spending alerts.
- Affordability check results (whether a hypothetical purchase would fit).
For a token with the spend scope:
- All of the above, AND
- The transaction id, amount, category, vendor, and remaining envelope balance for any purchase the agent itself authorized.
6.2 What agents CANNOT see
For ANY scope:
- Bank account numbers, routing numbers, or institution-issued identifiers.
- Bank connection tokens (Plaid / Teller
access_tokenvalues). These are encrypted at rest and never returned in any API response, audit entry, or coach context. - Raw transaction history outside the agent's bound categories.
- Personal information beyond what the user explicitly exposes via envelope names (e.g., the user's email, phone, address, or other accounts).
- Any other agent's tokens, transactions, or activity.
- Any other user's data.
This separation is enforced by the architecture: the local MCP process has
zero direct database access. It can only call the four documented HTTPS
endpoints with its Bearer token. There is no MCP tool that returns raw bank
data because no such tool exists at any layer
(backend/app/mcp/tools.py).
6.3 Defense in depth
A successful authorize_purchase traverses seven distinct enforcement
layers, any one of which can deny:
| # | Layer | Where enforced |
|---|---|---|
| 1 | Token hashing (SHA-256 at rest) | backend/app/models/agent_token.py (hash_token) |
| 2 | Bearer token transport over HTTPS | backend/app/mcp/client.py (httpx Bearer header) |
| 3 | Token lookup + is_active check (revoked → 401) |
backend/app/api/deps.py (get_agent_context) |
| 4 | Scope enforcement (spend required for authorize_purchase) |
backend/app/services/agent_spending.py + backend/app/middleware/agent_scope.py |
| 5 | Envelope binding check (category must be in allowed_category_ids) |
backend/app/services/agent_guard.py (check_envelope_binding) |
| 6 | Spending guards (per-tx cap, session cap, rate limit, pacing) | backend/app/services/agent_guard.py (check_and_record_spend) |
| 7 | Atomic commit with FOR UPDATE row lock on the token row |
backend/app/services/agent_guard.py + backend/app/services/agent_spending.py |
A request that passes layer 4 but fails layer 5 produces an
envelope_not_bound rejection. A request that passes layer 6 but fails the
final balance check produces envelope_empty and the guard-state mutation
is rolled back so the rejected attempt does not consume the agent's session
cap.
6.4 Threat model (informative, non-normative)
This subsection is informative; it captures the threats the spec is designed to resist and the threats it explicitly does not.
In scope:
- Stolen agent token used by an attacker. Mitigation: per-transaction
cap + session cap + envelope binding + kill switch contain blast radius.
An attacker with a stolen token can spend at most
min(per_tx_cap, session_cap, envelope_remaining)before either being kill-switched or hitting a guard. - Compromised local agent runtime. Mitigation: MCP process has no direct DB access; all enforcement is server-side.
- Race condition between guard check and guard update. Mitigation:
FOR UPDATErow lock onagent_tokensrow. - Cross-user data leakage via agent token. Mitigation: tokens carry
user_idFK; every server-side query filters byuser_id.
Out of scope (not addressed by this spec):
- Compromised user account (session-cookie theft). The user is the root of trust.
- Compromised Zado server. The spec assumes the policy-evaluation server is operating correctly.
- Side-channel inference attacks (an agent learning user habits from envelope balances over time).
7. Integration examples
This section is informative. Three patterns cover the common integration shapes. All examples assume the integrator has already registered an agent in the Zado app (Settings → Agent Access → Create Agent) and obtained a Bearer token.
7.1 Claude Code (.mcp.json)
The native Claude Code integration. Add to your project's .mcp.json:
{
"mcpServers": {
"zado": {
"command": "python",
"args": ["-m", "app.mcp.server"],
"cwd": "/path/to/zado/backend",
"env": {
"ZADO_API_URL": "https://zadofi.ai",
"ZADO_AGENT_TOKEN": "zado_agent_aBcDeFgHiJkLmNoPqRsTuVwXyZ..."
}
}
}
}
Once Claude Code restarts, the four tools (check_budget, list_envelopes,
get_daily_status, authorize_purchase) appear in the available tool set
under the zado namespace. (v0.2+ roadmap: a packaged zado-mcp PyPI
distribution will replace the python -m app.mcp.server invocation with
a single command: "zado-mcp" line.)
7.2 Generic MCP stdio client
For non-Claude agent runtimes, any MCP-compatible client can call Zado tools over stdio. Conceptual Python sketch:
import asyncio, os
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
async def main():
params = StdioServerParameters(
command="python",
args=["-m", "app.mcp.server"],
env={
"ZADO_API_URL": "https://zadofi.ai",
"ZADO_AGENT_TOKEN": os.environ["ZADO_AGENT_TOKEN"],
},
)
async with stdio_client(params) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
budget = await session.call_tool(
"check_budget", {"category": "groceries"}
)
print(budget)
result = await session.call_tool(
"authorize_purchase",
{"amount": 43.20, "category": "groceries", "vendor": "Whole Foods"},
)
print(result)
asyncio.run(main())
The same pattern works in any language with an MCP client library (TypeScript SDK, Go SDK, etc.). The wire protocol is identical; only the SDK surface differs.
7.3 OpenClaw end-to-end (real-world reference)
This is the integration that produced the 2026-04-03 first-loop. The flow:
- Agent receives a shopping intent from the user via OpenClaw's normal intake (e.g., "buy groceries at Whole Foods, around $45").
- Agent calls
check_budget("groceries")to confirm the envelope has capacity. Response:{ "remaining": 47.50, "budgeted": 400.00, "spent": 352.50, ... }. - Agent reads the response and decides the purchase fits.
- Agent calls
authorize_purchase(43.20, "groceries", "Whole Foods"). The server runs scope check → binding check → guards (caps, rate limit, pacing) → envelope balance check → atomic commit. Response:{ "authorized": true, "transaction_id": "...", "envelope_remaining": 4.30 }. - The transaction appears immediately in the user's ledger with
actor_type: "mcp_agent"andactor_details.agent_name: "OpenClaw"in the audit log. - The user can revoke the agent at any time via Settings → Agent Access → Freeze All Agents, instantly invalidating the token.
Settlement note: in v0.1, authorize_purchase records a Zado ledger entry
representing a policy decision that the spend is permitted. It does not
itself move money on a card rail. v0.2+ roadmap describes how a Zado
authorization can bind to a Stripe Issuing one-time virtual card so the
authorization and the card-rail charge become a single closed-loop
transaction.
8. Compatibility with adjacent protocols and platforms
This section is informative. It describes how Zado v0.1 maps to (or deliberately does not map to) the agent-payment work being done by other players. Zado is designed to complement, not compete with, each of these.
8.1 Google AP2 — Agent Payments Protocol
Google AP2 defines two cryptographically-signed Verifiable Digital Credential (VDC) primitives for agent payments:
- Intent Mandate — captures the conditions under which an agent may purchase on behalf of a user (human-not-present scenarios).
- Cart Mandate — captures the user's explicit authorization for a specific cart and price (human-present scenarios).
Zado mapping:
| AP2 primitive | Closest Zado primitive in v0.1 | Notes |
|---|---|---|
| Intent Mandate | An agent token's combined scope + allowed_category_ids + caps + pace_multiplier |
Zado expresses Intent as policy stored on the token, not as a signed VDC. v0.2+ roadmap: emit AP2-format Intent Mandate VDCs alongside token issuance. |
| Cart Mandate | An authorize_purchase success response |
The transaction_id and envelope_remaining are the canonical record of the user's policy authorization. v0.2+ roadmap: produce a signed VDC at authorize_purchase success for AP2 interop. |
| Verifiable Digital Credentials (VDCs) | Not in v0.1 | v0.2+ roadmap. |
The v0.1 spec is AP2-compatible at the policy layer — a Zado token is sufficient to express the policy half of an Intent Mandate — but does not yet emit AP2 wire-format signed credentials. An agent runtime that wraps Zado calls in AP2-format VDCs today would do the signing externally.
8.2 Stripe Machine Payments Protocol (MPP)
Stripe MPP (launched March 18, 2026) defines scope-bound, time-bound credentials for agents to invoke Stripe-rails payments. MPP focuses on the execution side of agent payments: how an agent presents itself to Stripe, what scope of payment it can initiate, and how Stripe authorizes the rail call.
Zado mapping: Zado is the consumer-side policy layer an MPP-scoped agent can consult before invoking MPP-routed payment. The flow:
- Agent receives intent.
- Agent calls Zado
check_budgetand/orauthorize_purchaseto confirm the spend is permitted by the user's envelope policy. - If Zado authorizes, the agent invokes its MPP credential to actually move money on Stripe rails.
- The Zado authorization + Stripe MPP charge are two halves of a closed-loop transaction (today, the linking is done by the integrator; v0.2+ roadmap binds a Stripe Issuing virtual card to a Zado authorization so the link is automatic).
Zado does not duplicate or replace any Stripe MPP capability. They sit at different layers.
8.3 Visa Intelligent Commerce Connect
Visa Intelligent Commerce Connect (April 2026, GA expected June 2026) provides network-side payment infrastructure for AI agents — tokenization, spend controls, multi-network acceptance. Like MPP, it focuses on the execution side, not consumer-side policy.
Zado mapping: Zado is the consumer-side budget gate that complements
network-side spend controls. A Visa-Intelligent-Commerce-routed agent
purchase can consult a Zado-equivalent policy layer for envelope-level
authorization before the network sees the request. v0.2+ roadmap: Zado
publishes a network-callable HTTPS endpoint (POST /api/agents/network/preauth)
that returns a normalized permitted: true|false response keyed by Visa
agent ID for direct integration with network-side tokenization flows.
8.4 Mastercard Agent Pay
Mastercard Agent Pay (rolling out to all US Mastercard cardholders by Q4 2026 holiday season) provides Agentic Tokens that consumers can configure with per-agent spend scopes. Like Visa IC, this is network-side and consumer-controlled — but the consumer interface is provided by Mastercard's own platform.
Zado mapping: Zado offers an alternative consumer surface for the same job — bank-account-backed (via Plaid) rather than card-network-bound, with envelope semantics that fit how ADHD-impacted users actually budget. The two are not mutually exclusive: a consumer can have both a Mastercard Agentic Token (for card-rail purchases) AND a Zado token (for any ACH-funded purchase or any agent-runtime that uses MCP rather than a card network's SDK).
8.5 Model Context Protocol (MCP)
Zado tools are exposed via standard MCP. The protocol-level conformance:
- All tools registered via
FastMCP.tool()(backend/app/mcp/server.py). - Stdio transport (local-only in v0.1).
- No bespoke extensions to the MCP protocol — any MCP-compatible client works.
v0.2+ roadmap: HTTP+OAuth2.1 transport per the MCP Authorization spec, allowing remote Zado MCP without a local Python process.
9. Future capabilities (v0.2+ roadmap)
The following capabilities are explicitly NOT shipped in v0.1. Integrators MUST NOT depend on them today. They are listed here so partners can plan and so the spec's evolution is transparent.
- Human Approval Gate. A UI inbox in the desktop app where an agent's
authorize_purchaserequest can be deferred to an explicit human approve/deny before it commits. Today,authorize_purchaseis synchronous and decided fully by the policy guards. The v0.2 design adds an optionalrequires_human_approval: trueflag on the token (or per-request) that parks the request in a pending state, fires a notification, and resolves on user action. - Stripe Issuing one-time virtual card binding. A successful
authorize_purchaseissues a single-use virtual card via Stripe Issuing bound to the authorized amount, vendor (where supported), and a short expiry. The card-rail charge and the Zado authorization become a closed loop that reconciles automatically via Plaid transaction sync. - Refund and dispute reconciliation. When a merchant refunds an
agent-initiated purchase, the corresponding envelope balance auto-corrects
and the agent's
session_spending_totalcredits back. Dispute lifecycle events update the audit trail. - AP2 Verifiable Digital Credential emission. Token issuance produces a
signed AP2 Intent Mandate VDC;
authorize_purchasesuccess produces a signed AP2 Cart Mandate VDC. Lets Zado-authorized spend flow into AP2 ecosystems without external signing. - HTTP+OAuth2.1 MCP transport. Per the MCP Authorization spec, remote Zado MCP without a local Python process. v0.1 is stdio-only.
- Multi-user envelope approval. Couples and small businesses where envelope spending requires N-of-M human approvers. Same envelope-as- permission model, with an additional approval-quorum primitive on the envelope.
- Network-side preauth endpoint.
POST /api/agents/network/preauthreturnspermitted: true|falsewith envelope context for direct consumption by Visa Intelligent Commerce or Mastercard Agent Pay network-side flows.
The version that ships any of these capabilities will increment to v0.2 or later per the versioning policy in Section 10.
10. License and versioning
10.1 License
This specification is licensed under CC-BY 4.0. You may share, adapt, and build upon it for any purpose, including commercial use, with attribution to Evolving Intelligence AI, LLC and a link to this canonical URL.
The reference implementation in this repository is licensed under the
project's existing license (see project root LICENSE).
10.2 Versioning policy
The spec follows a relaxed semantic versioning model:
- Patch versions (0.1.x): editorial corrections, clarifications, fixes to ambiguous wording. No behavioral changes. No deprecations.
- Minor versions (0.x): MAY add new tools, fields, rejection codes, or scopes. MUST NOT remove or rename existing tools, fields, rejection codes, or scopes. MUST NOT change the meaning of an existing field.
- Major versions (1.0+): MAY break compatibility. A migration guide MUST accompany any major version.
An integrator targeting v0.1 can rely on every documented surface continuing to work in any v0.x release.
10.3 Citation
Cite this spec as:
Zado Agent Trust Protocol v0.1, Evolving Intelligence AI, 2026-04-25. https://zadofi.ai/protocol/zado-agent-trust-protocol-v0.1
BibTeX is in docs/protocol/README.md.
Specification version: 0.1
Date: 2026-04-25
Author: Evolving Intelligence AI, LLC
Spec license: CC-BY 4.0
Reference implementation license: see project LICENSE
Canonical URL: https://zadofi.ai/protocol/zado-agent-trust-protocol-v0.1
Source repository: see project README.md
Reference implementation source files:
backend/app/models/agent_token.py,
backend/app/services/agent_spending.py,
backend/app/services/agent_guard.py,
backend/app/mcp/server.py,
backend/app/mcp/tools.py,
backend/app/mcp/client.py,
backend/app/core/audit_context.py,
backend/app/middleware/audit.py.