Core Concepts
The mental model
To make a past AI decision reproducible and accountable, you have to capture more than the answer — you need what went in, what the model was, and the environment it ran in. Briefcase keeps these as distinct records so each can be stored, queried, replayed, and verified on its own.
Briefcase records decisions at two levels:
@captureis the lightweight path: it wraps a function and emits a plain dict to an exporter. Best for live observability.DecisionSnapshotis the native, persistent record described below — a structured snapshot you build, store, reload, and replay. This is the thing you audit and reproduce later.
The rest of this page walks the persistent types using the running classify_ticket triage example.
DecisionSnapshot
A DecisionSnapshot is the immutable, point-in-time account of a single AI decision — the thing you audit, replay, and verify later. Everything else in Briefcase exists to enrich or store this record. It holds:
- inputs — a list of
Inputvalues sent to the model - outputs — a list of
Outputvalues the model returned - ModelParameters — model name, provider, and per-parameter settings
- ExecutionContext — the runtime environment the decision ran in
- execution_time_ms — how long the call took
| Field | What it is | Why it matters |
|---|---|---|
inputs | The Input values sent to the model | A replay needs the same inputs to reproduce the outcome |
outputs | The Output values the model returned | The result under audit, and the baseline a replay is compared against |
model_parameters | Model name, provider, per-call settings | Explains why the output looked the way it did; a parameter change can be attributed |
execution_context | The runtime environment | So a replay runs somewhere comparable and differences trace to a real change |
execution_time_ms | How long the call took | A performance baseline you can compare a replay against |
tags | Key/value labels (e.g. environment) | Lets you query and group decisions later via SnapshotQuery |
from briefcase import ( DecisionSnapshot, Input, ModelParameters, Output, init,)from briefcase.storage import SqliteBackend
init()backend = SqliteBackend("./decisions.db")
decision = 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)
decision.add_output(Output("category", "billing", "string").with_confidence(0.93))decision.with_execution_time(12.0)
decision_id = backend.save_decision(decision)
loaded = backend.load_decision(decision_id)print(loaded.function_name)print([(i.name, i.value) for i in loaded.inputs])print([(o.name, o.value, o.confidence) for o in loaded.outputs])print(loaded.execution_time_ms)load_decision returns a DecisionSnapshot. Inputs and outputs are lists, so read them as .inputs and .outputs.
Decision Flow
graph LR
A["DecisionSnapshot built"] --> B["save_decision"]
B --> C["Storage Backend"]
C --> D["load_decision / query"]
D --> E["Replay / Audit"]
- Build a
DecisionSnapshotwith inputs, outputs, and parameters - Persist it with
backend.save_decision(decision) - The backend stores it and returns the decision ID
- Reload it later with
backend.load_decision(decision_id) - Replay or audit the stored decision
Input and Output
Input(name, value, data_type) and Output(name, value, data_type) are typed wrappers around a single named value. data_type is a string describing the value (for example "string" or "json").
from briefcase import Input, Output
prompt = Input("prompt", "Summarize this article", "string")
answer = Output("summary", "A short summary.", "string")answer.with_confidence(0.88) # returns the Output, so it chainsOutput.with_confidence(confidence) attaches a confidence score, readable as output.confidence.
ModelParameters
ModelParameters(model_name) captures the model configuration at call time.
from briefcase import ModelParameters
params = ModelParameters("gpt-4o-mini")params.with_provider("openai")params.with_parameter("temperature", 0.0)params.with_parameter("max_tokens", 256)
print(params.model_name, params.provider, params.parameters)Read back the configuration via .model_name, .provider, and the .parameters dict.
ExecutionContext
The same inputs can produce a different answer on a different runtime version or seed. ExecutionContext records the environment a decision ran in — so a replay can run in a comparable one and any difference is attributable to a real change rather than a moved goalpost. It captures the runtime version, resolved dependencies, the random seed, and relevant environment variables. It does not carry timing — use DecisionSnapshot.execution_time_ms for that.
from briefcase import ExecutionContext
ctx = ExecutionContext()ctx.with_runtime_version("3.11.0")ctx.with_dependency("torch", "2.1.0")ctx.with_random_seed(42)ctx.with_env_var("ENVIRONMENT", "production")
print(ctx.runtime_version) # "3.11.0"print(ctx.dependencies) # {"torch": "2.1.0"}print(ctx.random_seed) # 42print(ctx.environment_variables) # {"ENVIRONMENT": "production"}Snapshot
A single decision rarely tells the whole story — a request or session usually produces several. Snapshot(snapshot_type) groups related decisions so you can store, load, and reason about them as one unit — for example, every decision made in one support request or session. Add decisions with add_decision and persist the group with backend.save.
from briefcase import DecisionSnapshot, Input, Output, Snapshot, initfrom briefcase.storage import SqliteBackend
init()backend = SqliteBackend.in_memory()
session = Snapshot("session")
decision = DecisionSnapshot("classify_ticket")decision.add_input(Input("ticket_text", "Where is my refund?", "string"))decision.add_output(Output("category", "refunds", "string").with_confidence(0.9))session.add_decision(decision)
snapshot_id = backend.save(session)loaded = backend.load(snapshot_id)print(loaded.snapshot_type, len(loaded.decisions))backend.load returns a Snapshot; read its grouped decisions from .decisions.
Where this fits
These types are the vocabulary for the whole journey. Now put them to work, or go deeper on the recording API that produces them.