npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

agent-toolprint

v0.1.1

Published

Double-signed receipts for tool invocations by AI agents — DSSE + JCS + Ed25519, verifiable offline.

Downloads

267

Readme

agent-toolprint

Double-signed receipts for every tool invocation by an AI agent. Portable, hash-binding, byte-canonical, verifiable offline by any third party.

agent-toolprint is the format that answers one audit question, cleanly: "yes, agent X called tool Y with these args at time T, and both sides agree." The agent signs. The tool counter-signs. Anyone with their public keys can verify — no host, no service, no chain.

It is a TypeScript library, a small spec, and a conformance suite other implementations validate against.

┌────────┐                                  ┌───────┐
│ agent  │ ── call(args) ──────────────────►│ tool  │
│        │ ◄── response ─────────────────── │       │
└───┬────┘                                  └───┬───┘
    │                                           │
    │  signAgent(receipt, agentSk)              │
    │  ─────────────────────────────────────────►
    │            envelope (1 sig)               │
    │                                           │ countersignTool(env, toolSk)
    │  ◄─────────────────────────────────────────
    │            envelope (2 sigs)              │
    ▼
verify(envelope, resolver) ──► { ok: true, receipt } | { ok: false, error }

Quickstart

git clone <repo-url> agent-toolprint
cd agent-toolprint
bun install
bun run demo

Expected: verify: { ok: true, ... } followed by verify (after 1-byte tamper): { ok: false, ... }. Under five minutes from git clone to verified receipt. The same Quickstart runs in CI on every PR — see .github/workflows/ci.yml.

30-second example

import {
  signAgent,
  countersignTool,
  verify,
  didKeyFromEd25519Pubkey,
  didKeyResolver,
  sha256Hash,
  type Receipt,
} from "agent-toolprint";
import { ed25519 } from "@noble/curves/ed25519.js";
import { randomUUID } from "node:crypto";
import { base64 } from "@scure/base";

const agentSk = ed25519.utils.randomSecretKey();
const toolSk = ed25519.utils.randomSecretKey();
const args = { query: "bun docs" };
const response = { results: ["https://bun.sh/docs"] };

const receipt: Receipt = {
  v: "tp/0.1",
  id: randomUUID(),
  ts: new Date().toISOString(),
  agent: { did: didKeyFromEd25519Pubkey(ed25519.getPublicKey(agentSk)), key_id: "agent" },
  tool:  { did: didKeyFromEd25519Pubkey(ed25519.getPublicKey(toolSk)),  key_id: "tool"  },
  call:   { name: "search", args_hash: sha256Hash(args) },
  result: { status: "ok", response_hash: sha256Hash(response) },
  nonce:  base64.encode(crypto.getRandomValues(new Uint8Array(32))),
};

const envelope = countersignTool(signAgent(receipt, agentSk), toolSk);
const result = await verify(envelope, { resolver: didKeyResolver, plaintext: { args, response } });
// → { ok: true, receipt }

Why agent-toolprint?

  • Both sides on record. Agent and tool sign the same canonical bytes. Neither can later claim "that wasn't me" or "that wasn't them".
  • Verifies offline. A public key and the envelope are enough — no calls to a host, ledger, or central service.
  • Plays with what you have. DSSE wire format, Ed25519 signatures, JCS canonical JSON, did:key identities. No new crypto.

Compared to alternatives

| | Tool-call audit? | Both sides sign? | Offline verify? | Wire format | |---|---|---|---|---| | agent-toolprint | ✓ | ✓ | ✓ | DSSE + JCS | | MCP | invocation only — defers audit | — | — | JSON-RPC | | OTel GenAI | observability — trusted telemetry | — | — | spans | | SigStore / in-toto / SLSA | build provenance | one signer per step | ✓ | DSSE | | EAS off-chain | attestation | single signer | requires EVM | EIP-712 | | Biscuit / Macaroons | authorization, not audit | — | ✓ | bearer token |

agent-toolprint does not replace MCP — it captures what happened on top of any transport. It does not replace OTel — it adds non-repudiation OTel doesn't claim to provide.

Public API

The library exports four functions and a small surface of helpers:

| | Signature | What it does | |---|---|---| | signAgent | (receipt, sk) → Envelope | Validates receipt, JCS-canonicalizes, returns DSSE envelope with the agent signature | | countersignTool | (envelope, sk) → Envelope | Verifies the envelope is well-formed, appends the tool signature | | verify | (envelope, opts) → Promise<{ok, ...}> | Runs all five SPEC §4 checks | | chain | (parent, child) → boolean | Returns child.parent === parent.id | | didKeyResolver | Resolver | Bundled did:key resolver — pluggable | | didKeyFromEd25519Pubkey | (pk) → string | Encode an Ed25519 pubkey as a did:key | | parseDidKey | (did) → Uint8Array | Decode a did:key to its Ed25519 pubkey | | sha256Hash | (value) → "sha256:<hex>" | SHA-256 over JCS-canonical bytes | | canonical / canonicalBytes | (value) → string \| bytes | RFC 8785 JCS | | ReceiptSchema / EnvelopeSchema | Zod | Strict validators for both shapes | | PAYLOAD_TYPE, PROTOCOL_VERSION | constants | DSSE payloadType and the v field value |

verify options

type VerifyOptions = {
  resolver: Resolver;                 // pluggable DID resolver — required
  now?: Date;                         // default: new Date()
  maxClockSkewMs?: number;            // default: 24h
  skipTimestampCheck?: boolean;       // default: false
  plaintext?: { args?: unknown; response?: unknown };  // optional re-hash check
};

Receipt grammar (excerpt)

Full normative grammar in SPEC.md. The shape:

{
  "v":  "tp/0.1",
  "id": "<RFC 4122 UUID>",
  "ts": "<RFC 3339>",
  "agent":  { "did": "did:key:z…", "key_id": "…" },
  "tool":   { "did": "did:key:z…", "key_id": "…" },
  "call":   { "name": "search", "args_hash":     "sha256:…" },
  "result": { "status": "ok",   "response_hash": "sha256:…" },
  "nonce":  "<base64 32-byte random>",
  "parent": "<receipt id, optional>"
}

Wrapped in a DSSE envelope with exactly two Ed25519 signatures (agent first, tool second), over the same canonical payload bytes.

Conformance

bun run conformance
# 15/15 passed in ~0.2s

The vectors in conformance/vectors/ are the contract with other implementations. Cover all four SPEC §6 clauses:

  • (C1) byte-identical canonical encoding across implementations (3 vectors)
  • (C2) mutation of any field fails verify (7 vectors — payload byte-flip, agent/tool sig flip, swap-sigs, agent/tool keyid mismatch, non-canonical payload)
  • (C3) single-signed envelope rejected (2 vectors)
  • (C4) parent.id == child.parent enforced (3 vectors)

Each vector is JSON, language-agnostic, runs standalone. See conformance/README.md for the format and how to add new vectors.

Project layout

src/                                  # 8 files, each <200 LoC, 314 total
├── index.ts        public API re-exports
├── types.ts        Receipt + Envelope Zod schemas (strict)
├── canonical.ts    JCS + sha256
├── did-key.ts      bundled did:key resolver + Resolver type
├── envelope.ts     DSSE PAE encoding + envelope helpers
├── sign.ts         signAgent + countersignTool
├── verify.ts       5-check verifier
└── chain.ts        chain(parent, child)

examples/demo.ts                      the 20-line demo
conformance/                          12 JSON vectors + runner
tests/                                bun:test, 57 tests across 11 files
docs/superpowers/plans/               internal — implementation history

SPEC.md                               normative grammar
SCOPE.md                              v0.1 feature decisions
ROADMAP.md                            what's next, what triggers v0.2
CHANGELOG.md                          release notes
CONTRIBUTING.md                       setup + workflow + spec discipline

Security

  • Replay is caller-side. The library is stateless. Consumers track (id, nonce) themselves to detect replays. See tests/security-replay.test.ts.
  • Revocation is out-of-band. A receipt remains cryptographically valid even after a party repudiates it; consumers consult an external revocation list.
  • Plaintext is out-of-band. Receipts carry only sha256:<hex> of JCS-canonical args/responses. Pass plaintext to verify ({ plaintext: { args, response } }) to re-hash and compare.
  • Key rotation on did:key is a no-op. The verification key is derived from the DID itself.

Roadmap & versioning

Current: v0.1.0. See ROADMAP.md for what's deferred to v0.2 and the trigger conditions for promoting each item. v0.1 is did:key-only, JCS-only, hash-only payloads. No CBOR, no inline bytes, no extension points — by design (SCOPE.md explains why).

Contributing

See CONTRIBUTING.md. Short version: TDD only, Biome handles style, every code change is either a SPEC refinement or brings the impl in line with SPEC.

License

Apache 2.0 — see LICENSE.