MCP connects agents to tools. A2A connects agents to each other.
counter-sign fills the third leg: what happens when an agent needs a human's yes.
01 · The gap
Agents can act. Nobody standardised who says yes.
Every serious agent platform agrees: before an agent spends money, posts publicly, or touches a customer, a human should sign off. But each platform invents its own private way of asking — the pause, the ping, the approval, the audit log, all locked inside one vendor's SDK.
The ecosystem standardised two conversations. The third one was missing.
MCP
Agent ↔ Tool
Anthropic · adopted everywhere
A2A
Agent ↔ Agent
Google · cross-vendor teamwork
COUNTER-SIGN
Agent ↔ Human authority
The missing leg
02 · The pitch, in motion
Twenty seconds on why this exists.
MCP gave agents tools. A2A gave agents colleagues. counter-sign gives agents a boss.
03 · The whole spec is four nouns
Small enough to read over coffee. Sharp enough to audit against.
Intent the ask
A small, signed JSON envelope the agent emits before any consequential action. What it wants to do, the risk tier, who can approve, how many, and how long it will wait. An Intent asks; it never authorizes.
Route the delivery
The Intent travels to wherever the approver already lives — Telegram, Discord, Slack, WhatsApp, email. No new dashboard to check. Adapters are pluggable and dumb on purpose.
Countersignature the receipt
The human's decision returns as a signed, portable, tamper-evident receipt: who approved, under which policy, at what time. This is the only artifact that conveys authority — and the one an auditor asks for.
Default the timeout rule
Every Intent declares what happens if nobody answers. Silence is never ambiguous. A routine post might default to reject in 15 minutes; the timeout produces a signed receipt too.
04 · When one yes isn't enough
Two-person control, built in.
The most sensitive actions shouldn't rest on a single click. An Intent can require quorum — M distinct approvers must each countersign before the action is authorized.
Distinct actors
No self-quorum
Repeated approvals from the same person count once. Two people means two people.
Any reject vetoes
One no halts it
A single reject resolves the Intent immediately and finally, however many approvals were already collected.
Fail-closed
Silence never grants
A quorum that doesn't complete in time yields the Default — and a quorum can never be paired with default-approve.
05 · Try it · you are the human in the loop
An agent wants to post. You hold the pen.
Left: the Intent the agent emits. Right: what lands in your Telegram. Approve it, reject it, or ignore it and watch the Default fire.
intent · emitted by publisher-agent
{
"countersign": "0.2",
"intent_id": "3f0a…e921",
"action": "linkedin.post.create",
"summary": "Publish Q3 campaign post
for Acme Bank",
"risk_tier": "high",
"approvers": ["telegram:cmo"],
"quorum": 1,
"timeout": 900,
"default": "reject"
} // signed by the agent key ↓sig ed25519:7bc1…e94a
Telegram · via counter-sign adapter
publisher-agent requests approval:
"Publish Q3 campaign post for Acme Bank" · risk: high
Defaults to REJECT in 15:00(demo runs fast)
06 · Receipts, not booleans
An approval that evaporates after the ifis worth nothing in an audit.
A Countersignature is ed25519-signed, names the actor and the policy, pins the exact intent, and verifies offline — no callback to the issuer, no trust in the system that stored it.
Portable
Verifiable by anyone
Hand a party the receipt and the Intent; they can check it themselves, forever, across vendors.
Integrity ≠ authority
Bind to a trusted key
A receipt that verifies against its own key only proves it wasn't tampered with. To act, bind it to an authority you trust.
ReceiptLog
Hash-chained history
An opt-in, append-only hash-chained log. Each receipt is tamper-evident on its own (its signature); anchor the chain head() out-of-band and any edit, reorder, or deletion below it is caught as diverged.
verify the approval history — authenticity (keyed) and completeness (vs. your anchor)
const head = await receiptLog.head(); // anchor { length, hash } out of bandconst report = await receiptLog.verifyAll({ trustedKeys: [AUTHORITY_PUBLIC_KEY], expectedHead: head });
// { total, valid, ok, faults: [...], chain: { intact, brokenAt?, reason? } }// ok === true ⇢ every receipt is genuine AND the log is intact against your anchor.
07 · Routes
Reach the approver where they already are.
One Adapter interface: deliver the Intent, hand back the decision. Push where the channel supports it, poll where it doesn't. What the approver sees is derived from the signed envelope — an adapter never alters or re-signs it.
Telegram
Inline Approve/Reject buttons; decisions arrive as verified callback queries.
Discord
Interaction buttons with Ed25519 signature verification on every callback.
Slack
Block Kit message; v0 request-signature check before any payload is trusted.
WhatsApp
Meta Cloud API with hub-signature verification on inbound webhooks.
Email
Signed, single-use links that expire with the timeout — and a GET never decides.
Local
No-network terminal approver for demos, tests, and CI. Zero tokens.
08 · Security by construction
The guarantees are in the signatures, not the servers.
Verify before acting
An unverifiable receipt is not a decision. The runtime must confirm the decision was signed by the authority it trusts for the Route — never act on self-consistency alone.
Domain separation
Every signature commits to an artifact-type context, so a signature minted for an Intent can never be replayed as a Countersignature or an email link.
Transport auth
Adapters verify their platform's webhook authentication — Slack/Discord/Meta signatures — before trusting a callback.
Key custody
Agent keys and authority keys are distinct roles: one forges requests, the other forges authority. Keep them separate.
09 · Quickstart
Gate any function behind a human, in about five lines.
wrap a tool call — the shim signs the Intent, routes it, and only runs on approval
import { wrapAction, ReceiptLog } from"@countersignlabs/counter-sign";
import { TelegramAdapter } from"@countersignlabs/counter-sign/adapters/telegram";
const deploy = wrapAction(deployProd,
{ action: "deploy.prod", summary: "Deploy v2.1.0 to production",
risk_tier: "high", approvers: ["telegram:8675309"],
timeout: 300, default: "reject" },
newTelegramAdapter(),
{ receiptLog: newReceiptLog("./receipts.jsonl") });
awaitdeploy("v2.1.0"); // runs only once a human countersigns; every decision is logged
Schemas, adapters, and the shim — patent grant included, so enterprise counsel can relax.
Spec license
CC BY 4.0
The specification text may be republished and translated freely with attribution. Standards spread by being quotable.
Stewardship
Authored, sponsored
Spec author Haridarman Kumaresan; sponsored by Agentsstack Pte. Ltd. Anyone can implement the protocol; CLAIRE is the first reference deployment. Filed as an IETF Internet-Draft ↗.
counter-sign is the first proposed component of a broader category — Agent Accountability Rails (AAR): cryptographic, human-named, mandate-scoped accountability infrastructure for autonomous agents. Other rails address credential-issuance lifecycle, delegated-mandate transport, and provider-side receipt verification. No single vendor should own the category.
11 · The one-line pitch
MCP gave agents tools. A2A gave agents colleagues. counter-sign gives agents a boss.