An open protocol · Apache-2.0 + CC BY 4.0 · v0.2.0 · on npm
counter-sign

An agent signs its intent.
A human countersigns it into authority.

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 if is 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 band
const 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" },
  new TelegramAdapter(),
  { receiptLog: new ReceiptLog("./receipts.jsonl") });

await deploy("v2.1.0"); // runs only once a human countersigns; every decision is logged
10 · How it's governed

Built to be everyone's. Authored by someone.

Code license
Apache-2.0
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 ↗.
Category
Agent Accountability Rails
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.