Skip to main content

1M Handshake Addon

The free client-side addon for the 1M Agent Handshake registry. It connects your organization's A2A-capable agent to the registry and supplies proof when your agent makes a real call to another organization's public AgentCard.

Installing and using the addon is free. A fee is charged only when a credit is redeemed to create a permanent, public handshake record in the registry.

Install

pip install 1m-handshake

Set up your organization

Run the install command once for your organization:

1m-handshake install

The addon uses organization metadata supplied by a compatible agent host when it is available. Otherwise, it asks the owner for the organization's public domain, then first tries a configured enterprise agent identity. If that is unavailable, it guides the owner through one-time work-email or DNS-TXT domain verification.

By default, setup also installs a visible 1M Handshake skill in the agent workspace. It gives the agent a voluntary capability to consider a claim after its own meaningful A2A interactions; it is not a listener, does not observe traffic, and never modifies AGENTS.md, CLAUDE.md, or other broad instructions. Run setup from the workspace root, or specify it directly:

1m-handshake install --workspace /path/to/agent-workspace

Use --no-agent-skill only when you want verification without capability installation.

The default endpoint is the public Freshstart testing service. Invited pilot testers can use --staging instead of writing the staging URL. Use --backend-url only when you have been given a different environment URL; --staging and --backend-url cannot be combined.

1m-handshake install --staging

Staging payment test

This optional, resettable test uses a local disposable Base Sepolia wallet. It never uses real money, requires owner approval by default, and is not a production approval or permanent public record.

1m-handshake staging-status
1m-handshake staging-claim counterparty.example

staging-claim makes a real A2A call only after confirmation, then performs a one-cent test settlement and records the result in staging. The addon keeps only privacy-safe local pilot milestones (for example, “funding ready” or “record redeemed”); it never logs wallet keys, prompts, agent messages, or call contents.

Create a handshake

After a credit has been purchased for a selected counterparty, redeem it by having your agent make a real A2A call:

1m-handshake on-demand <credit-code>

You can also use an eligible prepaid credit for a chosen organization:

1m-handshake use-credit counterparty.example

The addon never stores wallet or payment-provider credentials. Autonomous x402 redemption is opt-in and requires a signer supplied by the host integration.

Service agents (inbound)

A service agent that receives jobs from customers can record them as handshakes. The service is the party that wants the record (Org A); the customer is the counterparty. Wrap the service's own a2a-sdk request handler:

from handshake_addon.inbound import InboundHandshakeHandler
from handshake_addon.local_store import LocalStore

handler = InboundHandshakeHandler(
    inner=my_request_handler,
    org_a_domain="myservice.example",
    store=LocalStore(),
    customer_domain=resolve_customer,
    # context_for_customer=context_for,   # optional, see "Restarts"
    # check_schedule_seconds=(60, 600, 3600, 21600, 86400),   # default
    # resolver_timeout_seconds=1.0,                           # default
)
# pass `handler` wherever the service passed `my_request_handler`

The wrapper calls inner with identical arguments and returns or yields identical objects. Observation runs in background tasks after the reply is available, so the customer's reply is never held back waiting for it, and the reply is never altered. The customer never sees any 1MAH text. Exceptions raised by inner propagate unchanged. One caveat on latency: the LocalStore reads and writes run on the event-loop thread, and SQLite waits up to its default 5 s busy timeout for a lock. If the 1m-handshake CLI or the outbound interceptor holds the lock at that moment, the event loop (and so other requests) can stall for up to that long. That is fine at pilot scale.

org_a_domain must be the domain you passed to 1m-handshake install. It is lowercased, and if it differs from the stored domain the handler logs a warning naming both, because status and confirm would not see anything it records.

Use the same HANDSHAKE_ADDON_HOME as the 1m-handshake CLI (default ~/.1m-handshake), so the CLI sees what the handler records. On shutdown, await handler.aclose() cancels background tasks. await handler.drain() waits for in-flight observations (useful in tests).

Run the wrapped handler in a single worker process. Each handler instance has its own boot id, so in a multi-worker server another worker's first call would relabel this worker's live pending jobs as "Server restarted". Check-back contexts and the SDK's default in-memory task store are also per process.

customer_domain

customer_domain(params, context) is your function. It receives the SendMessageRequest and the ServerCallContext and returns the customer's hostname, or None. A job is skipped when it returns None, raises, takes longer than resolver_timeout_seconds (1 s), or returns something that is not a dotted domain name such as customer.example (single-label names and IP addresses are rejected; it is lowercased). Prefer an async function. A sync function runs inline on the event loop, so it must not block, and the timeout cannot interrupt it.

