On 27 September 2026, Apollo's chief economist Torsten Slok described agents that sweep household cash from 0.1 % checking accounts into accounts paying 3.3 to 5 %. David Kirk added on 6 October that agents will react to bank risk as well. Agents with identical logic would then leave the same bank at nearly the same time.
I wanted to know whether the mandate formats that agents will carry can express limits against that. I went through the current Verifiable Intent specification and drafted constraint types for deposit movements, together with the check a receiving bank would run.
What Verifiable Intent supports today
Verifiable Intent (VI) is Mastercard's layered SD-JWT credential format (Selective Disclosure JSON Web Token, a signed token whose fields can be revealed one by one) for agent authorization. Mastercard and Google contributed VI and AP2 (Agent Payments Protocol) to the FIDO Alliance in April 2026. All references below point to spec/constraints.md at commit 356c296 in github.com/agent-intent/verifiable-intent. The repository has not changed since 20 April 2026.
Each constraint is a JSON object with a required type field (§3.1). Parsers must preserve fields they do not recognize (§3.2).
VI registers eight constraint types (§6.2), covering merchants, line items, payees, amount range, budget, merchant-managed recurrence, agent-managed recurrence and a checkout-payment reference. For mandate.payment.agent_recurrence, the frequency field "guides timing but does not strictly enforce it" (§4.7).
Custom types are allowed (§6.1). Organisation-specific types use reverse-DNS or URN names ("Model B").
§5.4 has a rule that matters for deployment. Verifiers default to PERMISSIVE and skip unknown constraint types. For open mandates, the ones an agent carries before a concrete payment exists, this does not apply: "verifiers MUST reject open mandates containing unknown constraint types." A savings agent's mandate is an open mandate. Every verifier on the path therefore has to implement the new types before such a mandate is accepted anywhere.
Draft types
All amounts follow VI's convention of integer minor units per ISO 4217. Periods use the ISO 20022 frequency codes that VI already uses in §4.6.
{
"constraints": [
{ "type": "mandate.payment.amount_range", "currency": "USD", "max": 2500000 },
{ "type": "mandate.payment.budget", "currency": "USD", "max": 5000000 },
{ "type": "ch.moltrust.deposit.min_hold_days", "days": 90 },
{ "type": "ch.moltrust.deposit.sweep_threshold_bp", "min_rate_diff_bp": 50, "rate_basis": "APY" },
{ "type": "ch.moltrust.deposit.max_sweeps_per_period", "max": 2, "period": "QUTR" },
{ "type": "ch.moltrust.deposit.insured_counterparty_only",
"schemes": ["US-FDIC", "EU-DGSD", "CH-esisuisse"],
"max_insured_balance": { "currency": "USD", "amount": 25000000 } },
{ "type": "ch.moltrust.deposit.execution_window_hours", "hours": 24 }
]
}
min_hold_days binds the agent. Funds that arrive at an account may leave it again only after N days.
sweep_threshold_bp requires a minimum rate difference between source and target, in basis points (1 bp = 0.01 %). rate_basis fixes which published rate is compared.
max_sweeps_per_period caps the number of inter-bank transfers per period.
insured_counterparty_only restricts targets to institutions covered by one of the listed deposit guarantee schemes. The balance at a single target stays at or below max_insured_balance, here $250,000.
execution_window_hours delays execution after a trigger by an offset inside H hours.
Validation at the agent
The agent's local enforcement checks every planned transfer before it signs the closed payment mandate. The sketch below returns the same three dispositions as moltrust-enforce.
from hashlib import sha256
def check_transfer(c, t, history, evidence):
# c: constraint values from the open mandate
# t: planned transfer (source, target, amount, trigger_id, trigger_time, exec_time)
# history: earlier transfers under the same mandate
# evidence: signed rate and insurance observations for this trigger
last_in = history.last_arrival_at(t.source)
if last_in and (t.exec_time - last_in.time).days < c.min_hold_days:
return "forbid", "holding period not elapsed"
diff = evidence.rate(t.target) - evidence.rate(t.source)
if diff < c.min_rate_diff_bp:
return "forbid", "rate difference below threshold"
if history.count_in_period(c.period, t.exec_time) >= c.max_sweeps:
return "forbid", "sweep limit reached"
ins = evidence.insurance(t.target)
if ins.scheme not in c.schemes:
return "forbid", "target not covered"
if history.balance_at(t.target) + t.amount > c.max_insured_balance:
return "forbid", "above insured limit"
if abs(t.exec_time - (t.trigger_time + window_offset(t, c))) > TOLERANCE:
return "hold", "outside execution window"
return "allow", None
def window_offset(t, c):
seed = sha256(f"{t.mandate_id}|{t.trigger_id}".encode()).digest()
return int.from_bytes(seed[:8], "big") % (c.hours * 3600) # seconds
The offset derives from mandate ID and trigger ID. Each agent gets its own delay for the same trigger, and a third party can recompute that delay later.
rate and insurance read values with signature and validity window from the evidence record. Bank rates change during the day and coverage status changes rarely, yet both must be recomputable as they stood at the moment of the trigger. MolTrust's Attested-Evidence Replay keeps these inputs together with the decision, so the check can be run again offline.
What the receiving bank checks
The receiving bank holds the closed payment mandate together with the open mandate it references. It verifies the SD-JWT chain as specified by VI and reads the disclosed deposit constraints. It checks min_hold_days against its own product terms and assigns the conditions for the attested period. The bank cannot enforce the holding period itself. It sees a breach when the money leaves early, and the evidence record shows which agent, under which mandate, made the transfer.
Open issues
Registration. Until the types sit under mandate.payment.* or a verifier implements them, §5.4 makes every mandate that carries them unusable. Model B names are therefore only a transitional step.
Data sources. The insurance check needs a signed source per deposit guarantee scheme. FDIC BankFind is available for the US, and each EU member state has its own scheme. The rate comparison needs signed rate observations from both banks.
Rate basis. APY (Annual Percentage Yield, the effective annual rate including compounding) and nominal rate give different differences. The mandate should fix the basis, and the evidence should state which basis was observed.
The deposit constraints exist as a draft. Feedback on the schema is welcome in the comments.
Top comments (0)