Storage Adapters
A storage adapter is the backend that holds your audit trail — the durable home for every decision Briefcase captures, and the surface you query when someone asks why a decision was made.
storage For: platform & governance
How it works
-
Init — call
briefcase.init()once and construct a backend. -
Create — build a
DecisionSnapshotfor eachclassify_ticketcall. -
Save —
save_decision()persists the record and returns its id. -
Query — pull records back with a
SnapshotQuerywhen you need to review them.
flowchart LR
A[classify_ticket] --> B[DecisionSnapshot]
B --> C[SqliteBackend]
C --> D[(Audit trail)]
E[Reviewer query] --> C
Install
pip install briefcase-ai[storage]Which backend should I use?
| Backend | Persistence | Reach for it when |
|---|---|---|
SqliteBackend.in_memory() | No — gone on exit | Tests and local experiments, where no durable audit trail is needed |
SqliteBackend("path.db") | Yes — a file on disk | A single node where you want a real, queryable audit trail |
BufferedBackend | Yes — wraps another backend | High write volume, where batching save_decision calls matters |
BufferedBackend is not a separate store — it wraps a durable backend (such as SqliteBackend) and batches save_decision calls until the buffer fills, so you trade a small flush delay for less write pressure under load.
Init -> create -> save -> query
-
Init the runtime and backend
import briefcasefrom briefcase.storage import SqliteBackendbriefcase.init() # start the native runtime once per processbackend = SqliteBackend("decisions.db") -
Create a decision for each
classify_ticketcall.from briefcase import DecisionSnapshot, Input, Outputdecision = DecisionSnapshot("classify_ticket")decision.add_input(Input("text", "reset my password", "string"))output = Output("category", "account_access", "string")output.with_confidence(0.92)decision.add_output(output)decision.add_tag("queue", "support") -
Save the record —
save_decision()returns its id.decision_id = backend.save_decision(decision)loaded = backend.load_decision(decision_id)print(loaded.function_name) # classify_ticket -
Query the audit trail with a
SnapshotQuery.from briefcase import SnapshotQueryresults = backend.query(SnapshotQuery().with_function_name("classify_ticket").with_tag("queue", "support"))print(len(results))
briefcase.init() must be called once before using a backend to start the native runtime.
Backends in detail
In-memory (for tests)
SqliteBackend.in_memory() keeps data in memory — fast and ephemeral, the right choice for tests where you do not need records to survive the process.
import briefcasefrom briefcase.storage import SqliteBackend
briefcase.init()backend = SqliteBackend.in_memory()File on disk (for a real audit trail)
SqliteBackend(path) writes to a file — a durable, queryable audit trail in one place, the workhorse for single-node deployments.
import briefcasefrom briefcase.storage import SqliteBackend
briefcase.init()backend = SqliteBackend("decisions.db")print(backend.health_check()) # TrueBuffered (for high volume)
BufferedBackend wraps a durable backend and batches save_decision calls until the buffer fills.
import briefcasefrom briefcase.storage import SqliteBackend, BufferedBackendfrom briefcase import DecisionSnapshot, Input
briefcase.init()backend = BufferedBackend(SqliteBackend("decisions.db"), buffer_size=100)
decision = DecisionSnapshot("classify_ticket")decision.add_input(Input("text", "update my address", "string"))backend.save_decision(decision)A governance query: load decisions for review
The point of a durable backend is the review it enables. When a reviewer asks for last week’s support-queue decisions, you answer with a tagged SnapshotQuery against the same store that captured them.
import briefcasefrom briefcase.storage import SqliteBackendfrom briefcase import SnapshotQuery
briefcase.init()backend = SqliteBackend("decisions.db")
# Pull every support-queue triage decision for reviewquery = ( SnapshotQuery() .with_function_name("classify_ticket") .with_tag("queue", "support") .with_limit(50) .with_offset(0))for decision in backend.query(query): # hand each record to a reviewer, or replay it to verify ...SnapshotQuery supports with_function_name, with_module_name, with_tag, with_limit, and with_offset. From here a reviewer can audit a decision or replay it to reproduce exactly what happened.
Snapshots: grouping multiple decisions
A Snapshot groups several decisions; save() returns the snapshot id and load() returns it.
import briefcasefrom briefcase.storage import SqliteBackendfrom briefcase import DecisionSnapshot, Input, Snapshot
briefcase.init()backend = SqliteBackend.in_memory()
decision = DecisionSnapshot("classify_ticket")decision.add_input(Input("text", "where is my order", "string"))
session = Snapshot("session")session.add_decision(decision)
snapshot_id = backend.save(session)restored = backend.load(snapshot_id)print(len(restored.decisions)) # 1The persistence interface
SqliteBackend exposes the full interface (BufferedBackend only buffers save_decision calls before flushing them to the backend it wraps):
backend.save(snapshot) # store a Snapshot, returns its idbackend.load(snapshot_id) # load a Snapshotbackend.save_decision(decision) # store a DecisionSnapshot, returns its idbackend.load_decision(decision_id)backend.query(snapshot_query) # run a SnapshotQuerybackend.delete(snapshot_id)backend.health_check()Available backends
| Backend | Class | Description |
|---|---|---|
| SQLite | SqliteBackend | Local SQLite database (file or in-memory) |
| Buffered | BufferedBackend | Wraps a backend and batches writes |
Where this fits
Storage is the Store & Query act: the durable home for everything Capture produced, and the surface the later acts read from.