Skip to main content

Verified Payment Agreements

A Verified Payment Agreement turns a plain-language arrangement ("I'm selling my boat to Mike for $80,000 — $10,000 when we sign, the rest protected until the title transfers") into a deterministic, versioned policy: verified parties, protected funds, and releases that happen only when the agreed conditions are proven — never on trust, and never on chat text.

The invariants every surface enforces:

  • The agreement is the source of truth. Chat, email, and OCR only draft; the content-addressed policy the parties accept is what binds.
  • AI never moves money. The drafting assistant works through an allowlisted tool surface with no accept/authorize/release verbs.
  • Events inform; the policy decides. Confirmations, webhooks, and documents become verified facts; the deterministic engine evaluates them and only then authorizes a release.
  • Pay the verified DID. Releases settle through Pay-the-DID to the bound beneficiary identity — never to bank details seen in an email.
  • Material changes need re-acceptance. Acceptance and the payer's EIP-712 authorization bind exact hashes; a changed term invalidates both automatically.

Lifecycle

DRAFT ─propose─▶ AWAITING_ACCEPTANCE ─all accept + authorize─▶ ACTIVE

protected funds deposited (FULL_PREFUND) │

facts proven ─▶ releases authorized ─▶ paid via Pay-the-DID


COMPLETED

Disputes pause the affected steps (money stays protected); every conditional step carries a deadline and a fallback (PAUSE_FOR_RESOLUTION, CANCEL_AND_REFUND_STAGE, or NOTIFY_ONLY).

Draft → card → accept

# 1. Draft (or use the AI chat / a template / an emailed invoice)
permara agreements create "Boat sale to Mike" \
--currency USD --total-minor 8000000 \
--payer "Mike Reynolds" --payee "Lance E." --payee-email mike@example.com

# 2. Compile the release policy from a template
permara agreements compile <agreementId> \
--template deposit-balance --deposit-minor 1000000 \
--deadline 2026-12-31T00:00:00Z

# 3. Review the deterministic Agreement Card — the exact terms that bind
permara agreements card <agreementId>

# 4. Freeze + share + accept
permara agreements propose <agreementId>
permara agreements share <agreementId> payee # single-use claim link for app.permara.com
permara agreements accept <agreementId> payer # hash echo of the reviewed card

Activation additionally requires the payer's EIP-712 AgreementAuthorization (scope ACTIVATION) covering the exact agreement + policy hashes and the release manifest — preview the typed data with POST /v1/agreements/:id/authorizations/preview, sign with the registered member key, and submit.

AI drafting chat (and the agent runtime)

The chat is LLM-first but the model holds no authority: it drafts through a typed, allowlisted tool registry and deterministic code decides. There is no tool that accepts, authorizes, funds, releases, shares or signs — those live only on the review surface, bound to the exact agreement/policy hashes.

permara ask "Create a $500 USD purchase order for Harbor Supply. Pay 25% after acceptance and the rest after confirmed delivery." --plan-only --json
cat brief.txt | permara ask - --json # message from stdin (explicit only)
permara ask "and the balance on delivery" --session <id> # continue a session
permara payables upload invoice-2041.pdf # → uploadId (bytes never reach the model)
permara ask "Set up the PO from the attached invoice" --attach <uploadId>
permara ask "Send it for review" --confirm send_for_review # host confirmation for a host-required tool
permara chat # interactive; streams the reply, Ctrl-C stops a turn, /confirm <tool>, /attach <uploadId>, /quit
permara agent doctor # environment, provider, writes switch, manifest reachability
permara agent tools --json # the scope-filtered tool manifest
permara agent eval --suite golden --fixtures docs/agent-platform/fixtures/acceptance-fixtures.json
  • Turns are idempotent: pass clientTurnId; a replay returns the stored result, a different text under the same id is a 409.
  • --plan-only / mode: 'plan' runs read tools and returns write tools as proposals with zero side effects.
  • Providers are configuration (AGREEMENTS_AI_PROVIDER: anthropic, openai, deepseek, or the deterministic mock in keyless sandboxes); every turn records providerMeta for reproducibility.
  • Financing follows the lending engine (ADR 0030): "I'd like financing for 100%" is recorded on the draft and shown on the card. With LENDING_ENABLED off that is where it stops and the draft cannot activate until the request is withdrawn. With it on, request_financing_offer (host-confirmed) approves the prepared document onto the draft, registers the purchase order and returns the pool's signed offer as two numbers — what the pool pays the supplier, what the business repays by when — once the supplier has joined the agreement; accept_financing_offer (host-confirmed) signs the acceptance and draws the loan after the supplier has signed the purchase order, which fulfils the request and unblocks activation. Nothing is priced, applied for or funded by the model itself.
  • Clarifications are deterministic and ordered by execution risk, never by model confidence.
  • Kill switches: AGREEMENTS_AI_ENABLED=false (chat off, guided flow stays), AGREEMENTS_AI_WRITES_ENABLED=false (reads only).

Contract, threat model and fixtures: docs/agent-platform/ (ADR 0026).

Proof: confirmations and event sources

# A party confirms a condition (signed-in attestation)
permara agreements confirm <agreementId> --requirement balance-confirmed

# An external system attests through a registered event source
permara agreements event-sources add <agreementId> \
--name "Carrier webhook" --requirement delivered
# → endpointId for the policy's WEBHOOK_ENDPOINT trust rule,
# HMAC secret shown once, ingest path /v1/event-sources/<endpointId>/events

Event-source deliveries are HMAC-signed (safebank-signature: t=<unix>,v1=<hex> over <t>.<rawBody> — the same scheme Permara's outbound webhooks use), timestamp-bounded, replay-fenced per nonce, and content-deduped. permara agreements event-sources test mints a runnable signed sample.

Payables: emailed invoices

Forward vendor invoices to your tenant's payables address (permara payables address). Each email is scanned, OCR-extracted with per-field confidence, and gated:

  • low-confidence critical fields require your corrections;
  • duplicates are flagged;
  • changed bank details are flagged and block drafting until you verify with the vendor out-of-band — extracted bank details are risk signals, never payment instructions.

permara payables approve <extractionId> turns the reviewed extraction into an agreement draft (kind INVOICE), entering the same accept/authorize/ protect lifecycle as every other agreement.

Observability

  • permara agreements timeline <id> — plain-language history plus "why is this waiting?" derived from the latest deterministic decision.
  • permara agreements decision <id> — the decision record (hash-chained).
  • permara agreements record <id> — the Verified Payment Record: a portable, hashed proof of what was agreed, proven, authorized, and settled.
  • Outbound webhooks: subscribe to agreement.* events (proposed, activated, funded, release settled, dispute opened, completed, …).

Feature gates

EnvEffect
AGREEMENTS_ENABLEDMaster gate — off means every route 404s.
AGREEMENTS_AI_ENABLEDLLM chat path (default on; needs ANTHROPIC_API_KEY in production).
AGREEMENT_ESCROW_ADDRESSOn-chain protection via AgreementEscrow (sandbox mock otherwise).
PAYABLES_OCRllm for the DeepSeek vision reader (DEEPSEEK_API_KEY, no AWS); textract for AWS Textract; deterministic mock otherwise.
PAYABLES_OCR_MODELVision model for PAYABLES_OCR=llm; default deepseek-flash.