Treeship
CLI reference

session

Manage attestation sessions. Start, close, report, and share Session Receipts.

A session groups related work into a single unit. When you close a session, Treeship composes a Session Receipt v1 and writes a portable .treeship package that can be inspected and verified offline, or uploaded to a hub for sharing at a public URL.

See the Session Receipts concept page for the full format and determinism rules.

treeship session start

Start a new session.

treeship session start
treeship session start --name "fix auth bug"
treeship session start --actor agent://coder
treeship session start --workflow-ref art_0123456789abcdef0123456789abcdef
treeship session start --allow-dangerous-root

Options

OptionDescription
--name <text>Human-readable session name
--actor <uri>Default actor URI for attestations in this session
--workflow-ref <artifact-id>Validate a local signed workflow.v1 declaration and bind it into the signed session-start root action
--allow-dangerous-rootPermit a session whose git root is your home directory

Starting a session writes .treeship/session.json, initializes an append-only event log at .treeship/sessions/<session_id>/events.jsonl, and emits a session.started event. All subsequent attestations and wrap calls are linked to the session.

With --workflow-ref, Treeship verifies that the local artifact is a valid workflow.v1 receipt signed by a key in the current keystore before writing any session state. The artifact ID is then included inside the signed session.start action and preserved in the final signed session record. At close, Treeship re-verifies the root action and refuses to sign if the mutable manifest claims a different workflow. This binds the run to a declaration; separate checkpoint proofs establish whether the declaration pre-existed the run.

Treeship refuses to start a session when the git toplevel is your home directory. Start from a project checkout instead. Use --allow-dangerous-root only when you intentionally want a home-scoped session.

treeship session status

Show the current session state.

treeship session status

Outputs session id, name, actor, started-at timestamp, artifact count verified against the chain, and the number of events in the session's event log.

If no session is active, exits cleanly with a hint to run session start.

treeship session invite

Mint a single-use, expiring invitation that lets a second agent join an existing session. This is the host side of the multi-agent session lifecycle.

treeship session invite ssn_abc --invitee-cert "ed25519:ISSUER:org-x" --expires 1h
treeship session invite ssn_abc --invitee-pubkey deadbeefdeadbeef --capabilities tool.call
treeship session invite ssn_abc --open --expires 30m

Arguments

ArgumentDescription
[session_id]Session to mint the invitation for. Defaults to the active session.

Options

OptionDescription
--invitee-cert <issuer:subjects>Cert-restricted invitation. Format is <issuer_pubkey>:<subject1,subject2,...>.
--invitee-pubkey <fp-or-pubkey>Pubkey-restricted invitation. A 16-hex fingerprint or a full canonical pubkey.
--openOpen invitation that anyone holding the blob can redeem. Opt-in only.
--capabilities <types>Comma-separated action types to grant. Defaults to tool.call.
--expires <duration>Lifetime such as 30s, 5m, 1h, 7d. Defaults to 1 hour. The protocol max is 7 days.
--format <text|json>Output format. JSON returns { invitation_id, bootstrap_blob, session_ref, expires_at }.
--no-armorEmit raw JSON instead of the armored blob.

Exactly one of --invitee-cert, --invitee-pubkey, or --open is required. The command signs the invitation with the keystore's default key (the session host), persists it under the artifact store, and prints an armored -----BEGIN TREESHIP INVITATION----- bootstrap blob on stdout.

A joining agent only honors the invitation if the host's issuer key is pinned locally as a session_host trust root. Confirm the key with the host out of band first, then pin it with treeship trust add <key_id> ed25519:<pubkey> --kind session_host. See Multi-Agent Sessions.

treeship session join

Redeem an invitation and emit a pending participant event. This is the joiner side of the lifecycle.

treeship session join --invite-file ./invite.blob --actor agent://researcher
cat ./invite.blob | treeship session join --invite - --actor agent://qa
treeship session join --invite-file ./invite.blob --actor agent://researcher --out ./pending.json

Options

OptionDescription
--invite <blob>Paste the bootstrap blob inline. Use - to read from stdin.
--invite-file <path>Read the bootstrap blob from a file.
--actor <uri>Joining agent's actor URI, such as agent://researcher (required).
--out <path>Also write the pending participant envelope to this file, to hand to a host on another machine (see Joining from another machine below).
--format <text|json>Output format.

