Quickstart
Install
pip install briefcase-aiPick your path
Briefcase records decisions two ways. They serve two different outcomes — pick based on whether the decision needs to outlive the current process.
| Path | Outcome | API | When to use |
|---|---|---|---|
| Live Observability | Watch decisions as they happen | @capture + observe() | Local debugging, notebooks, low-overhead logging |
| Persistent Decisions | Reload, replay & audit later | DecisionSnapshot + a backend | Anything you’ll need to reproduce, verify, or govern |
- Lightweight: wraps a function and logs its inputs, outputs, and timing.
- Handed to an exporter (console, file, or memory) — not persisted to a backend.
- Best for low-overhead logging and live observability.
- Structured: you build the record field by field with typed inputs and outputs.
- Persisted to a storage backend and reloadable by ID.
- Replayable, so you can re-run and compare against the original output.
See Core Concepts for how each record is structured.
Record, persist, and replay
-
Record a decision (Live Observability)
The
@capturedecorator records every call — inputs, outputs, and timing — and hands the lightweight record to an exporter. But@capturealone has nowhere to send what it records:briefcase.observe()is the one call that wires up that exporter. Pass"memory"to collect records in a list,"console"to print them to stderr, or a.jsonlpath to append them to a file. Because@captureexports in a background thread by default, passasync_capture=Falsewhen you want the record available synchronously — for example to read it back right after the call.import briefcasemem = briefcase.observe("memory") # send captured records to memory@briefcase.capture(decision_type="ticket-classification", async_capture=False)def classify_ticket(text: str) -> str:# call your model herereturn "billing"classify_ticket("My invoice is wrong")print(mem.records[0])@captureis for low-overhead logging. To persist a structured decision that you can reload and replay, build aDecisionSnapshotand store it with a backend. -
Persist a snapshot (Persistent Decisions)
from briefcase import (DecisionSnapshot,Input,ModelParameters,Output,init,)from briefcase.storage import SqliteBackendinit() # start the native runtimedecision = DecisionSnapshot("classify_ticket")decision.add_input(Input("ticket_text", "My invoice is wrong", "string"))params = ModelParameters("gpt-4o-mini")params.with_provider("openai")params.with_parameter("temperature", 0.0)decision.with_model_parameters(params)output = Output("category", "billing", "string")output.with_confidence(0.93)decision.add_output(output)decision.with_execution_time(12.0)backend = SqliteBackend.in_memory() # or SqliteBackend("./decisions.db")decision_id = backend.save_decision(decision)print(f"Recorded decision {decision_id}")save_decisionreturns the snapshot ID. Use a file path instead ofin_memory()to keep decisions across runs. -
Replay a decision
Re-run a stored decision and compare the result against the original output.
from briefcase.replay import ReplayEngineengine = ReplayEngine(backend)result = engine.replay(decision_id, "strict")print("status:", result.status)print("outputs match:", result.outputs_match)print("execution time (ms):", result.execution_time_ms)ReplayResultexposes.status,.outputs_match,.replay_output,.execution_time_ms, and.policy_violations. Valid replay modes are"strict"and"tolerant".
What’s next
You’ve captured, persisted, and replayed a decision. The journey continues with controlling what runs before a decision, auditing one after the fact, and choosing where persistent decisions live.