What gets recorded

  • A first job that ends completed, or is answered with a message, is stored as a recordable proof.
  • Any other first outcome (still running, input required, failed and so on) becomes a pending entry. A pending entry is never recordable.
  • 1m-handshake status lists recordable customers under "Recordable:" (inbound ones are marked "(inbound)") and pending jobs under "Pending:" with the reason, first seen, last checked and next check.
  • 1m-handshake confirm <customer-domain> records a recordable customer. It can start a payment flow (a held credit, your autonomous or assisted payment setup, or a checkout link). A customer that is only pending has no proof, so confirm refuses it with the pending reason, before any payment starts, until the job completes.
  • A customer already recorded (by either direction) is never listed as pending.
  • Pending reasons: the job is still running; waiting for the customer's input; waiting for the customer to authenticate (these three end in "will check again" while a check-back is running, and in "no longer checking automatically." when none is); the job ended as failed, rejected or canceled (a later completed job from that customer can still be recorded); "No longer checking; job state unknown"; "Server restarted; couldn't confirm whether this job finished."; "Job is still running after the restart; no longer checking automatically."; "Job lost with the restart; outcome unknown."
  • A pending entry with no check-back running never blocks the customer: a later job from them gets its own pending entry or proof.

Check-back

Check-back runs for every pending job that has not ended, including jobs waiting for the customer's input or authentication; it does not run for jobs that ended failed, rejected or canceled. The handler asks inner.on_get_task at 60 s, 10 min, 1 h, 6 h and 24 h after it first saw the job (check_schedule_seconds). A job that has completed is promoted to recordable. When the schedule is exhausted and the job has still not completed, the entry stays pending with the reason "No longer checking; job state unknown". The customer's own get_task or subscribe calls promote a job only when they return completed; any other final state they show (input or authentication required, failed, rejected, canceled) updates the pending reason. Check-back uses the original call's context, kept in memory only and never persisted.

Restarts

Pending entries record which handler instance created them. When a new instance handles its first observing call (send_message, streaming send_message, get_task or subscribe; other methods do not trigger it), it relabels pending jobs from the previous run as "Server restarted; ..." and stops tracking them. Nothing happens at construction. With the SDK's default in-memory task store, jobs do not survive a restart, so they stay relabelled. To re-check them actively you need both a persistent task store and the optional context_for_customer(customer_domain) hook, which returns a ServerCallContext (or None, sync or async, same 1 s timeout) that lets inner look up that customer's task. Even then, a job that is still running at restart is re-checked once at startup, not repeatedly; the re-check records what it finds (completed is promoted, an ended job shows how it ended, a running job says it is still running). A job the task store no longer knows is labelled "Job lost with the restart; outcome unknown."

Limits

  • Push-notification completion is not observed. Completion is noticed by check-back and by the customer's own polling or subscribe calls.
  • A customer's own get_task result that arrives while the very first observation is still resolving can be missed until the next check-back.
  • LocalStore is a single SQLite file and is not meant for heavy multi-worker concurrency (run one worker, see above). A busy lock can cause one capture to be missed. It never breaks a reply.
  • Inbound claims are self-reported. Nothing can corroborate that the customer sent the job. The public record page shows "Job received from ".

License

MIT. See LICENSE.

Metadata

Release files for 1m-handshake 0.1.7

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for 1m-handshake 0.1.7
File Size Uploaded Provenance Actions
1m_handshake-0.1.7.tar.gz 104.4 kB Without provenance Details for 1m_handshake-0.1.7.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for 1m-handshake 0.1.7
File Interpreter ABI Platform Actions
1m_handshake-0.1.7-py3-none-any.whl Python 3 none any Details for 1m_handshake-0.1.7-py3-none-any.whl

Total release size: 171.3 kB

Release files / 1m_handshake-0.1.7.tar.gz

Download URL 1m_handshake-0.1.7.tar.gz
Size 104.4 kB
Tags Source
SHA-256 checksum
How to use checksums
cdabd6c272f0ba4c6720ac491bf1829f5511310c95b15080e0313639c0a874c5
BLAKE2b-256 checksum
How to use checksums
63fc9818eee30c5f5b911ba25ef3dc6fa22a89fe6789713d92c5599e8c730b1e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.3

Release files / 1m_handshake-0.1.7-py3-none-any.whl

Download URL 1m_handshake-0.1.7-py3-none-any.whl
Size 66.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
dbebc08ec33742b257748b8a8d5fca0fe3df8d58e1ce9857bc71d8ebecd09b33
BLAKE2b-256 checksum
How to use checksums
05816b4ebde23b4a492eb463794943f87ef11147692454c5ff8f784fd486d543
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.3

Security for 1m-handshake 0.1.7

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page