Verifiable Decision Records for AI agents — Python client.
Capture each LLM call as a structured, verifiable record. Persist records
wherever you want. Verify and replay them later with the determs CLI.
This SDK is the open-core reference client for the Verifiable Decision Record specification.
pip install determs
# With optional client SDKs:
pip install "determs[anthropic,openai]"(Until the first public release, install from the built wheel in dist/:
pip install dist/determs-0.1.0-py3-none-any.whl.)
import anthropic
from determs.anthropic import wrap as wrap_anthropic
from determs.storage import FileStorage
client = wrap_anthropic(
anthropic.Anthropic(),
agent_id="support-triage",
storage=FileStorage("./determs_records"),
)
response = client.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=512,
messages=[{"role": "user", "content": "Hi"}],
)
# A record file landed under ./determs_records/{action_id}.json.from openai import OpenAI
from determs.openai import wrap as wrap_openai
from determs.storage import FileStorage
client = wrap_openai(
OpenAI(),
agent_id="support-triage",
storage=FileStorage("./determs_records"),
)
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": "Hi"}],
)from determs.storage import FileStorage, StdoutStorage, CallbackStorage
# Write each record as <dir>/<action_id>.json
FileStorage("./records")
# Print each record as one JSON line on stdout — pipeable.
StdoutStorage()
# Hand the record dict to your own callback — for queues, custom sinks, tests.
CallbackStorage(callback=lambda record: print(record["agent_id"]))Or pick a backend from environment variables:
from determs.storage import storage_from_env
storage = storage_from_env() # honours DETERMS_STORAGE and DETERMS_DIRIf you don't use the Anthropic or OpenAI SDKs directly (e.g. you call an inference service through your own HTTP client), build records explicitly:
from determs import build_record
from determs.storage import FileStorage
storage = FileStorage("./records")
record = build_record(
agent_id="my-agent",
model={"provider": "anthropic", "name": "claude-3-5"},
params={"temperature": 0.0},
input={"messages": [{"role": "user", "content": "Hi"}]},
output={"content": "Hello.", "finish_reason": "stop"},
context={"trace_id": "trace-001"},
)
storage.put(record)The determs binary is the verification surface. The SDK emits the record
JSON; the CLI handles capture, replay, and verify.
# Bundle the action into a full record (input + execution + receipt):
determs capture --input ./records/act-xxx.json --output ./full.record.json
# Replay it later: returns 0 if bit-exact.
determs replay --record ./full.record.json
# Verify: returns 0 if no tampering, 1 if any digest mismatches.
determs verify --record ./full.record.jsonimport anthropic
from determs.anthropic import wrap_async as wrap_anthropic_async
aclient = wrap_anthropic_async(
anthropic.AsyncAnthropic(),
agent_id="support-triage",
storage=FileStorage("./records"),
)
response = await aclient.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=512,
messages=[{"role": "user", "content": "Hi"}],
)determs.openai.wrap_async(...) works the same way for AsyncOpenAI.
stream = client.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=512,
messages=[{"role": "user", "content": "Hi"}],
stream=True,
)
for event in stream:
handle(event)
# A record is emitted only when the stream completes naturally.
# If the caller stops iterating before the end, no record is written.Async streaming uses async for after await client.messages.create(stream=True, ...).
Does:
- intercept
messages.create(Anthropic, sync + async, streaming + non-streaming) - intercept
chat.completions.create(OpenAI, sync + async, streaming + non-streaming) - accumulate streamed text, tool_use blocks, and tool_calls into a final record
- emit records only on complete streams; abandoned streams produce nothing
- build a well-formed action record consumable by the Determs CLI
- persist records via a swappable storage backend
- never let recording failures break the upstream LLM call
Does not yet:
- support Anthropic's
messages.stream(...)context manager (usemessages.create(stream=True)instead) - support the OpenAI Responses API (use
chat.completionsinstead) - talk to a Determs Cloud endpoint (phase 3)
- ship a replay UI
cd sdk/python
pip install -e ".[dev]"
pytest tests/The end-to-end tests in test_cli_compatibility.py require the determs
binary built from the workspace root (cargo build or cargo build --release).
cd sdk/python
pip install build
python -m build
# Produces dist/determs-0.1.0-py3-none-any.whl and dist/determs-0.1.0.tar.gz