An open protocol for AI agents that need to send each other typed, signed messages on the public web. Built for scheduling, introductions, receipts, and other coordination flows where a user delegates an agent to act on their behalf.
Status: v0.1, experimental. Wire formats, trust semantics, and APIs may change without backward-compatible migration before v1.0.
| Spec | specs/ |
| Docs | ink.tulpa.network |
| Contributing | CONTRIBUTING.md |
| Security | SECURITY.md |
| Code of Conduct | CODE_OF_CONDUCT.md |
| Changelog | CHANGELOG.md |
Every INK message is an Ed25519-signed envelope over a JCS (RFC 8785) canonical serialization. The signature base binds the protocol version, HTTP method, request path, recipient DID, body, and timestamp. Replay protection uses a per-sender nonce plus a timestamp freshness window of 5 minutes past and 30 seconds future.
Message types cover intents, challenges, resolutions, receipts, audit events, encrypted payloads, and authenticated agent-card queries. Handshake messages carry a correlation ID; audit and receipt messages do not. Key rotation is governed by an authority rule documented in docs/key-rotation-rule.md: the Agent Card's published key set is canonical, revoked keys never verify, and a stale bootstrap key cannot bypass rotation.
INK assumes AT Protocol for identity by default but isn't coupled to it. Any system that can publish an Ed25519 signing key under a stable identifier can participate.
npm install @adastracomputing/inkThe package ships TypeScript sources directly. Consumers need a TS-aware toolchain (tsx, ts-node, esbuild, Vite, etc.).
import {
generateKeypair,
deriveAgentId,
signInkMessage,
verifyInkSignature,
verifyInkAuth,
} from "@adastracomputing/ink";
const keypair = await generateKeypair();
const agentId = deriveAgentId(keypair.publicKey);
const input = {
method: "POST",
path: "/ink/v1/tulpa:zRecipient/intent",
recipientDid: "tulpa:zRecipient",
body: {
protocol: "ink/0.1",
type: "network.tulpa.intent",
from: agentId,
to: "tulpa:zRecipient",
intent: "meeting_request",
timestamp: new Date().toISOString(),
nonce: crypto.randomUUID(),
},
timestamp: new Date().toISOString(),
};
const signature = await signInkMessage(input, keypair.privateKey);
const ok = await verifyInkSignature(input, signature, keypair.publicKey);For inbound request verification, verifyInkAuth parses the Authorization: INK-Ed25519 <sig> header, checks freshness, and applies the key-rotation authority rule. It requires a nonceStore option so the 5-minute freshness window does not silently accept replays; pass a NonceStore to have the middleware enforce single-use, or "deferred" to acknowledge that the caller will run checkReplay (or equivalent) elsewhere in the request pipeline.
For consumers of bilateral audit-exchange responses (network.tulpa.audit_response), call both verifyAuditResponseSignature (signed response wrapper) and verifyAuditEventChain (sequence-by-one and previousEventHash continuity, fork detection). The signature gate alone does not prevent a peer from returning a gapped or forked slice.
For consumers of witness audit-query responses (network.tulpa.audit_query_response, Auditability §7.3, added in 0.1.0-alpha.3), call verifyAuditQueryResponse({response, witnessPublicKey, expectedRequester, expectedMessageId, verifyEventSignature, expectedServiceDid?, laterCheckpoint?}). The verifyEventSignature callback is REQUIRED: it resolves the submitting agent's Ed25519 keys (typically via Agent Card §2) and validates each event's agentSignature. Without it, the verifier refuses to return valid, because Merkle inclusion alone does not prove a real agent produced the event (§7.5). The verifier enforces envelope shape, the requester binding (prevents cross-requester replay), events/proofs strict one-to-one alignment, the §7.4 per-event scope rule, walks every Merkle proof via computeAuditMerkleLeafHash up to the response's rootHash, runs verifyEventSignature on every event and supports an optional later-checkpoint cross-check. The lower-level verifyAuditQueryResponseSignature is signature-only and is not sufficient to accept a witness response on its own.
npm test # vitest
npm run typecheck # tsc --noEmit
npm run lint # eslint
npm run check:surface # public-surface drift checkFor Nix users: nix develop gives a pinned Node 22 + git + gitleaks shell. nix build produces the publishable npm tarball under result/. nix run github:Ad-Astra-Computing/ink -- verify-inclusion --file receipt.json --witness https://witness.example.com runs the CLI without installing anything globally.
src/ library implementation
crypto/ signing, multi-key verification, key encoding
models/ Zod schemas for Agent Card, handshake, key entries
middleware/ transport-level INK auth (verifyInkAuth)
discovery/ Agent Card fetching and candidate-key extraction
ink/ discovery gating, handshake budget, receipts, checkpointing
specs/ protocol spec documents
docs/ maturity notes, threat model, key rotation rules
test-vectors/ JSON interop vectors
test/ vitest unit + integration tests
The library runs on any runtime providing standard Web Crypto and fetch: Node 22+, Deno, Bun, Cloudflare Workers, browsers. The timestamp freshness window is enforced inside verifyInkAuth; nonce single-use is enforced when a NonceStore is passed (otherwise checkReplay must be called separately). Nonce backing storage and its TTL policy are the integrator's choice.
Reliable to depend on:
- Envelope structure and signing base
- Authorization: signed intent plus Agent Card key set
- Key rotation authority rule (see
docs/key-rotation-rule.md) - Replay protection (nonce plus timestamp window)
Subject to change before v1.0:
- Authorization chain framing (delegation and attenuation semantics)
- Containment vocabulary (capability-gated visibility, sender budgets)
- Interop conventions with non-AT-Protocol identity systems
- Receipt and audit envelope shape for third-party witnesses
You will see network.tulpa.* on the wire (e.g. network.tulpa.intent) and ink.tulpa.network for the docs site. Both are historical artifacts of the protocol's origin and do not imply a runtime dependency on Tulpa. A vendor-neutral namespace may be introduced in a future revision.
INK is developed by Ad Astra Computing as the underlying protocol for Tulpa. The spec and the library in this repo are deliberately free of Tulpa product code so other agent platforms can adopt INK without inheriting Tulpa's surface area. Tulpa's product integration (message orchestration, marketplace, user-facing APIs) lives in a separate, closed-source codebase.
See SECURITY.md for the disclosure path. The threat model is in docs/threat-model.md. Do not open a public issue for security problems.
Dual-licensed under either of:
- MIT (
LICENSE-MIT) - Apache 2.0 (
LICENSE-APACHE)
at your option. The Apache 2.0 license includes an explicit patent grant; MIT is the simpler text. Pick whichever fits your downstream policy. This covers the code, specs, docs, and test vectors. Contributions are accepted under both licenses.