Python SDK reference
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.
uv add stratlstratl.decision(...)
Section titled “stratl.decision(...)”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 givend.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 |
Events
Section titled “Events”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.
Content and fingerprints
Section titled “Content and fingerprints”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.
stratl.Client
Section titled “stratl.Client”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.
The record format module
Section titled “The record format module”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.
Example
Section titled “Example”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.