Treeship
SDK

treeship-sdk (Python)

Python SDK for Treeship. Wraps the CLI binary for signing, verification, session reporting, and hub operations.

Install

pip install treeship-sdk

The Python SDK shells out to the treeship CLI binary. The CLI must be installed and initialized (treeship init) before using the SDK.

Verified against CLI 0.31.9. attest_approval() sends a scope (allowed_actions/allowed_actors/allowed_subjects/max_uses, or unscoped=True) and wrap() uses --format json to parse the artifact id — both work end to end today. See each method's section below for the current signature.

Constructing a client

Treeship() takes keyword arguments only:

ParameterTypeDescription
cli_pathOptional[str | Path]Explicit path to the treeship binary. Highest priority.
bot_modeboolBootstrap the CLI if it is missing — resolves $TREESHIP_BIN, then a cache, then a GitHub Release or npm install.
timeoutOptional[int]Default subprocess timeout in seconds.
cwdOptional[str | Path]Working directory for CLI calls.
envOptional[Mapping[str, str]]Environment for CLI calls.

Resolution order for the binary is cli_path > bot_mode bootstrap > plain treeship on PATH. Note that $TREESHIP_BIN is consulted only on the bootstrap path: a default Treeship() runs whatever treeship your PATH resolves, even if $TREESHIP_BIN points elsewhere.

Treeship.ensure_cli()

A classmethod that resolves a working CLI binary without instantiating the SDK -- the same bootstrap logic bot_mode=True runs at construction time, callable up front. Order: env var ($TREESHIP_BIN) -> PATH -> cache -> npm install -> GitHub Release; each step is fast and idempotent, and the function returns at the first success. Raises TreeshipBootstrapError if every probe fails.

Returns a BootstrapResult: ok, binary (absolute path), version, source ("env" | "path" | "npm-cache" | "github-release"), and cache_dir (set only for cache-based sources). A Treeship(bot_mode=True) instance exposes the same result on its .bootstrap property (None when bot_mode wasn't used), and .binary for the resolved path regardless of mode.

from treeship_sdk import Treeship

result = Treeship.ensure_cli()
print(result.source)  # "path", "npm-cache", "github-release", ...

Quick start

from treeship_sdk import Treeship

ts = Treeship()

# Attest an action
result = ts.attest_action(
    actor="agent://my-agent",
    action="tool.call",
)
print(result.artifact_id)

# Verify it
verified = ts.verify(result.artifact_id)
print(verified.outcome)  # "pass"

# Upload a closed session's receipt and get the public URL
report = ts.session_report()
print(report.receipt_url)

Treeship class

Wraps the treeship CLI. All methods raise TreeshipError on CLI failure.

attest_action(actor, action, ...)

Create a signed action receipt.

result = ts.attest_action(
    actor="agent://coder",
    action="tool.call",
    parent_id="art_abc123",
    meta={"tool": "read_file", "path": "src/main.rs"},
)
ParameterTypeDescription
actorstrActor URI
actionstrLabel for the action
parent_idOptional[str]Parent artifact id for chain linking
approval_nonceOptional[str]Nonce from an existing approval
metaOptional[Dict[str, Any]]Arbitrary metadata

Returns ActionResult(artifact_id).

attest_approval(approver, description, allowed_actions=None, allowed_actors=None, allowed_subjects=None, max_uses=None, unscoped=False, expires_at=None)

Create a signed approval receipt with a binding nonce.

Replay is enforced by the local Approval Use Journal, which reserves a use before the action is signed — a reused nonce is refused at attest time, on this device or workspace. Distributed single-use across machines is still open.

A scope is required. Pass at least one of allowed_actions, allowed_actors, allowed_subjects or max_uses — or unscoped=True to mint a bearer approval deliberately. An approval that constrains nothing is a bearer token, and the CLI refuses to create one by omission.

expires_at is an RFC 3339 timestamp (2030-12-31T23:59:59Z), not a duration. It was named expires_in before, which invited "1h" — a value the CLI signed verbatim and then compared as text, producing an approval that was already expired.

approval = ts.attest_approval(
    approver="human://alice",
    description="approve payment max $500",
    allowed_actions=["payments.charge"],
    max_uses=1,
)

Returns ApprovalResult(artifact_id, nonce).

attest_handoff(from_actor, to_actor, artifacts, approvals=None)

Create a signed handoff receipt between agents.

Returns ActionResult(artifact_id).

attest_decision(actor, model=None, tokens_in=None, ...)

Create a signed decision receipt capturing LLM reasoning context.

Returns ActionResult(artifact_id).

verify(artifact_id)

Verify an artifact and walk its chain.

result = ts.verify("art_abc123")
if result.outcome == "pass":
    print(f"Chain length: {result.chain}")

Returns VerifyResult(outcome, chain, target).

hub_push(artifact_id)

Push an artifact to the configured hub.

Returns PushResult(hub_url, rekor_index).

wrap(command, actor=None, *, timeout=None)

Wrap a shell command with a signed receipt. command accepts a string (split with shlex) or a sequence of arguments (preferred -- exact argv, no quoting ambiguity).

The wrapper calls treeship wrap --format json under the hood: in JSON mode the CLI moves the wrapped command's own stdout onto stderr (still captured into the receipt's output digest) so that stdout carries exactly one JSON document -- the artifact id -- for the wrapper to parse.

