Мандат и кошелёк-ядро
Полномочия (что агенту можно) и компонент, который их enforce'ит (кошелёк). Ключевой принцип Stipendex: LLM не прикасается к деньгам — см. инвариант I1 в overview.md.
Mandate — делегирование полномочий
Подписанный принципалом JSON. Хранится в кошельке; получателям не раскрывается (в Proof уходит только hash).
{
"stipendex": "mandate/0.1",
"mandate_id": "m_7c1f…",
"principal_key": "ed25519:AAAA…",
"wallet_key": "ed25519:BBBB…",
"limits": {
"currency": "USD",
"per_tx": "1.00",
"per_day": "20.00",
"total": "100.00"
},
"allow": [
"registry:4agents/verified",
"mcp:exa-search",
"https://api.example.com"
],
"approval_over": "1.00",
"approval_mode": "async",
"valid_until": "2026-08-15T00:00:00Z",
"purpose": "research tools + purchases for project X",
"sig": "<jws принципала>"
}
| Поле | Правила |
|---|---|
limits |
все три уровня обязательны; кошелёк ведёт счётчики durable (переживают рестарт) |
allow[] |
источники доверия: прямой id получателя, URL, либо реестр (registry:<провайдер>/<уровень>, см. trust-registry.md). Пустой список = платить нельзя никому |
approval_over |
суммы выше — только с Confirmation; 0 = подтверждать всё |
approval_mode |
interactive | async | deny — см. HITL ниже |
valid_until |
SHOULD: дни, не месяцы — короткий TTL вместо инфраструктуры отзыва; отзыв досрочно — через revocation фасилитатора |
purpose |
человекочитаемое назначение; показывается в каждом запросе подтверждения |
Мандатов может быть несколько (по проекту/агенту); кошелёк выбирает по получателю и назначению, при неоднозначности — самый ограничивающий.
Кошелёк-ядро (Wallet Kernel)
Детерминированный компонент при хосте агента (библиотека, сайдкар-процесс или удалённый сервис). Нормативное поведение:
Интерфейс для агента — ровно одна операция
wallet.pay(quote) → { status: "paid", proof }
| { status: "pending_approval", eta }
| { status: "denied", reason }
- Никаких параметров суммы, получателя, адреса. Сумму и получателя определяет Quote, подписанный получателем, — тексту модели верить негде и незачем.
- MUST NOT существовать иных денежных операций, доступных модели (transfer, topup, export ключа и т.п.). Prompt injection упирается в отсутствие поверхности атаки.
reasonвdenied— машиночитаемый код (not_in_allowlist,per_day_exceeded,quote_expired…), чтобы агент мог честно объяснить принципалу.
Порядок проверки (MUST, именно в этом порядке)
- Подпись и срок Quote;
quote_idне израсходован ранее. Клейм quote_id атомарен; при отказе без движения денег он снимается (легитимный повтор возможен) и становится постоянным только после успешного расчёта. - Получатель ∈
allow, default deny. Прямая запись (id/URL) пропускает квоту только еслиquote.receiver.keyсовпадает с ключом, которому кошелёк уже доверяет для этого id (пиннинг в конфигурации или однократно полученный дескриптор) — иначе атакующий мог бы выпускать самоподписанные квоты под доверенным именем (T1). Registry-записи проверяются по подписанному провайдером дескриптору. - Лимиты:
per_tx→ дневной счётчик →total. Дляestimate— считаетсяestimate + tolerance(худший случай). Прохождение проверки атомарно резервирует сумму (без окна check-then-record): резерв держится на время HITL-ожидания, освобождается при отказе/сбое и остаётся записью о трате при успехе — конкурентные платежи и очередь подтверждений не могут совместно превысить мандат. - Порог
approval_over→ HITL (ниже). Checkout-профиль — Confirmation всегда. После долгого интерактивного ожидания кошелёк перепроверяет срок квоты. - Выбор рельса (пересечение
quote.railsи настроенных адаптеров; приоритет — настройка кошелька) → расчёт → Proof.
Ключи
- Ключ кошелька живёт вне процесса агента (keychain ОС, отдельный демон, KMS) — процесс с LLM-контекстом не имеет к нему доступа. Кошелёк подписывает, но не хранит средства — средства у рельса/фасилитатора.
- Компрометация хоста ограничена мандатом: максимум ущерба = остаток лимитов до
valid_untilв пределах allowlist. Это проектная граница ущерба, задаваемая принципалом заранее. - Для server-hosted агентов граница та же, но удалённо: ключи MUST оставаться в хранилище под контролем принципала (его устройство, его KMS), и никакой сервис оператора не может исполнить платёж в одностороннем порядке. Это не только свойство безопасности — это регуляторная граница, удерживающая кошелёк в статусе некастодиального (вне периметров money transmitter / MiCA custody).
HITL — подтверждение человеком
Три режима (approval_mode), единый объект Confirmation на выходе:
| Режим | Поведение |
|---|---|
interactive |
вопрос принципалу в текущем канале (MCP elicitation / CLI-промпт); ответ ждётся синхронно |
async |
платёж в pending-очередь (у кошелька или фасилитатора) + push принципалу; агенту сразу pending_approval, он продолжает остальную работу; TTL очереди = TTL Quote |
deny |
headless-строгий: всё сверх порога — denied; для cron-агентов без канала к человеку |
Confirmation — зафиксированное согласие
{
"stipendex": "confirmation/0.1",
"confirmation_id": "c_5e2b…",
"quote_id": "q_8f3d1c…",
"mandate_id": "m_7c1f…",
"approved_total": { "value": "92.40", "currency": "USD" },
"tolerance_ack": true,
"channel": "elicitation",
"approved_at": "2026-07-16T12:03:02Z",
"sig": "<jws кошелька>",
"principal_assertion": "<webauthn/passkey assertion | null>"
}
- Базовый уровень: подписывает кошелёк, фиксируя канал, показанный текст и ответ.
- Сильный уровень (SHOULD для checkout):
principal_assertion— passkey/WebAuthn подпись принципала на digest подтверждения. Это машиночитаемое доказательство акцепта конкретной суммы конкретным человеком — то, что предъявляется банку при чарджбэке и закрывает юридическую дыру «полномочия агента» (риск H5 из 4agents order-flow). Направление совместимо с AP2/FIDO. - Подтверждение привязано к
quote_id: «да» на один итог нельзя переиспользовать для другого.
Что кошелёк отдаёт принципалу
- Журнал:
mandate → confirmation → quote → proof → receiptпо каждой операции. - Счётчики лимитов в реальном времени («$3.20 из $20 сегодня»).
- Pending-очередь с кнопками approve/deny.
- Экспорт цепочки доказательств по операции (для спора/чарджбэка).