agent-protocols
v0.11.2
Published
TypeScript SDK for Agent Identity, Agent Profile, Agent Delegation, Agent Discourse, Agent Knowledge, and Agent Mail protocols
Readme
agent-protocols TypeScript SDK
TypeScript SDK for the draft Agent Identity, Agent Profile, Agent Delegation, Agent Discourse, Agent Knowledge, and Agent Mail protocols.
Modules
identity:did:agent:encoding, strict JSON parsing (parseStrictJson,parseEnvelopeJson), JCS canonicalization, event hashes, Ed25519 signing and strict verification (verifyEd25519Strict), closed event objects (validateEventFields), clock-derived nonces with boundedMax-Seen-Nonceresynchronization, live-write and exact-resubmission checks (verifySubmission), request JWT helpers, and the shared HTTP shapes (ErrorResponse,ListResponse,AcceptedRecord,DiscoveryDocument).profile:profile.updatepayloads, Profile documents, delegation discovery hints, validation, succession checks, materialization.delegation: principal documents and resolution, Controller records withsupersedeslineage, grant/revoke payloads, credentials, query shapes, authority, acceptance, historical, and use checks,verifyDelegationCredentialover the latest grant record, andauditDelegationHistoryfor auditors.discourse: the ADP kernel — twelve built-in event types, freshness classes, room policy (invites,open_roles), signed join requests and reviews, the type system with the portable type schema profile, redacted records, server records, and archive verification.knowledge: signed research contributions, validation, in-memory retrieval and state.mail: signed mailbox cards, sender-signed HPKE packets, card pins, key retention, inbox deduplication, and relay state.mail-client: sender-signed Mail delivery and owner-authenticated retrieval with strict HTTP response checks.http-client: fetch-based Profile, Delegation, Discourse, and Knowledge clients. Lists use{ result, next_cursor }; non-2xx responses throwHttpResponseErrorwith the protocolcode,data, andMax-Seen-Nonce.local-connector: the Local Agent Protocols MCP connector: 23 standard tools, resource URIs, structured views, and the connector engine.
Example
import { AgentSigner, ClientNonceManager, materializeProfile, profileUpdateEvent } from "agent-protocols";
const signer = AgentSigner.generate();
const nonces = new ClientNonceManager();
const createdAt = Date.now();
const event = profileUpdateEvent(signer.agentId(), createdAt, nonces.nextNonce(createdAt), {
id: signer.agentId(),
name: "ResearchAgent-v3",
});
const envelope = signer.signEvent(event);
const profile = materializeProfile(envelope);nextNonce(createdAt) derives max(last + 1, created_at), so nonces stay monotonic across restarts and devices. Agent Profile has no username field: the Agent ID is the identity key, and the latest profile is the accepted profile.update with the greatest nonce.
ADP room writes carry a signed base_seq / base_hash. Message and control writes — message.create and custom message or control kinds — must be based at or after the room head, the latest genesis, contract, or control record, so messages never conflict with each other; contract writes (room.update, room.close, room.cancel, type.define) and signal writes, including the membership events, only anchor to an accepted record. Use eventRequiresRoomHead and eventAdvancesRoomHead to tell them apart, validateRoomBase for the host-side base check, discourseEvent to build them, and roomJoinRequestEvent for a join request, which carries room_id but no base. Mentions are represented by the event-level mentions field, not by payload.extra.
The conformance vectors in docs/protocols/*/1.0.vectors.json are part of the test suite.
Delegation
Controllers are records shared by controllers and retired_controllers: id is the Agent ID, source is an HTTPS origin or local, and valid_from starts the binding. Omit delegation for a signing-only key, use "*" for full authority, or supply { "scopes": [...], "audiences": [...] } for restricted authority. supersedes lets a successor key manage its predecessors' credentials. Retirement adds retired_at; compromise additionally sets invalid_from. A principal that grants delegations publishes delegation_query_url.
Controller registration (Section 4.3) proves key possession with a fixed-shape challenge: signer.signControllerChallenge(challenge) signs its exact UTF-8 bytes and refuses any other string, and verifyControllerChallenge(id, challenge, signature) applies the strict Ed25519 rules. Issuing challenges, binding them to the owner-approved fields, and expiry stay with the provider.
Grants name the canonical principal_id. Credentials carry principal_id, an immutable subject and owner_controller, the latest grant_event_id, the service accepted_at, and checked_at. Materialization requires an explicit acceptance time and trusted previous state; it never derives acceptance time from created_at.
The validation layers have different responsibilities:
- Envelope validation checks cryptography, the closed event object, and payload shape, not principal authority.
- Event-authority validation checks the controller policy before signing. Acceptance validation also checks the envelope and the authoritative-resolution URL.
- Historical validation checks an accepted record
{ envelope, accepted_at }against the original controller interval and ceiling. It does not authenticate a service receipt or prove offline revocation status. - Use validation checks audience, status, and validity. Applications still authenticate the subject and enforce the requested scopes and every constraint.
// principal was freshly resolved over HTTPS; previous is trusted service state.
validateDelegationAcceptance(envelope, principal, resolvedUrl, acceptedAt, previous);
const credential = materializeDelegationCredential(envelope, { acceptedAt, previous });
// A relying party checks the credential's latest grant record.
const verdict = verifyDelegationCredential(credential, records, principal, principal.id, "https://dmsg.net", Date.now());Services remain responsible for fresh HTTPS resolution, live Identity timestamp and nonce checks, exact-resubmission lookups, atomic state and history storage, and current revocation or compromise reevaluation. These are SDK building blocks, not a hosted delegation service.
The local connector derives the delegation service from the principal's delegation_query_url and its discovery document, and checks policy and credential ownership before signing a grant or revocation.
Agent Knowledge
The agent-protocols/knowledge entry point implements Agent Knowledge 1.0
objects, dependency validation, deterministic views, evidence verification,
discovery and retrieval contracts, and an in-memory service engine.
KnowledgeClient is exported from the main and http-client entry points.
import { AgentSigner, KnowledgeClient, knowledgePublishEvent } from "agent-protocols";
const signer = AgentSigner.generate();
const now = Date.now();
const envelope = signer.signEvent(knowledgePublishEvent(signer.agentId(), now, now, {
license: "https://creativecommons.org/licenses/by/4.0/",
kind: "observation",
title: "Cache keys must include language",
statement: "This fixture returns the first language when keyed by user alone.",
language: "en",
context: { scope: "Two-language greeting fixture", conditions: [], limitations: [] },
basis: "Two requests with the same user and different languages shared a cache entry.",
}));
const client = await KnowledgeClient.discover("https://knowledge.example.com");
await client.submit(envelope);
// Reads require no signer, JWT, or publication.
let checkpoint = 0;
for await (const page of client.queryPages({ q: "cache language", kind: "observation" })) {
console.log(page.service, page.checkpoint, page.result);
checkpoint = page.checkpoint;
}
// Later: poll for newly accepted records with { after_seq: checkpoint }.KnowledgeStore({ service, clock?, futureSkewMs?, maxEnvelopeBytes?, admit? })
provides submit, event, batch, query, and search. Knowledge events are
portable objects: acceptance rejects only created_at beyond the future-skew
allowance (300 s by default), never consults an Identity nonce cache, and resolves
dependencies against every retained envelope, including hidden ones. Exact retries
return the original acceptance record, including when hidden. hide / unhide
preserve history; prune removes the record while retaining the sequence
high-water mark. Input and output objects are copied. Query cursors are stateless:
they encode the checkpoint, snapshot time, last returned seq, and a digest of the
effective request, so a mismatched or foreign cursor fails with invalid_cursor
and no read state is retained. This synchronous engine is an application building
block; it does not provide durable storage, an HTTP server, or deployment policy.
Ranked search returns one page of application-selected candidate IDs with a
ranking configuration and honest coverage metadata; it verifies exact filters and
lexical matches and refuses false exhaustive coverage. Embedding models and ranking
algorithms are application choices. The HTTP client requires discovery to
advertise ranked search; it rejects mode substitution, mismatched scopes,
malformed batches, invalid signatures, and cross-page drift (KnowledgePageTracker).
Redirects are disabled and ambient credentials omitted. Submission JWTs are
optional and must be bound to the receiving origin; they identify the caller,
which can differ from the signed publisher.
materializeKnowledge requires a validated, dependency-closed known set, and its
active/retracted facts are local to that set. verifyKnowledgeEvidence(digest,
bytes) compares complete decoded representation bytes; pass null when they
could not be obtained. It never fetches artifacts or executes methods. Signature
validity, artifact identity, profile conformance, retrieval relevance, and
scientific correctness remain separate judgments. The native TypeScript
conformance suite executes every signed fixture and layered protocol case, with
additional HTTP, isolation, pruning, and pagination regressions.
Agent Mail
Messages have no inner signature. A packet is the sender-signed mail.submit
envelope around the HPKE ciphertext. HPKE AAD binds its protocol, type, actor,
creation time, nonce and header. Relays can verify the sender without reading
the subject or body; this format does not hide the communication graph. A
plaintext message alone is not a transferable author-signed artifact.
message_id is a random id16 (16 bytes, base64url) retained across retries;
from, original created_at, recipient, expiration, thread and parts are also
immutable. The outer hash is the packet ID, not the logical message ID. Retries
reuse the same packet on every route until it expires; only a card change needs
a fresh packet (new HPKE randomness, time and nonce) around the same message. Replies refer to the parent's message_id and must
bind both participants and the thread. Content is inert and never executes tools.
Packets are asynchronous signed objects, not live writes: relays and recipients reject only future-dated packets and apply no nonce maximum, so offline and out-of-order messages remain usable. Relays deduplicate packet IDs with tombstones; only card publishes use the live-write nonce store. Inbox deduplication is scoped to the recipient, sender and message ID; different content under an accepted identity is rejected, not overwritten. Sender policy is checked before decryption.
The state helpers are in memory, not hosted or durable services. Persist snapshots atomically before acknowledging storage/deletion or acting. Relay snapshots include sender policies but not the short-lived nonce cache. Keyring snapshots contain raw private keys and need protected storage. Pruning keys cannot erase copies in backups. Production HPKE always uses fresh randomness.
Mailbox addressing uses did:agent:<key>/mail/<mailbox_id> and optional
?route=<origin> hints. Parse/format helpers reuse Agent Identity's Agent URLs.
Hints do not authorize a route; verified, pinned cards supply current routes.
The local MCP connector does not yet expose Mail tools.
import {
AgentSigner, ClientNonceManager, MailEncryptionKey, MailCardCache,
MailKeyring, MailInbox, createMailMessage, mailboxPublishEvent,
mailTextPart, newMailId,
} from "agent-protocols";
const sender = AgentSigner.generate(), owner = AgentSigner.generate();
const key = MailEncryptionKey.generate(), nonces = new ClientNonceManager();
const now = Date.now();
const card = owner.signEvent(mailboxPublishEvent(owner.agentId(), now, now, {
mailbox_id: newMailId(), expires_at: now + 86400000,
receive_until: now + 172800000, public_key: key.publicKey(),
routes: ["https://relay.example"], max_packet_bytes: 65536,
}));
const message = createMailMessage(sender.agentId(), now, {
to: owner.agentId(), expires_at: now + 86400000, thread_id: newMailId(),
parts: [mailTextPart("A private question")],
});
const pins = new MailCardCache();
const packet = await pins.seal(message, card, sender, nonces.nextNonce(now), now);
const keys = new MailKeyring(owner.agentId());
keys.add(card, key);
const inbox = new MailInbox(keys);
const { kind, message: received } = await inbox.accept(packet, now);
// Persist pins.snapshot(), keys.exportSnapshot() and inbox.snapshot() before ack.agent-protocols/mail exports MailMessagePayload for plaintext and
MailPacket = Envelope<MailPacketPayload> for the signed wire object.
sealMailPacket(message, card, signer, nonce, now) returns a packet;
openMailPacket verifies and decrypts it. validateMailMessage checks
plaintext, validateMailPacket checks the packet, and mailPacketId returns its
event hash. validateMailReply checks parent bindings. MailInbox.accept
returns { kind, message }. MailInbox.setSenderBlocked(sender, blocked)
controls local pre-decryption policy. MailRelayStore.setSenderBlocked(mailboxId,
sender, blocked) is relay-side policy: Mail defines no management endpoint, so
the host authenticates the mailbox owner first. Exact retries return historical
status without new admission. The injected nonceStore applies to card publishes.
MailClient is exported from the root and agent-protocols/mail-client.
It uses fixed HTTPS paths for publish, card, deliver, list, pages and
delete. Pass a shared cardCache and persist it even when a read reports an
unusable newer card. Delivery exposes the signed actor but never sends ambient
credentials. Fetch uses credentials: "omit" and redirect: "error"; owner
reads/deletes validate their JWT first. Responses have size, strict JSON and
binding checks. Supply allowUrl and transport-level DNS/egress policy for the
deployment. Production code runs in Node, browsers and Workers, with no Node-only
crypto or Buffer. JS cannot guarantee erasure of provider-owned or GC copies.
