The persistence layer stores the full deliberative process in SQLite for audit, replay, and on-demand export. It is
configurable via environment variables and supports three modes: db_only, dual, and file_only.
| Variable | Description |
|---|---|
MORALSTACK_DB_PATH |
Path to SQLite database file |
MORALSTACK_PERSIST_MODE |
db_only | dual | file_only |
MORALSTACK_UI_USERNAME |
Basic Auth username for UI (optional) |
MORALSTACK_UI_PASSWORD |
Basic Auth password for UI (optional) |
Default: If MORALSTACK_DB_PATH is set and MORALSTACK_PERSIST_MODE is not set, mode defaults to db_only.
- db_only: All data in DB; no file artifacts (reports, decision_trace.jsonl, debug.log)
- dual: DB + legacy file writes (transition mode)
- file_only: Legacy behavior; no DB writes
| File | Responsibility |
|---|---|
moralstack/persistence/config.py |
Load env: DB_PATH, PERSIST_MODE, UI creds |
moralstack/persistence/context.py |
Contextvars: run_id, request_id, cycle |
moralstack/persistence/db.py |
SQLite schema, init_db, SqliteUnitOfWork, batch insert helpers |
moralstack/persistence/sink.py |
Safe insert: persist_llm_call, persist_decision_trace, persist_debug_event, batch APIs |
- runs — Run metadata (run_id, run_type, started_at, ended_at, status)
- requests — Per-request data (run_id, request_id, prompt, domain)
- llm_calls — Full LLM call log (prompt, system_prompt, raw_response, phase, module, cycle,
sequence_in_cycle). The optional
sequence_in_cycle(integer) encodes the logical order within a deliberation cycle (1=policy, 2=critic, 3=simulator, 4=perspectives, 5=hindsight, 6=refusal/finalize). It is used by the UI and report to display the Deliberation Journey in execution order (policy → critic/simulator/perspectives → hindsight) instead of completion order when modules run in parallel. - decision_traces — Decision audit trail (stage, sequence, trace_json)
- debug_events — Debug payloads (location, message, data)
- exports_cache — Cached markdown exports (optional)
- SqliteUnitOfWork: Context manager in
db.pythat holds a single connection for a logical unit of work. Usewith SqliteUnitOfWork() as uow:(orSqliteUnitOfWork(db_path=...)to pass a path). On exit: commit on success, rollback on exception; the connection is always closed. Whenget_persist_mode()isfile_onlyorget_db_path()is not set,uow.connisNone(no-op). - Optional
uowon sink:persist_llm_call,persist_decision_trace, andpersist_debug_eventaccept an optionaluow: SqliteUnitOfWork | None = None. If provided anduow.connis not None, they use that connection and do not commit or close (the UoW commits on exit). Callers can thus group multiple writes in one transaction. - Batch APIs:
persist_llm_calls_batch(entries, uow=None),persist_decision_traces_batch(entries, uow=None), andpersist_debug_events_batch(entries, uow=None)insert multiple rows in one go. Each entry is a dict with the same keys as the single-persist kwargs (context supplies run_id/request_id/cycle when missing). Low-levelinsert_llm_calls_batch(conn, rows),insert_decision_traces_batch(conn, rows), andinsert_debug_events_batch(conn, rows)indb.pyaccept a connection and a list of tuples for direct use with an existing UoW. - Thread-safety: Use one UoW per thread or per request; do not share the same UoW instance across threads. SQLite in WAL mode with one connection per UoW is safe for typical single-thread or per-request usage.
- Context:
set_current_run_id,set_current_request_id,set_current_cycleare set by CLI/benchmark/controller before processing. - Sink:
persist_llm_call,persist_decision_trace,persist_debug_eventare called from deliberation_runner, decision_service, diagnostics, risk estimator. They do not raise; failures are logged. Passuowwhen grouping writes in a single transaction. - Export:
moralstack/reports/markdown_export.pyprovidesexport_request_markdownandexport_run_benchmark_markdownfor on-demand report generation from DB. Single-request markdown is produced viarequest_report_from_dbplusrender_request_report(renderer_markdown).
The FastAPI dashboard (moralstack-ui) exposes runs and requests. After starting moralstack-ui, open *
http://localhost:8765/* in your browser. You will be redirected to the login page; use MORALSTACK_UI_USERNAME and
MORALSTACK_UI_PASSWORD from .env. After login, the dashboard is at /runs.
- Orchestrator — Sets request context
- Decision Explainability — Trace structure
- INSTALL.md — UI optional dependencies