How it works
Signing, chaining, actors, verification, Hub, and Merkle proofs explained.
This page explains the mechanics behind Treeship. If you have not run the quickstart yet, start there. Come back here when you want to understand what is happening under the hood.
Signing
Every artifact Treeship produces is an Ed25519 signature over a structured payload.
When you run treeship init, a new Ed25519 keypair — the ship key — is generated and encrypted at rest on your machine. Private keys never leave your device. The ship key is the default signer, and agents you register with their own key (treeship onboard, or treeship agent register --own-key) sign with per-agent keys, each bound to its agent:// URI by a ship-signed certificate. Anyone holding the matching public key (or a certificate chain to a key they pinned) can verify any artifact you have ever created.
The signature format follows DSSE (Dead Simple Signing Envelope):
DSSE envelope
├── payloadType: application/vnd.treeship.action.v1+json
├── payload (base64-encoded JSON):
│ {
│ type: "treeship/action/v1",
│ timestamp: "2026-03-26T21:00:00Z",
│ actor: "agent://researcher",
│ action: "document.analyze",
│ parentId: "art_previousstep",
│ approvalNonce: "nce_7f8e9d0a",
│ meta: { ... }
│ }
└── signatures:
[{ keyid: "key_9f8e7d6c", sig: "base64url(ed25519_sig)" }]The signature is computed over PAE(payloadType, payload) (Pre-Authentication Encoding), which prevents type confusion attacks where a valid signature for one payload type could be reused for another.
Chaining
Artifacts form an ordered chain through parentId references.
When you create a new artifact, Treeship automatically sets its parentId to the ID of the most recent artifact in your local store. The artifact ID itself is derived from sha256(PAE_bytes)[..16], which means the ID is a content hash. If anyone tampers with the payload, the ID no longer matches and verification fails.
This gives you two properties:
- Ordering. You can walk the chain from any artifact back to the first one.
- Tamper evidence. Modifying, inserting, or removing any artifact breaks the chain for every artifact that follows it.
Actors
An actor is any entity that performs work inside your Treeship. Actors are identified by URI:
| Prefix | Meaning | Example |
|---|---|---|
human:// | A human identity | human://alice |
agent:// | An AI agent or automation | agent://deployer |
system:// | An external system (used in receipts) | system://stripe-webhook |
URIs are freeform after the prefix. Use whatever naming convention fits your organization.
An actor URI comes in two trust grades, and the verifier always tells you which one you got:
asserted— the artifact was signed by the shared ship key, and the actor URI is a label inside the signed payload. The signature proves which trust domain produced the artifact, not which actor within it. This is the default for unregistered actors, and it keeps the key surface small when you don't need more.proven (key-bound)— the actor is a registered agent with its own Ed25519 key, and the artifact is signed by that key. A ship-signedagent_cert.v1certificate binds the key to theagent://URI, so "who did this" is cryptographically established, not just claimed.
treeship onboard <agent> does the registration, certificate, and capability card in one command; the MCP and A2A bridges provision per-agent keys automatically on startup. A single Treeship often has multiple actors — a developer, a CI bot, several AI agents — contributing to the same chain of work, each verifiable at its own grade.
Actors appear in different roles depending on the attestation type:
| Attestation | Flag | Role |
|---|---|---|
| Action | --actor | Who performed the action |
| Approval | --approver | Who authorized the action |
| Handoff | --from / --to | Sender and receiver of work |
| Receipt | --system | External system producing the receipt |
Being referenced as an actor does not grant permission to act. To take a sensitive action, an agent still needs an approval from an authorized approver.
Verification
Verification is fully offline and deterministic. No network call, no server, no account.
treeship verify art_f7e6d5c4The verifier performs the following checks:
Recompute the artifact ID
Derive the expected ID from sha256(PAE_bytes)[..16] and compare it to the stored ID. If they do not match, the payload has been tampered with.
Check the Ed25519 signature
Verify the signature in the DSSE envelope against the signer's public key — the ship key, or the agent's own key for registered agents. A bad signature means the artifact was not produced by the claimed signer. The verdict also grades the actor: actor proof: proven (key-bound) when a certified per-agent key signed it, asserted when the actor URI is a label under the ship key.
Validate the statement schema
Confirm the payload conforms to the expected Treeship statement schema for its declared type.
Walk the parent chain
Follow parentId links recursively, verifying each ancestor artifact the same way. A broken link at any point fails the entire chain.
Check approval binding, scope, and replay (if present)
If the artifact references an approvalNonce, confirm a matching signed approval exists; if the approval has a scope, confirm the action's actor / action / subject fall inside the signed allow-lists; observe whether the same nonce was consumed earlier inside this verified package. Verify reports each property as a separate line. Beyond the package, the Approval Use Journal (shipped) enforces replay at consume time: treeship attest action reserves a grant use before signing.
Return the result
Exit 0 means the full chain is intact. Exit 1 means something failed, with reasons printed.
The verifier is open source and Apache-2.0 licensed. You can audit it, embed it in CI, or run it in a browser. Trust the math, not the infrastructure.
Hub
Hub is the optional sharing layer. It adds discoverability and public verify URLs, but it never adds trust. The signatures are the trust.
Hub connections
A hub connection is a named link between your local Treeship and a workspace on Hub. Hub connections work like tmux sessions: you can attach, detach, and kill them independently. Your Treeship stays the same regardless of how many hub connections exist.
| tmux | Treeship | What it does |
|---|---|---|
tmux new -s work | treeship hub attach --name work | Create a new named connection |
tmux attach -t work | treeship hub use work | Switch to an existing connection |
tmux detach | treeship hub detach | Disconnect without destroying (keys preserved) |
tmux kill-session -t work | treeship hub kill work | Remove the connection permanently |
tmux ls | treeship hub ls | List all connections |
Hub connections are about audiences, not environments. The Treeship key stays the same across all hub connections. Artifacts never change. Hub connections only control where artifacts appear on Hub.
Treeship (one keypair, one artifact store)
|-- hub: default -> hub.treeship.dev/alice/personal
|-- hub: acme -> hub.treeship.dev/acme-corp/projects
+-- hub: client-x -> hub.treeship.dev/clientx/deliverablesMost developers need exactly one hub connection.
When you attach to Hub, your Treeship's public key is registered. If the same public key appears in multiple hub connections, Hub knows they belong to the same Treeship. This is how Hub links workspaces to identities without requiring accounts or emails.
Attach and detach
Hub uses a device-authorization flow. When you run treeship hub attach, the CLI prints a URL and a short code. You visit the URL in a browser, enter the code, and approve the connection. From that point on, the CLI can push artifacts to your Hub workspace.
Detach disconnects the active hub connection but preserves all keys locally. You can re-attach later without re-authenticating.
Kill removes the hub connection from local config permanently. Artifacts already pushed to Hub remain there: they are content-addressed and independently verifiable.
DPoP authentication
Hub connections use DPoP (Demonstration of Proof-of-Possession) tokens. Each API request includes a proof that the caller holds the private key associated with the connection. This prevents token theft: a stolen access token is useless without the corresponding private key.
Hub connection commands
| Command | Description |
|---|---|
treeship hub attach | Connect to Hub (creates new or reconnects) |
treeship hub attach --name <name> | Create or reconnect a named hub connection |
treeship hub detach | Disconnect active hub connection (keeps keys) |
treeship hub ls | List all known hub connections |
treeship hub status | Show active hub connection details |
treeship hub use <name> | Switch active hub connection |
treeship hub push <id> | Push artifact to active hub connection |
treeship hub push <id> --hub <name> | Push to a specific hub connection |
treeship hub push <id> --all | Push to all hub connections |
treeship hub pull <id> | Pull artifact from Hub |
treeship hub open | Open workspace in browser |
treeship hub kill <name> | Remove a hub connection |
Hub connection config
Hub connections are stored in ~/.treeship/config.json:
{
"hub_connections": {
"default": {
"hub_id": "hub_661e5463912d",
"key_id": "key_9f8e7d6c",
"endpoint": "api.treeship.dev"
},
"work": {
"hub_id": "hub_a2b3c4d5e6f7",
"key_id": "key_9f8e7d6c",
"endpoint": "api.treeship.dev"
}
},
"active_hub": "default"
}Each hub connection has its own DPoP keypair for authenticating with Hub. Adding or removing a hub connection never changes your Treeship key.
Configs from earlier versions (flat hub object or docks format) are automatically migrated to the hub_connections format on first run. No manual action required.
Workspace URLs
When you push an artifact to Hub, it gets a public URL:
https://treeship.dev/verify/art_f7e6d5c4Anyone who opens that URL can verify the full artifact chain in their browser. No account, no install, no CLI required. The browser-based verifier runs the same checks as the CLI verifier described above.
Merkle proofs
Checkpoints are created by the publisher, not by Hub — the Hub holds no Merkle tree and creates nothing (that is the load-bearing invariant of the whole design). You run treeship checkpoint to sign a Merkle root over your artifacts, and treeship merkle publish to push it with per-artifact inclusion proofs; the Hub stores and serves them verbatim. These checkpoints enable two additional properties:
Inclusion proofs
Given an artifact and a checkpoint, anyone can verify that the artifact was included in the checkpoint's Merkle tree. This proves the artifact existed at the time the checkpoint was created, even if Hub later tries to deny it.
Anti-backdating
Because checkpoints are published at known intervals, you can prove that an artifact existed before a certain time. If your artifact appears in checkpoint N, and checkpoint N was published at time T, then the artifact must have been pushed before T. This prevents anyone (including Hub) from inserting artifacts after the fact and claiming they were always there.
How it works in practice
Day to day you rarely touch proofs directly: treeship onboard --publish runs the checkpoint-and-publish steps for you, and the browser verifier checks inclusion proofs whenever they are available. The properties hold whether or not you think about them — but the checkpoints exist because a publisher signed them, never because the Hub vouched.
If you want to verify a proof manually:
treeship audit --hub https://api.treeship.dev agent://f7e6d5c4✓ inclusion verified
checkpoint: cp_20260326_2100
root: sha256:a1b2c3...
proof: 3 intermediate hashesThis confirms the artifact was included in the specified checkpoint and that the Merkle path is valid from the artifact's leaf node to the checkpoint root.