treeship-sdk (Python)
Python SDK for Treeship. Wraps the CLI binary for signing, verification, session reporting, and hub operations.
Install
pip install treeship-sdkThe 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:
| Parameter | Type | Description |
|---|---|---|
cli_path | Optional[str | Path] | Explicit path to the treeship binary. Highest priority. |
bot_mode | bool | Bootstrap the CLI if it is missing — resolves $TREESHIP_BIN, then a cache, then a GitHub Release or npm install. |
timeout | Optional[int] | Default subprocess timeout in seconds. |
cwd | Optional[str | Path] | Working directory for CLI calls. |
env | Optional[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"},
)| Parameter | Type | Description |
|---|---|---|
actor | str | Actor URI |
action | str | Label for the action |
parent_id | Optional[str] | Parent artifact id for chain linking |
approval_nonce | Optional[str] | Nonce from an existing approval |
meta | Optional[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")| Parameter | Type | Description |
|---|---|---|
session_id | Optional[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: strApprovalResult
@dataclass
class ApprovalResult:
artifact_id: str
nonce: strVerifyResult
@dataclass
class VerifyResult:
outcome: str # "pass", "fail", "error"
chain: int
target: strPushResult
@dataclass
class PushResult:
hub_url: str
rekor_index: Optional[int] = NoneSessionEventResult
@dataclass
class SessionEventResult:
event_id: str
sequence_no: int
session_id: strSessionReportResult
@dataclass
class SessionReportResult:
session_id: str
receipt_url: str
agents: int = 0
events: int = 0Error 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}")