ReferenceDARTC
Envelope
The signed JSON frame every DARTC peer exchanges.
Shape
interface DartcEnvelope<TPayload = unknown, TA2A = unknown> {
version: "0.2";
msg_id: string; // UUIDv7 preferred (UUIDv4 accepted during early development)
from: string;
to: string;
topic: string;
timestamp: number; // Unix epoch ms
signature: string; // base64(Ed25519(canonicalJson(envelope without signature)))
a2a?: TA2A; // A2A semantic payload (Agent Card, Message, Task, …)
dartc?: DartcMetadata; // transport metadata (stream / chunk_id / requires_ack / …)
payload?: TPayload; // app-specific data
}
interface DartcMetadata {
stream?: boolean;
chunk_id?: number;
is_final?: boolean;
priority?: "low" | "normal" | "high";
requires_ack?: boolean;
ack_for?: string;
}Canonicalisation + signature
1. Remove `signature`.
2. Canonicalize JSON by sorting object keys recursively.
3. UTF-8 encode the canonical JSON.
4. Sign or verify those bytes with Ed25519.The signing bytes are exactly what @gemmapod/dartc#signingBytes(envelope)
returns. The signature itself is base64-encoded raw Ed25519 (64 bytes
decoded).
Identity rules
from/toare agent or session identifiers. Format is opaque to DARTC — typicallypod:<pod-id>:<role>orvisitor:<session-pubkey>.to: "*"means broadcast within the current connection.topiccontrols routing and policy. Reserved prefixes:dartc.*— session control (hello,ack,error,ping,close)a2a.*— A2A-shaped semantic interopgemmapod.*— chat + UI events for this SDK
- Application-specific topics can use any non-reserved prefix
(
orders,negotiate,support, etc.).
Helpers
@gemmapod/dartc ships pure helpers for every step. Builders accept
the fields and assemble an UnsignedDartcEnvelope; sign/verify go
through a DartcSigner / DartcVerifier you supply.
import {
createEnvelope,
signEnvelope,
verifyEnvelope,
parseEnvelope,
canonicalJson,
signingBytes,
} from "@gemmapod/dartc";
const envelope = await signEnvelope(
createEnvelope({
from: "visitor:session-pubkey",
to: "pod:hello-pod:origin",
topic: "gemmapod.chat.request",
payload: { request_id: "req_1", messages: [{ role: "user", content: "Hello" }] },
}),
async (bytes) => signWithSessionKey(bytes), // your signer
);
const ok = await verifyEnvelope(envelope, async (bytes, sig) =>
await verifyWithSenderPubkey(bytes, sig),
);Size + chunking
- Keep individual frames below 64 KiB.
- Use
dartc.chunk_idfor ordered stream chunks. - Set
dartc.is_finalonly for DARTC stream completion metadata. A2A task completion still follows A2A task state semantics. - Use
dartc.requires_ackfor high-value control messages — not for every text delta.
Replay defence
The recipient SHOULD drop frames where:
timestampis outside an acceptable clock skew windowmsg_idhas been seen on this session
The reference Host uses a sliding window of recent msg_ids
per session.