Self-service reference / reviewed 5 September 2026
Private Agent Handoff: setup and threat model
One lead agent shares selected brief fields with one reviewer. The kit signs and encrypts locally, and checks policy before releasing the brief at the recipient. It does not host messages, execute tasks, or authorize a payment.
Download the kit · SHA-256 checksum · Source and tests
What is available now?
A free MIT-licensed Python kit, an executable offline demo, negative-path checks, this setup guide and a synthetic browser policy simulator. The downloaded demo performs real encryption and signature checks. The browser simulator does not encrypt, deliver or run a task.
No application, setup call, invoice or Ontario approval is needed. Guided implementation is no longer offered. This is a local reference, not a hosted private messaging service or managed agent platform.
Payment and automation
The local kit is free and self-service. No wallet, account, sales call or Ontario approval is required. Your team provisions keys, policy and transport in its own environment. Once integrated, routine handoffs use your worker's policy without asking Ontario to approve each one. Your team still owns operations, security review and incident handling.
Ontario's existing x402-paid APIs are separate: agents pay per call in USDC on Base and routine verified calls are fulfilled automatically. Downloading this kit does not call or charge those APIs. No website card checkout is offered for this free kit.
Open the guided setup assistant for environment-specific steps and local troubleshooting. It uses fixed rules, keeps your choices in the browser and never asks for keys or private task data.
Start here
Your first local handoff
Use Python 3.11 or newer and the synthetic example supplied in the kit. Nothing is uploaded to Ontario. Dependency installation needs internet access; the demo and checks run offline afterwards.
Download and inspect
Download the ZIP, extract it, then open a terminal in the extracted
ontario-private-agent-handofffolder containingrequirements.txt. Keep this outside a public repository.Download checksum · Inspect source and MIT license. The checksum checks file consistency, not an independent security audit.
Install in an isolated environment
macOS / Linux
python3 -m venv .venv .venv/bin/python -m pip install -r requirements.txt .venv/bin/python demo.py .venv/bin/python -m unittest -v checksWindows PowerShell commands
py -3 -m venv .venv .\.venv\Scripts\python.exe -m pip install -r requirements.txt .\.venv\Scripts\python.exe demo.py .\.venv\Scripts\python.exe -m unittest -v checksUse a private Windows user directory with restrictive access controls. POSIX file modes do not configure Windows ACLs. The published release checks run on macOS; Windows is not independently validated.
Check the result
{ "status": "passed", "selected_fields": ["objective", "deliverable"], "receipt_verified": true, "replay_denied": true, "executed": false, "paid": false }The demo generates temporary keys in memory, signs and encrypts a synthetic brief, accepts it once, verifies the receipt and rejects a second acceptance. It prints no keys or task contents. Its temporary replay database is removed when the run ends.
The checks must finish with
OK. This is engineering verification, not proof that your production environment is secure. The demo does not run an LLM, deliver a review or spend money.Connect your own reviewer
Keep the sender's private key with the sender and the reviewer's private key with the reviewer. Pin public keys through your trusted provisioning channel. Initialize durable recipient state once, select approved fields, then deliver the envelope over your existing authenticated transport.
Your worker calls
accept()before using the brief. On any denial, stop; do not bypass policy or reset replay state. Acceptance is recorded before plaintext is returned, so this is at-most-once release, not guaranteed job completion. After a crash, a new handoff needs authorization.Python integration example · Step-by-step CLI and private outputs · Production checklist and limits
When something does not pass
No module named jwcrypto- Install requirements and run commands with the same
.venvPython, not your system Python. requirements.txtnot found- Open the extracted kit folder first. Do not run commands inside the ZIP or its parent directory.
already_consumed- Expected when repeating acceptance. If a retry is legitimately needed, authorize a new handoff. Never delete live replay state to force it through.
recipient_state_missing- For first-time setup, initialize the receiver's state with
handoff.py init-state. If live state was lost, stop processing and recover under your state-migration policy. expired_or_not_yet_valid- Check both host clocks and transport delay. Generate a newly authorized handoff within the 60 to 900 second lifetime; do not disable expiry.
unverifiable_envelopeor policy denial- Check pinned public keys, recipient private key, exact issuer/audience and field allowlist. Reject altered input; never trust a key supplied by the incoming message.
Inspect each CLI step
After the one-command demo, use this macOS/Linux example to inspect private files and run each handoff step separately. Start in a fresh extracted kit folder with synthetic data only. Unlike the demo, these commands retain private keys and plaintext locally.
python3 -m venv .venv
.venv/bin/python -m pip install -r requirements.txt
.venv/bin/python -m unittest -v checks
mkdir -m 700 local-demo
.venv/bin/python handoff.py keygen \
--private local-demo/lead.private.json --public local-demo/lead.public.json
.venv/bin/python handoff.py keygen \
--private local-demo/reviewer.private.json --public local-demo/reviewer.public.json
.venv/bin/python handoff.py init-state --state local-demo/reviewer.sqlite3
.venv/bin/python handoff.py seal \
--brief brief.example.json --fields objective,deliverable \
--issuer agency:lead --audience agency:reviewer --ttl 300 \
--issuer-key local-demo/lead.private.json \
--recipient-key local-demo/reviewer.public.json \
--out local-demo/handoff.jwe
.venv/bin/python handoff.py accept \
--envelope local-demo/handoff.jwe \
--issuer agency:lead --audience agency:reviewer \
--issuer-key local-demo/lead.public.json \
--recipient-key local-demo/reviewer.private.json \
--fields objective,deliverable --state local-demo/reviewer.sqlite3 \
--out local-demo/accepted.jsonaccepted.json contains private plaintext and a recipient-signed receipt. Never publish or log it. The console contains a decision only. Repeating acceptance with a new output path must return already_consumed.
For a real integration, separate the sender and receiver processes and keep each private key at its owner. Deliver the encrypted envelope over your existing authenticated transport. Pin public keys and identifiers through an independent trusted channel; never take a trust key from the incoming envelope.
Connect the gate to your worker
This integration outline uses the same functions as the executable demo.py. Key loading, authenticated transport and your reviewer implementation belong to your environment; the snippet intentionally does not invent these components.
from handoff import seal, accept, verify_receipt
# Sender: keys loaded from your protected key store.
envelope, handoff_id = seal(
brief=approved_brief, fields=["objective", "deliverable"],
issuer="agency:lead", audience="agency:reviewer",
issuer_key=lead_private_key,
recipient_key=reviewer_public_key, ttl=300,
)
# Receiver: authenticated delivery; keys pinned independently.
# Initialize this durable state once during provisioning, not per request.
accepted = accept(
envelope, issuer="agency:lead", audience="agency:reviewer",
issuer_public_key=lead_public_key,
recipient_key=reviewer_private_key,
state="/private/receiver/decisions.sqlite3",
allowed_fields=["objective", "deliverable"],
)
receipt = verify_receipt(
accepted["receipt"], reviewer_public_key,
"agency:lead", "agency:reviewer", envelope,
)
# Only now may your worker use accepted["brief"] under its own policy.
# Do not log the brief. The kit itself never executes a review or pays.On any exception, deny delivery and record a payload-free error in your own operations system. A receipt proves acceptance by the pinned key, not completed work. If a worker crashes after consumption, require a newly authorized handoff instead of retrying the same envelope or resetting state.
To revoke an unaccepted handoff, run handoff.py revoke --state local-demo/reviewer.sqlite3 --issuer agency:lead --id HANDOFF_ID_FROM_SEAL on the receiver. An accepted result means the brief was already released and cannot be retracted.
What is enforced?
| Control | Reference behavior |
|---|---|
| Data minimization | Only selected objective, deliverable and style strings. Recipient independently allowlists fields. Maximum selected payload: 8,192 bytes. |
| Identity binding | Locally pinned signing/decryption keys and exact issuer/audience. Names are operator-configured identifiers, not certified real-world identities. |
| Scope | review:brief only; zero spending permission. No execution engine. |
| Freshness | 60 to 900 seconds. Receiver clock checks issuance, not-before and expiry. |
| Replay | One atomic acceptance per issuer and handoff ID in the recipient's durable SQLite store. Missing or corrupt state blocks acceptance. |
| Revocation | Authorized operator writes a revocation into the recipient's local store before acceptance. No remote revocation service. |
| Receipt | Recipient-signed acceptance record with a hash of the encrypted envelope. No task contents; not proof of execution, quality or payment. |
Threat model and limits
The reference assumes uncompromised hosts, private keys, authenticated provisioning, synchronized clocks, and durable state. The team retains responsibility for transport authorization, key lifecycle, monitoring, data classification, backups and recipients.
- Tests cover wrong issuer/key/recipient, altered envelopes, expiry, replay, local revocation, unexpected fields, scope escalation, nonzero spending and unavailable state. Passing tests is not an independent audit.
- An authorized receiver can retain or forward plaintext. Revocation cannot undo an accepted handoff.
- Field selection does not detect secrets inside an approved field. This is not automated redaction or data-loss prevention.
- Encryption does not solve prompt injection, malicious instructions, endpoint compromise, or traffic analysis. RSA encryption in this kit does not provide forward secrecy; no post-quantum claim is made.
- Local files use restrictive permissions, not encryption at rest. Protect keys, accepted briefs and backups separately.
- Replay protection covers one shared recipient store, not independent replicas. Distributed deployment needs a shared atomic store and revocation design. Do not roll back or delete live replay state.
- Receipts are not OAuth tokens, Ontario safety attestations, or certified identities. Keep existing authorization, and do not forward one service's bearer token to another.
- Production use with sensitive data requires an organizational security/privacy review. No compliance certification, guaranteed safety or production SLA is supplied.
What does Ontario receive?
The downloaded kit makes no network requests and sends no keys, envelopes or task payloads to Ontario. Installing dependencies contacts the package index. The website serves ordinary page assets and receives normal hosting request metadata. Its simulator uses fixed synthetic data without local storage or analytics events.
There is no application form or confidential-payload upload. The former POST /api/private-handoff/pilot now returns 410 Gone with self-service links and does not create an application, alert or charge. Earlier application records remain private; contact hello@ontarioprotocol.com for deletion of a past submission. Never send task briefs, private keys or credentials to Ontario.
Protocol and library references
The kit uses JWCrypto for JWS RS256, followed by JWE RSA-OAEP-256 / A256GCM, with RSA keys of at least 3072 bits. Fixed headers reject compression, key-lookup URLs and algorithm negotiation. The requirements pin JWCrypto 1.6.0; inspect its security release history.
These are standard JOSE envelopes with application-specific review claims. They do not replace MCP transport authorization. Ontario's existing payment preflight remains separate and is not invoked by this zero-spend review kit.