The command pins the issuer against the local SessionHost trust roots, verifies the invitation signature, checks expiry and restriction, records the nonce in the Approval Use Journal with max_uses=1, and emits a single-signature participant envelope that is pending the host countersign. It prints the participant artifact id to pass to session countersign. A second join against the same invitation fails through the journal's single-use path.

Without --out, the pending envelope only exists in the local artifact store, which only works when the host countersigns on the same machine. With --out, the same envelope is also written to a file you can copy to the host's machine, which the host then countersigns with --pending (see below). Whichever machine you're on, session join always prints a same-machine countersign <participant_id> hint -- ignore it and use --pending <file> on the host side when the join happened elsewhere.

treeship session mint-challenge

Mint a 128-bit challenge nonce for a room-join liveness check. Run by the host, before countersigning.

treeship session mint-challenge
treeship session mint-challenge --format json

Prints a nonce and the RFC 3339 instant it was minted (issued_at). Both answer-challenge and countersign refuse a nonce shorter than 32 characters or drawn from too small an alphabet -- a guessable nonce could be pre-signed, which would make the liveness proof show only that a document exists, not that the joining agent is live right now.

✓ challenge nonce minted
  nonce:      098cd1bfa62728a03c4ce1f53c59795b
  issued_at:  2026-09-26T17:42:54Z

treeship session answer-challenge

