Treeship
Integrations

@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/a2a

The 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

PhaseArtifact created
Task arrivesIntent, who sent it, which skill, A2A task and message IDs
Task completesReceipt chained to the intent, elapsed time, status, artifact digest
Outbound artifacttreeship_artifact_id and treeship_receipt_url injected into metadata
Handoff to another A2A agentSigned handoff, from-agent, to-agent, context
AgentCard at /.well-known/agent.jsonA 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

VariableEffect
TREESHIP_DISABLE=1Skips all attestation. Hooks return undefined.
TREESHIP_SESSION_IDInherited from treeship session start; auto-included in payloads.
TREESHIP_DEBUG=1Logs 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.