Skip to content
Open the portal
Developer docs

Python SDK reference

Engineers12 min read

The stratl package (sdk/python) does three things: it builds records, it owns the Python implementation of the record format (the API imports it; nothing else re-implements hashing), and it submits records with their original content.

Terminal window
uv add stratl

A context manager. On exit the record is built and, if a client was given, submitted with its artifacts. It submits even when the block raises, so a decision that crashed midway is still on record.

with stratl.decision(agent, subject, *, client=None, agent_name=None, agent_version=None,
agent_owner=None, subject_display=None) as d:
...
d.last # the Decision that was built (always available)
d.status # "recorded" or "spooled" when a client was given
d.artifacts # {"sha256:...": Artifact(data, content_type)} for every fingerprint in the record
Argument Meaning
agent The agent id, e.g. "claims-triage". Also recorded as the first actor
subject A (type, id) tuple, e.g. ("claim", "8843")
agent_name, agent_version, agent_owner Optional agent details. Send the version: it is how you later say which release decided
subject_display A human-readable label for the subject

Each method appends one event, numbered in order, timestamped now. Content arguments are fingerprinted; the bytes are kept as artifacts.

Method Event Notes
d.input(content) input hash
d.retrieval(source, docs) retrieval doc_refs, one fingerprint per document
d.model(provider, model, prompt, response, *, version=None, config=None) model_call input_hash, output_hash, config_hash (of config or {}), version
d.tool(name, args, result, *, status="ok") tool_call args_hash, result_hash, status
d.policy(policy_id, *, version, result, rule=None) policy_eval version is stored as a string
d.approval(*, required, approver=None, authority=None, decision=None, identity_provider=None, comment=None) approval Adds the approver as a human actor with role authority; comment is fingerprinted
d.actor(kind, id_, *, role=None, identity_provider=None) none Adds a participant explicitly (a requester, a system, a second reviewer)
d.action(name, *, target, result, params=None) action params_hash when params are given; executed_at is now
d.outcome(*, adverse, category=None, status="completed", effect_on_subject=None) outcome adverse is what triggers notices

d.build(assurance="observed", custody=None) builds the Decision without submitting. Records built by the SDK are observed: the SDK saw the calls happen. Do not raise it.

stratl.decision.fingerprint(value) returns ("sha256:<hex>", Artifact). The rules, shared with the TypeScript SDK so both produce the same fingerprint for the same content:

Value Bytes hashed Content type
str UTF-8 text/plain; charset=utf-8
bytes as given application/octet-stream
anything else RFC 8785 canonical JSON; values JSON cannot express (datetimes, Decimals) are written with str application/json

The record never contains the content. The client uploads the artifacts after the record is accepted: it asks /v1/artifacts/check which fingerprints the store lacks and PUTs only those, with the content type. The API recomputes the hash and refuses a mismatch. Pass Client(store_artifacts=False) to send fingerprints only.

client = stratl.Client(base_url=None, api_key=None, *, spool_dir=None, timeout=5.0, store_artifacts=True)

Reads STRATL_API_URL (default http://localhost:8000), STRATL_API_KEY and STRATL_SPOOL_DIR (default .stratl-spool).

Method Does
client.submit(decision, *, artifacts=None) → "recorded" or "spooled" POSTs the record, then uploads missing artifacts. Never raises for network problems: it spools the record and its artifacts to disk and returns "spooled". client.last_error says why
client.spooled() How many records wait in the spool
client.flush_spool() → FlushResult(sent, remaining, error) Resends spooled records in order, artifacts included; stops at the first failure and reports it
client.custody_labels() The custody labels records in this workspace should carry, asked once and cached. submit applies them to a record that still has the default labels, so a record written to a workspace that holds its own custody says customer

The spool file format is the one the TypeScript SDK writes, so either SDK can flush the other’s spool.

stratl.srf holds the models (Decision, Event, Subject, Agent, Actor, Provenance, Custody, Integrity) and two functions: canonicalize(value) (RFC 8785 bytes) and canonical_sha256(value). Decision.wire() is the JSON as transmitted (no nulls); Decision.unsigned_payload() is the wire form without integrity, which is what gets hashed and signed. The record format walkthrough.

examples/refund-agent/ in the backend repository is a complete agent: it reads tickets, decides refunds with Claude or with built-in rules, asks a person to approve large refunds, issues them and records every step keyed by the order. Run it with make example against a local API.