Skip to content
Docs for briefcase-ai v3.3.0see what’s new.

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:

  • @capture is the lightweight path: it wraps a function and emits a plain dict to an exporter. Best for live observability.
  • DecisionSnapshot is 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 Input values sent to the model
  • outputs — a list of Output values 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
FieldWhat it isWhy it matters
inputsThe Input values sent to the modelA replay needs the same inputs to reproduce the outcome
outputsThe Output values the model returnedThe result under audit, and the baseline a replay is compared against
model_parametersModel name, provider, per-call settingsExplains why the output looked the way it did; a parameter change can be attributed
execution_contextThe runtime environmentSo a replay runs somewhere comparable and differences trace to a real change
execution_time_msHow long the call tookA performance baseline you can compare a replay against
tagsKey/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"]
  1. Build a DecisionSnapshot with inputs, outputs, and parameters
  2. Persist it with backend.save_decision(decision)
  3. The backend stores it and returns the decision ID
  4. Reload it later with backend.load_decision(decision_id)
  5. 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 chains

Output.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) # 42
print(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, init
from 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.