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 statuslists 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, soconfirmrefuses 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_taskresult that arrives while the very first observation is still resolving can be missed until the next check-back. LocalStoreis 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)
| 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)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| 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.