Returns ActionResult(artifact_id).

session_event(event_type, *, tool=None, file=None, destination=None, actor=None, agent_name=None, duration_ms=None, exit_code=None)

Append a structured event to the active session's timeline. Mirrors ship.session.event() in the TypeScript SDK. The timeline is what the receipt page renders (unsigned, in the session's events.jsonl); signed artifacts remain the evidence. Typical event_type values: agent.called_tool, agent.wrote_file, agent.read_file, agent.connected_network, agent.decision.

Returns SessionEventResult(event_id, sequence_no, session_id).

session_report(session_id=None, *, timeout=None)

Upload a closed session's Session Receipt to the configured hub and return the public URL. Stays up until the publishing dock runs treeship hub unpublish.

# Upload the most recently closed session
result = ts.session_report()
print(result.receipt_url)
# https://treeship.dev/receipt/ssn_42e740bd9eb238f6

# Upload a specific session by id
result = ts.session_report(session_id="ssn_42e740bd9eb238f6")
ParameterTypeDescription
session_idOptional[str]Session id to upload. Defaults to the most recently closed session's package under .treeship/sessions/.

Returns SessionReportResult(session_id, receipt_url, agents, events).

This method shells out to treeship session report, which reads the .treeship package from disk, DPoP-signs a PUT to the hub, and prints the receipt URL. The Python wrapper parses the text output and returns the structured result.

The returned receipt_url is permanent and public. Share it freely; no token, no expiry, no auth required to fetch it. See the receipt API docs for the endpoint reference.

session_report() requires an attached hub. Without one the CLI returns receipt_url: null alongside hub not attached -- run `treeship hub attach` to publish; receipt verifies locally, and the wrapper raises TreeshipError: session report JSON missing receipt_url. Run treeship hub attach --endpoint https://api.treeship.dev first. The receipt still verifies offline without a hub — publishing is what needs one.

Result types

All results are simple dataclasses exported from treeship_sdk.

ActionResult

@dataclass
class ActionResult:
    artifact_id: str

ApprovalResult

@dataclass
class ApprovalResult:
    artifact_id: str
    nonce: str

VerifyResult

@dataclass
class VerifyResult:
    outcome: str  # "pass", "fail", "error"
    chain: int
    target: str

PushResult

@dataclass
class PushResult:
    hub_url: str
    rekor_index: Optional[int] = None

SessionEventResult

@dataclass
class SessionEventResult:
    event_id: str
    sequence_no: int
    session_id: str

SessionReportResult

@dataclass
class SessionReportResult:
    session_id: str
    receipt_url: str
    agents: int = 0
    events: int = 0

Error handling

The SDK raises TreeshipError when the CLI exits non-zero, when the CLI is missing from PATH, or when the CLI output cannot be parsed.

from treeship_sdk import Treeship, TreeshipError

ts = Treeship()

try:
    result = ts.session_report()
except TreeshipError as e:
    print(f"session report failed: {e}")
    print(f"CLI args used: {e.args_used}")