@treeship/a2a
Drop-in Treeship attestation for A2A (Agent2Agent) servers and clients.
A2A solves how agents communicate. It does not solve what proof exists that they did what they claimed. The @treeship/a2a package fills that gap. Every A2A task receipt, completion, and handoff becomes a signed Treeship artifact, and every outbound A2A artifact carries a receipt URL the receiving agent can verify.
A2A makes agents interoperable. Treeship makes that interoperability trustworthy and auditable.
Install
npm install @treeship/a2aThe package is framework-agnostic, it does not import any specific A2A SDK. You wire its hooks into whichever A2A server you already run.
What gets attested
| Phase | Artifact created |
|---|---|
| Task arrives | Intent, who sent it, which skill, A2A task and message IDs |
| Task completes | Receipt chained to the intent, elapsed time, status, artifact digest |
| Outbound artifact | treeship_artifact_id and treeship_receipt_url injected into metadata |
| Handoff to another A2A agent | Signed handoff, from-agent, to-agent, context |
AgentCard at /.well-known/agent.json | A treeship.dev/extensions/attestation/v1 extension publishing your ship ID |
Publish a Treeship-attested AgentCard
import { buildAgentCard } from '@treeship/a2a';
app.get('/.well-known/agent.json', (_req, res) => {
res.json(
buildAgentCard(
{
name: 'OpenClaw Research Agent',
version: '1.2.0',
url: 'https://openclaw.example/a2a',
capabilities: { streaming: true, pushNotifications: true },
skills: [
{ id: 'web-research', name: 'Web Research' },
],
},
{
ship_id: process.env.TREESHIP_SHIP_ID!,
verification_key: 'ed25519:abc123...',
},
),
);
});The output AgentCard contains the canonical Treeship extension:
{
"extensions": [
{
"uri": "treeship.dev/extensions/attestation/v1",
"required": false,
"params": {
"ship_id": "shp_4a9f2c1d",
"receipt_endpoint": "https://treeship.dev/receipt"
}
}
]
}Wrap your task handler
import { TreeshipA2AMiddleware } from '@treeship/a2a';
const treeship = new TreeshipA2AMiddleware({
shipId: process.env.TREESHIP_SHIP_ID!,
});
app.post('/a2a/tasks', async (req, res) => {
const { taskId, skill, from, messageId } = req.body;
await treeship.onTaskReceived({ taskId, skill, fromAgent: from, messageId });
const start = Date.now();
let status: 'completed' | 'failed' = 'completed';
let artifact;
try {
artifact = await runMyAgent(req.body);
} catch (e) {
status = 'failed';
throw e;
} finally {
const result = await treeship.onTaskCompleted({
taskId,
elapsedMs: Date.now() - start,
status,
artifactDigest: artifact ? TreeshipA2AMiddleware.digestArtifact(artifact) : undefined,
});
if (artifact) artifact = treeship.decorateArtifact(artifact, result);
}
res.json(artifact);
});The artifact your peer receives now carries a verifiable trail:
{
"artifactId": "research-output-001",
"parts": [{ "kind": "text", "text": "Research findings..." }],
"metadata": {
"treeship_artifact_id": "art_7f8e9d0a1b2c3d4e",
"treeship_receipt_url": "https://treeship.dev/receipt/ssn_01HR9W2D4Q4M7A0C",
"treeship_session_id": "ssn_01HR9W2D4Q4M7A0C",
"treeship_ship_id": "shp_4a9f2c1d"
}
}Only digests and metadata enter the artifact. Raw task content is never stored. The digest proves which data was involved without exposing the data itself.
Verify a peer before delegating
import { fetchAgentCard, hasTreeshipExtension, verifyArtifact } from '@treeship/a2a';
const card = await fetchAgentCard('https://partner-agent.example');
if (!hasTreeshipExtension(card)) {
throw new Error('Refusing to delegate: peer is not Treeship-attested');
}
// ... send your A2A task ... and when the artifact comes back:
const verification = await verifyArtifact(remoteArtifact.metadata);
if (!verification || !verification.withinDeclaredBounds) {
throw new Error('Peer artifact failed Treeship verification');
}Record a handoff
await treeship.onHandoff({
toAgent: 'agent://openclaw',
taskId: 'a2a-task-7f8e9d',
context: 'Research phase delegated: find comparable Merkle MMR implementations',
messageId: 'msg_abc123',
});This is the same artifact treeship attest handoff produces from the CLI, it appears in the parent session's receipt as a delegation boundary.
Environment variables
| Variable | Effect |
|---|---|
TREESHIP_DISABLE=1 | Skips all attestation. Hooks return undefined. |
TREESHIP_SESSION_ID | Inherited from treeship session start; auto-included in payloads. |
TREESHIP_DEBUG=1 | Logs attestation failures to stderr. |
Design rules
- Treeship errors never fail the underlying A2A handler.
- Intent attestation is awaited so the proof exists before the agent runs.
- The middleware has zero runtime dependencies and is framework-agnostic.
- Handoffs and AgentCard extensions are opt-in but on by default.