Treeship
Concepts

Capability Cards

A signed, verifiable agent capability card. A key attests what an agent is and what it can do, and verify-capability checks that claim against the agent's actual evidence. Covers agent_card.v1, key binding, per-actor signing, revocation, and browser verification.

A capability card is a signed receipt in which a key declares an agent's identity and its capability set, carried as the payload of an agent_card.v1 receipt. It answers a question no static descriptor format (A2A's AgentCard, NANDA) can: not just what does this agent claim it can do, but does its actual evidence match the claim, and is the claim cryptographically bound to a key you trust?

Two different things called a card

This page is about the typed, signed capability card (agent_card.v1), a portable proof object. It is distinct from the local Agent Card, which is a workspace trust object Treeship keeps on disk to track an agent's lifecycle. A capability card is something you mint, verify, and revoke; an Agent Card is local inventory.

The two questions

treeship verify-capability <card> answers two things:

  1. Is the card key-bound? A card is key-bound only when its keyid is the key that signed the envelope and that key is pinned under AgentCert. Otherwise the card is self-asserted, a claim anyone holding the signing key could have made. Binding strength is always reported; it is never silently assumed.
  2. Do the agent's captured actions stay within the declared capability set? Every action receipt signed by the card's key is checked: its action label or meta.tool must match a declared capability, either exactly (db.query) or as a family.* glob (file.* matches file.write). The result is an in-scope / out-of-scope count.

The honest contract

verify-capability proves consistency over captured evidence: that the actions Treeship recorded are in or out of scope. It does not prove the agent took no off-card action, that completeness guarantee is a runtime enforcement job, not something a signature can deliver. The output says so on every run.

Minting a card

treeship attest card \
  --agent agent://deployer \
  --tools file.*,db.query \
  --models claude-sonnet-4

This builds the agent_card.v1 payload from typed flags, validates it against the registered predicate, signs it, and reports whether the card is key-bound at mint time. The card's keyid defaults to the signing key.

Key binding and per-actor signing

A card is only as strong as the key behind it. By default every action in a workspace is signed by the single ship key, so a card's keyid is the shared key and the card is self-asserted. Per-actor signing makes the binding real:

# Give the agent its own key, certified by the ship and pinned under AgentCert.
treeship agent register --name deployer --tools file.write --own-key

--own-key mints a dedicated per-agent key, certifies it (the ship signs as issuer), pins it under AgentCert, and records it on the agent card. After that, treeship attest action --actor agent://deployer … and treeship attest card --agent agent://deployer … sign with the agent's own key. Now the card's keyid is the agent's pinned key, and verify-capability reports key-bound: yes.

The same binding makes a plain action's actor provable. treeship verify <action> reports:

actor:         agent://deployer
actor proof:   proven (key-bound)

proven means the receipt's signer is the actor's registered, AgentCert-pinned key. asserted means a free-text label signed by the shared key. This is a display label only, it never changes whether verify returns pass or fail.

Strictly additive

Per-actor signing is opt-in. An agent without a registered per-agent key signs with the ship key exactly as before, and existing receipts keep verifying. See Agent Identity for the certificate model the per-agent key plugs into.

Verifying a card

treeship verify-capability art_<agent_card_id>
✓ capability card
  card:              art_…
  agent:             agent://deployer
  key-bound:         yes (AgentCert)
  declared tools:    file.*, db.query
  in-scope actions:  2
  out-of-scope:      1
  status:            verified

The optional evidence_anchor on a card commits the agent to a receipt set (a count, a tip, a Merkle root) at mint time, so post-hoc omission or backfill is detectable, verify-capability flags a mismatch between the committed count and what it observes.

Capability provenance

A capability is only as trustworthy as where its claim came from. By default --tools file.* is just declared, an operator typed it, or a model emitted it. Capability provenance grades every declared capability by its source, so a registry of claims becomes a registry of captured truth:

  • captured — the tool is actually wired into the agent's harness, read from real config (mcp.json, a Claude Code settings.json permissions.allow list) at mint time.
  • exercised — the capability is backed by real captured action receipts (computed by the cross-check above).
  • discovered — read from the agent's own published descriptor (an A2A AgentCard's skills, via --from-a2a). A real provenance source, the agent declared it about itself, weaker than receipt-backed exercised but never a bare operator claim.
  • declared-only — asserted, never wired and never exercised.

Mint a card whose capabilities are captured from config, not typed by hand:

treeship attest card --agent agent://deployer \
  --from-harness ./.claude/settings.json

When the capability set is an operator's explicit declaration rather than something captured from a config, declare it from a JSON file with --tools-json. It is the runtime companion to --from-harness: each entry is stamped declared with the file as its source, an operator's claim, honestly labeled and traceable to where it came from, never presented as captured. (This is how programmatic callers such as the protocol bridges supply a capability set: a bridge that exposes Treeship's own meta-tools must not pass those off as the agent's capabilities, so it declares, it does not auto-capture.)

# capabilities.json: ["deploy.run", "db.query"]  (or { "tools": [...] })
treeship attest card --agent agent://deployer \
  --tools-json ./capabilities.json

For an A2A agent, map its published AgentCard directly. Its skills become capabilities stamped discovered with the AgentCard's url as their source, while protocol-level capabilities (streaming, push notifications) are excluded as transport, not domain capabilities. An A2A agent's self-description thus becomes a key-bound, verifiable Treeship card:

treeship attest card --agent agent://trader --from-a2a ./agentcard.json
#   provenance:  0 of 2 captured from harness, 2 discovered from A2A AgentCard

verify-capability and resolve then report the breakdown, for example provenance: 2 captured, 0 exercised, 1 declared-only. A declared deploy.prod that is wired nowhere and never used is visibly weaker than the tools backed by evidence.

Provenance grades, it does not block

captured proves a tool is wired, not that the agent is confined to it (confinement is runtime enforcement). exercised proves use, not the capability ceiling. Provenance reports the strength of a claim; it never rejects a card. See the capability-provenance spec.

Revoking a card

treeship revoke-capability art_<agent_card_id> --reason key-rotation

This mints a signed agent_card_revocation.v1 receipt. verify-capability honors a revocation only when its signer is authorized: the card's own key (self-revocation) or a Ship trust root (issuer revocation). An unauthorized revocation, signed by any other key, is ignored. When an authorized revocation is present the card's status becomes REVOKED and a "do not honor" warning is shown. A stranger cannot revoke your card.

Verifying in the browser

The same check runs client-side. @treeship/verify-js exposes verifyCapability(card, actions, trustRoots), backed by a WASM export, that returns the same verdict the CLI does, key-bound status, declared tools, and the in/out-of-scope cross-check. The matching logic lives in one shared Rust module (treeship_core::capability) used by both the CLI and the WASM verifier, so a browser receipt viewer and the command line cannot disagree.

import { verifyCapability } from '@treeship/verify-js';

const result = await verifyCapability(cardEnvelope, actionEnvelopes, trustRoots);
// { key_bound: true, in_scope: 2, out_of_scope: 1, status: 'verified', ... }

See also

  • Predicate registry — the agent_card.v1 and agent_card_revocation.v1 schemas
  • Agent Identity — the certificate that binds an agent to its key
  • Agent Cards — the local workspace trust object (a different thing)
  • Trust fabric — how identity, capability, and receipts compose