Sign the host's room-join liveness nonce with the joining agent's own key. Run by the joining agent after session join, in response to a nonce the host sends out of band (chat, a copied string, whatever channel you're already using).

treeship session answer-challenge art_part_abc123 --challenge 098cd1bfa62728a03c4ce1f53c59795b --actor agent://researcher

Arguments

ArgumentDescription
participant_idParticipant artifact id produced by session join.

Options

OptionDescription
--challenge <nonce>The nonce the host issued out of band for this join (required).
--actor <uri>Joining agent's actor URI. Must resolve to the same key that ran session join (required).
--out <path>Output path for the signed response. Defaults to <participant_id>.challenge-response.json.

The response file ({nonce, signature, signed_at}) is what you hand back to the host for session countersign --challenge-response.

treeship session countersign

Host countersigns a pending participant event. After this runs, the participant envelope carries two signatures (joining agent plus host) and verifies as a finalized join.

# Same machine
treeship session countersign art_part_abc123

# Same machine, with a liveness challenge
treeship session countersign art_part_abc123 \
  --challenge 098cd1bfa62728a03c4ce1f53c59795b \
  --challenge-issued-at 2026-09-26T17:42:54Z \
  --challenge-response ./art_part_abc123.challenge-response.json

# Across machines: the join happened elsewhere and its pending envelope
# arrived as a file. The challenge is mandatory here, not optional.
treeship session countersign --pending ./pending.json \
  --challenge 098cd1bfa62728a03c4ce1f53c59795b \
  --challenge-issued-at 2026-09-26T17:42:54Z \
  --challenge-response ./response.json

Arguments

ArgumentDescription
[participant_id]Participant artifact id, for a join on this machine. Omit it and pass --pending for a join on another machine.

Options

OptionDescription
--pending <path>A pending participant envelope a joiner on another machine sent (the file session join --out wrote). Requires --challenge and --challenge-response.
--challenge <nonce>The nonce you (the host) issued for the liveness check, from session mint-challenge. Must be supplied together with --challenge-response.
--challenge-response <path>Path to the joining agent's signed response to --challenge, from session answer-challenge. - reads stdin.
--challenge-issued-at <rfc3339>The issued_at instant session mint-challenge printed. With it, countersign writes a signed session-liveness/v1 artifact recording the challenge-to-answer window, so a third party can later see how long that window was. Required whenever --pending is used; optional (but recommended) on the same machine.

The command confirms the running default key matches the invitation's issuer, re-validates the participant's session, capabilities, and restriction against the trust-pinned invitation (never the bytes in the pending envelope alone), attaches the host countersign over the same canonical bytes, self-verifies, and overwrites the storage record with the finalized two-signature envelope. A participant envelope is invalid until both signatures are present.

The liveness challenge is mandatory across machines, optional on the same machine -- for now. Passing --pending without --challenge/--challenge-response is refused outright: across machines, the joiner's signed answer to a challenge you just minted is the only evidence it holds its key right now. On the same machine, omitting the challenge still countersigns, but prints a warning that the join was not liveness-checked. The challenge becomes mandatory for every countersign, same machine or not, in 0.32.

A minted challenge is only valid for a window from --challenge-issued-at: 10 minutes (600 seconds) by default, or TREESHIP_JOIN_CHALLENGE_WINDOW_SECS if set. Countersign also enforces that the challenge wasn't issued in the future and the joiner's signed response falls inside that window (allowing 60 seconds of clock skew either way); outside the window, it refuses with a message telling you to mint a fresh challenge.

Countersign is single-use, enforced by the host: it reserves the invitation's one use in the local Approval Use Journal before it signs, and never releases it even if a later step fails. A second countersign attempt against the same invitation -- with or without --pending -- is refused:

✗ invitation art_74d95cbc1defb54e0c7a921f14ab1623 has already been countersigned (max_uses 1), countersign refused: ...

Countersign also refuses to finalize a join into a session that isn't open on this host right now -- closed after the invitation was minted, or never started here:

✗ the invitation's session ssn_4bfda2e970618c3b is not open on this ship (closed, or never started here); countersign refused

treeship session close

Close the active session, compose the Session Receipt, and write a .treeship package.

treeship session close
treeship session close --summary "fixed JWT expiry bug"
treeship session close --summary "fixed JWT expiry bug" --receipt-dir receipts

Options

OptionDescription
--summary <text>Plain-text summary of what the session accomplished. Like every other field of the receipt, a path under your home directory in it is rewritten as ~/... before signing, and close says so when it happens
--headline <text>One-line headline for the receipt
--review <text>What should be reviewed before trusting the output
--receipt-dir <dir>Copy the sealed .treeship package into <dir>/<session_id>.treeship (created if missing). .treeship/ is usually gitignored; this puts the package somewhere it can be committed next to the change it accounts for. See Receipts in pull requests.
--format <text|json>Output format. JSON adds receipt_digest, commit_trailer and receipt_copy.

On close, Treeship:

  1. Appends a session.closed event to the event log.
  2. Walks the artifact chain from .last back to the session's root artifact.
  3. Composes a canonical Session Receipt v1 from the events, the artifact chain, and the agent graph.
  4. Builds a .treeship package under .treeship/sessions/<session_id>.treeship/ with receipt.json, merkle.json, render.json, per-artifact inclusion proofs, and a static preview.html.
  5. Signs a session.close chain artifact as before.
  6. Mints a coverage.v1 receipt chained onto it: what the attached harnesses declare they could capture and what the event log shows they did. It is sealed in the package with everything else and reported by package verify as the coverage row.
  7. Prints the package path, the canonical receipt digest, and the Merkle root.

Close is guarded by a lock so only one close or abandon operation can mutate session state at a time. Git reconciliation is also bounded: if the untracked-file scan exceeds the cap, Treeship skips per-file untracked events, records the truncation in the receipt proofs section, and treeship package verify reports a reconcile_completeness warning.

Example output:

✓ session receipt composed
  package:   .treeship/sessions/ssn_42e740bd9eb238f6.treeship
  digest:    sha256:942e6b2bfcbe860223fbae57ee7d9b73400a278ccfb0f1d5ca865aec9ce9aba6
  merkle:    mroot_effc020c67548f53
  files:     8

✓ session closed
  id:       ssn_42e740bd9eb238f6
  duration: 6m
  receipts: 3
  events:   2
  coverage: declared high (claude-code)  art_9c1d2e3f4a5b6c7d
  trailer:  Treeship-Receipt: ssn_42e740bd9eb238f6 sha256:942e6b2bfcbe860223fbae57ee7d9b73400a278ccfb0f1d5ca865aec9ce9aba6

The trailer: line is a git commit trailer. Paste it as the last line of the commit that carries the change the session produced, and a verifier can pair the commit with the sealed package: the digest is sha256(receipt.json), the same value package verify recomputes. With --receipt-dir, close also prints copied: with the path of the committed copy.

After close, run treeship package verify against the package path to confirm the receipt is locally verifiable.

treeship session abandon

Quarantine a wedged active or closing session without deleting evidence.

treeship session abandon

The command moves .treeship/session.json or .treeship/session.closing plus the session event directory into .treeship/quarantine-*. Use it when a close was interrupted or a session is poisoned and you need the next session start to begin cleanly. The quarantined files remain available for debugging.

treeship session report

Upload a closed session's receipt to the configured hub and print the public URL.

treeship session report
treeship session report ssn_42e740bd9eb238f6

Options

ArgumentDescription
[session_id]Session ID to upload. Defaults to the most recently closed session's package under .treeship/sessions/.

The command:

  1. Locates the .treeship package for the requested session (or the most recently modified one).
  2. Reads receipt.json verbatim so the digest signed by session close is preserved bit-for-bit.
  3. DPoP-signs a PUT {hub_endpoint}/v1/receipt/{session_id} request using the active hub connection's private key.
  4. Prints the receipt URL.

If a package directory exists but receipt.json was never written, session report reports the package as incomplete instead of treating it as a missing closed session.

Example output:

✓ session receipt uploaded
  hub:      local
  session:  ssn_42e740bd9eb238f6
  agents:   1
  events:   2

  receipt:  https://treeship.dev/receipt/ssn_42e740bd9eb238f6

  → share these URLs freely -- they need no auth and stay up until you unpublish them

The receipt URL is public and needs no token, expiry, or auth to fetch. A2A clients, dashboards, and offline verifiers can all curl it programmatically. It stays up until the publishing dock takes it down with treeship hub unpublish, which is permanent: the receipt body is removed for good and the id cannot be re-published. See GET /v1/receipt/{session_id} and DELETE /v1/receipt/{session_id} for the API reference.

Exit code

0 when the package's own local verify passed or warned and, if an upload was attempted, it succeeded. Nonzero when the local verify failed (verification_status: fail), on every path including --no-upload, and nonzero on an upload error; the JSON document is emitted in those cases, so a script can branch on the exit code or on verification_status and error. No hub attached without --no-upload is an error on stderr with nothing on stdout: attach a hub, or pass --no-upload for the local verdict.

Which workspace a session uses

session start writes session.json into the nearest .treeship/ above the current directory (the walk stops at your home directory). From then on every session command, and every command that signs into the session (attest, approve, halt, judge, the Claude Code plugin's hooks), records into the workspace whose config.json sits beside that session.json. Only a workspace inside your home directory qualifies, and the .treeship/ and the config.json beside the session must be real directories and files, not links. An explicit --config still wins. TREESHIP_CONFIG in the environment applies only when no active session is found this way; before 0.31.12 an exported TREESHIP_CONFIG sent the plugin's approvals and attestations to another store while the session's own event log kept the events. A project's config found this way gets the same checks as treeship discovery (no stores outside its .treeship, no foreign extends, no links).

What gets sealed in a receipt

A Session Receipt v1 includes:

  • Session metadata: id, name, mode, started-at, ended-at, duration, status
  • Participants summary: total agents, spawned sub-agents, handoffs, max delegation depth, hosts, tool runtimes
  • Agent graph: every agent instance with its role, host, depth, and edges (parent-child spawn, handoff, collaboration, return)
  • Timeline: all events in deterministic order (timestamp, sequence_no, event_id)
  • Side effects: files read, files written, ports opened, network connections, processes, tool invocations
  • Artifacts: chain entries with content-addressed ids
  • Merkle root + inclusion proofs over the artifact ids
  • Render hints for Explorer and the preview HTML

Events the session can capture include tool calls, file reads (via the daemon's sensitive-file watcher), file writes, network connections, process execution, agent spawning, handoffs, collaborations, and returns.

A session is auto-sealed: the chain of signed artifacts inside the session is what the receipt commits to via the Merkle root. The receipt itself is a human-readable summary on top of that cryptographic backbone.