SQLite Persistence & CLI
SqliteStoryRenderer writes every story, stage, and failure to a SQLite database as it happens. The bundled runtime-narrative CLI reads that database back for post-hoc inspection, and since it's plain SQLite you can also query it directly.
Schema
SqliteStoryRenderer creates three tables on first use.
stories — one row per story
| Column | Type | Description |
|---|---|---|
story_id | TEXT PK | UUID |
name | TEXT | Story name |
started_at | TEXT | ISO-8601 timestamp |
completed_at | TEXT | ISO-8601 timestamp |
success | INTEGER | 1 = success, 0 = failed |
duration_seconds | REAL | Computed from timestamps via julianday |
stages — one row per stage execution
| Column | Type | Description |
|---|---|---|
id | INTEGER PK | Auto-increment |
story_id | TEXT | FK to stories |
stage_name | TEXT | Stage name |
stage_index | INTEGER | Position in story |
parent_stage_name | TEXT | Null for top-level stages |
started_at | TEXT | |
completed_at | TEXT | |
duration_seconds | REAL | |
completed | INTEGER | 1 when StageCompleted emitted |
failed | INTEGER | 1 when FailureOccurred emitted |
failures — one row per failure
| Column | Type | Description |
|---|---|---|
id | INTEGER PK | Auto-increment |
story_id | TEXT | FK to stories |
stage_name | TEXT | Stage where failure occurred |
error_type | TEXT | Exception class name |
error_message | TEXT | str(exc) |
filename | TEXT | Primary frame path |
lineno | INTEGER | Primary frame line number |
function | TEXT | Primary frame function name |
source_line | TEXT | Failing source line |
traceback_text | TEXT | Full traceback |
exception_chain | TEXT | |
llm_analysis | TEXT | Null until LLMAnalysisReady back-fills it |
occurred_at | TEXT | datetime('now') |
Wiring up the renderer
python
from runtime_narrative import story, stage
from runtime_narrative.renderer.persistence_renderer import SqliteStoryRenderer
db = SqliteStoryRenderer("narrative.db")
with story("User Import", renderers=[db], total_stages=2):
with stage("Fetch"):
users = [{"id": 1, "name": "Alice"}]
with stage("Upsert"):
pass
CLI
The runtime-narrative CLI is registered as a console script at install time.
List recent failures
bash
# Last 10 failures in the default database
runtime-narrative failures
# Last 25 failures in a custom database
runtime-narrative failures --db narrative.db --last 25
# Filter by stage name
runtime-narrative failures --stage "Upsert"
# Filter by story name
runtime-narrative failures --story "User Import"
# Combine filters
runtime-narrative failures --stage "Charge Card" --story "Order%" --last 5
Show full details for a specific story
bash
runtime-narrative story <story_id>
runtime-narrative story a1b2c3d4 --db narrative.db
The story subcommand prints the story summary, a table of all stages with status and duration, and full failure details including LLM analysis when available.
Direct SQL access
Since the database is plain SQLite you can query it directly with any tool.
python
import sqlite3
conn = sqlite3.connect("narrative.db")
# Failed stories in the last hour
rows = conn.execute("""
SELECT name, duration_seconds, completed_at
FROM stories
WHERE success = 0
AND completed_at > datetime('now', '-1 hour')
ORDER BY completed_at DESC
""").fetchall()
# Most common failure types
rows = conn.execute("""
SELECT error_type, COUNT(*) as count
FROM failures
GROUP BY error_type
ORDER BY count DESC
""").fetchall()
Next step
See Testing with StoryRecorder for asserting on stage completion and failures in unit tests, without needing a database at all.