Skip to main content

Model execution as human-readable stories with lean/rich failure diagnostics and optional LLM analysis

Project description

runtime-narrative

Turn any Python execution into a traceable story composed of named stages. Get minimal logs when everything works — and surgical, LLM-powered diagnostics the moment something breaks.

▶ Story started: Import Customers
✔ Load CSV           0.012s
✔ Validate Data      0.004s

❌ Failure at: Insert Records

  ValueError: duplicate customer id
  Location:   app/db.py:47  insert_row
  Code:       raise ValueError("duplicate customer id")
  Chain:      ValueError ← sqlite3.IntegrityError

  ## Exact Why
  A record with the same customer_id already exists (UNIQUE constraint).

  ## Targeted Fix
  Use INSERT OR IGNORE, or check for an existing row before inserting.

This README is a fast on-ramp. For complete API reference, every renderer/analyzer/integration in depth, and the full event schema, see WIKI.md.

Why

  • Stories and stagesstory()/stage() are dual sync/async context managers (or decorators, or auto-instrumentation) that need no restructuring of existing code.
  • Sub-story tracing — nest a story() inside an active one (e.g. an API call triggering a DB query) and it auto-links as a traceable child with its own success/failure and duration — no new API.
  • Lean by default, rich on demand — a compressed stack summary and exact failure frame always; local-variable capture with automatic secret redaction only when you ask for it.
  • Optional LLM diagnosis — Ollama, any OpenAI-compatible endpoint, or Anthropic Claude can turn a traceback into an exact-cause explanation and a targeted fix.
  • Bring your own everything — any object with handle(event) is a renderer; any object with analyze_failure(...) is an analyzer. Console, JSON, SQLite, OpenTelemetry, Prometheus, HTML, webhooks, and stdlib logging capture all ship built in.

Installation

pip install runtime-narrative

Optional extras unlock additional renderers and integrations:

Extra What it installs
console typer — colored terminal output in ConsoleRenderer
fastapi starletteRuntimeNarrativeMiddleware
otel opentelemetry-api, opentelemetry-sdkOtelRenderer, OtelLogRenderer, OtelMetricsRenderer
prometheus prometheus-clientPrometheusRenderer
anthropic anthropicAnthropicFailureAnalyzer
django django — Django ASGI/WSGI middleware
celery celeryNarrativeTask, connect_narrative
grpc grpcio — gRPC server interceptors
structlog structlog — richer default ConsoleRenderer style for captured logging output
all Everything above
pip install "runtime-narrative[console,fastapi,anthropic]"

Quick start

from runtime_narrative import story, stage

with story("Import Customers"):
    with stage("Load CSV"):
        rows = load_csv("customers.csv")
    with stage("Validate Data"):
        validate(rows)
    with stage("Insert Records"):
        db.insert(rows)

ConsoleRenderer is the default and needs no configuration — on failure it prints the exact frame, a source snippet, the exception chain, and a compressed stack summary. story/stage are dual sync/async: async with works identically for async code.


Sub-stories: end-to-end call tracing

Open a story() while another is already active (in the same sync/async context) and it automatically becomes a linked sub-story — inheriting the parent's renderers/diagnostics/analyzer, carrying parent_story_id/root_story_id, and succeeding or failing independently:

async def execute_query(sql: str):
    async with story(f"DB: {sql}"):           # auto-linked to whatever story is active
        async with stage("Execute Query"):
            await conn.execute(sql)

async def create_order():
    async with story("POST /orders"):
        async with stage("Persist Order"):
            await execute_query("INSERT INTO orders ...")

ConsoleRenderer renders the resulting call tree directly, tagging every line with a [short_id], coloring a whole story family consistently, and indenting by nesting depth:

[ad8cc2] ▶ Story started: POST /orders
  [ad8cc2] ▶ Stage started: Persist Order
    [d17c63] ▶ Story started: DB: INSERT orders
      [d17c63] ▶ Stage started: Execute Query
      [d17c63] ✔ Stage completed: Execute Query (0.021s)
    [d17c63] ▶ Story ended: SUCCESS (0.034s)
  [ad8cc2] ✔ Stage completed: Persist Order (0.034s)
[ad8cc2] ▶ Story ended: SUCCESS (0.052s)

This holds up under concurrency for free: asyncio.Task copies ContextVar state at creation and each OS thread starts with a fresh top-level context, so many concurrent callers sharing one helper (like execute_query above) never cross-link into each other's tree.

Run: uv run python examples/substory_db_call.py — full reference: WIKI §21


Capture existing logging calls

NarrativeLogHandler folds logging.warning()/.error() into the same event pipeline as story()/stage() — one stream instead of two, tagged with the story/stage it happened in:

import logging
from runtime_narrative import NarrativeLogHandler

logging.getLogger().addHandler(NarrativeLogHandler(level=logging.WARNING))

extra={...} becomes structured fields; with the structlog extra installed, ConsoleRenderer renders them with structlog's own default style (colored level, timestamp, key=value fields):

logger.warning("cache miss", extra={"order_id": "ORD-42"})
# [d9e653] 2026-07-01T16:28:34 [warning  ] cache miss   order_id=ORD-42 stage=Fetch

Customize per-level prefixes or plug in your own renderer:

ConsoleRenderer(level_icons={"warning": "⚠ ", "error": "✗ "})

Route different story families to different styles or destinations with FilteredRenderer(predicate, renderer) — every event carries story_name:

from runtime_narrative import ConsoleRenderer, FilteredRenderer

renderers = [
    FilteredRenderer(lambda e: e.story_name.startswith("GET "), ConsoleRenderer()),
    FilteredRenderer(lambda e: not e.story_name.startswith("GET "), ConsoleRenderer(level_icons={"error": "✗ "})),
]

Run: uv run python examples/logging_bridge.py, uv run python examples/structured_log_routing.py — full reference: WIKI §21


Story outcomes: fold the access log into the story line

StoryCompleted carries an outcome label. The FastAPI/Starlette and Django middlewares set it automatically from the response status, and ConsoleRenderer then renders a single self-contained line per request — story name, result, and HTTP status together:

[d7678e] ▶ Story started: GET /api/call
[d7678e] ▶ Story ended: GET /api/call - SUCCESS (200 OK, 0.023s)

Since the story line now carries everything the server access log would print, you can silence the duplicate line — e.g. uvicorn.run(app, access_log=False).

Outside middleware, set an outcome on any story yourself:

from runtime_narrative import http_outcome, story

with story("GET /api/call") as runtime:
    ...
    runtime.set_outcome(http_outcome(200))   # or any short label, e.g. "3 rows"

Feature tour

Everything below works the same way in every context (sync/async, decorators, auto-instrumentation, any framework middleware). One line each here; full detail and every parameter in the Wiki.

Area What you get Full reference
Decorators @runtime_narrative_story / @runtime_narrative_stage — wrap functions without restructuring call sites WIKI §7
Auto-instrumentation @narrative_class, @no_stage, instrument_module(), auto_instrument() — instrument classes/modules with zero call-site changes WIKI §8
Failure diagnostics Lean/rich modes, production traceback caps, secret redaction, FailureDiagnosticsConfig WIKI §9
Failure analyzers OllamaFailureAnalyzer, LLMFailureAnalyzer, AnthropicFailureAnalyzer, DeduplicatingAnalyzer, background_analysis=True WIKI §9, §16
Renderers ConsoleRenderer, JsonRenderer/RotatingJsonRenderer, HtmlReportRenderer, SqliteStoryRenderer, OtelRenderer/OtelLogRenderer/OtelMetricsRenderer, PrometheusRenderer, AlertRoutingRenderer, FilteredRenderer WIKI §10
Framework integrations FastAPI/Starlette middleware, Django ASGI/WSGI middleware, Celery task base class, gRPC interceptors WIKI §11
Async task groups NarrativeTaskGroup — concurrent asyncio tasks under one shared story WIKI §12
Persistence & CLI SqliteStoryRenderer + runtime-narrative failures / runtime-narrative story <id> WIKI §13
Testing StoryRecorder — dual sync/async assertion API, no output produced WIKI §14
dry_run mode Suppress stage-body exceptions; verify instrumentation wiring with no side effects WIKI §15
Custom renderers/analyzers Any handle(event) object is a renderer; any analyze_failure(...) object is an analyzer WIKI §17, §18
Utilities has_active_story(), stage(optional=True) for library code that may run with or without a story WIKI §6
StoryRuntime.record_failure() Record a failure in saga/rollback flows without owning exception propagation WIKI §5
Event schema All seven event dataclasses and their fields WIKI §20

Examples

Every script under examples/ is runnable as-is: uv run python examples/<name>.py.

Core

Script Demonstrates
success.py Minimal story()/stage() API, no decorators, a success path
basic.py @runtime_narrative_story/@runtime_narrative_stage decorators, a failure path
basic_ollama.py Same failure path with OllamaFailureAnalyzer attached

Sub-stories and logging (newest features)

Script Demonstrates
substory_db_call.py Nested story() auto-linking as a sub-story (API call → DB call)
logging_bridge.py NarrativeLogHandler folding logging calls into the story pipeline
structured_log_routing.py extra= fields, level_icons, and FilteredRenderer per-story-family routing

Auto-instrumentation

Script Demonstrates
narrative_class.py @narrative_class and @no_stage
instrument_module.py instrument_module() on an existing module
auto_instrument.py auto_instrument() import-hook, zero call-site changes

Failure diagnostics and analysis

Script Demonstrates
diagnostics_config.py FailureDiagnosticsConfig — rich mode, redaction, production caps
background_analysis.py background_analysis=True — non-blocking LLM analysis
anthropic_analyzer.py AnthropicFailureAnalyzer + DeduplicatingAnalyzer

Renderers and observability

Script Demonstrates
html_report.py HtmlReportRenderer — self-contained HTML report
sqlite_persistence.py SqliteStoryRenderer + the runtime-narrative CLI
otel_tracing.py OtelRenderer, OtelLogRenderer, OtelMetricsRenderer
alert_routing.py AlertRoutingRenderer — async webhook fan-out
colorful_errors_and_emojis.py ConsoleRenderer's built-in color + level_icons emoji across log levels and a failure

Framework integrations and concurrency

Script Demonstrates
middleware_skip_if.py RuntimeNarrativeMiddleware(skip_if=...) for FastAPI/Starlette
task_group.py NarrativeTaskGroup — concurrent asyncio tasks under one story
fastapi_app/ Full FastAPI demo app (uv run python -m examples.fastapi_app.run)

Testing and lifecycle utilities

Script Demonstrates
story_recorder.py StoryRecorder test assertion API
dry_run_mode.py dry_run=True — verify wiring with no side effects
optional_stage.py has_active_story() and stage(optional=True)
saga_record_failure.py StoryRuntime.record_failure() in a saga/rollback flow
stage_story_name.py story_name on StageStarted/StageCompleted

Environment variables

Variable Values Default Effect
RUNTIME_NARRATIVE_ENV development, production development Production caps tracebacks to 8 000 chars and forces lean mode
RUNTIME_NARRATIVE_FAILURE_DIAGNOSTICS lean, rich lean rich captures local variable values at the failing frames. Invalid values raise ValueError at story construction.
RUNTIME_NARRATIVE_ALLOW_RICH_IN_PRODUCTION 1, true off Bypass production safeguard; allow rich diagnostics in production
RUNTIME_NARRATIVE_MODEL model name string Default model for AnthropicFailureAnalyzer; also used by example scripts for OllamaFailureAnalyzer / LLMFailureAnalyzer
ANTHROPIC_API_KEY API key Required by AnthropicFailureAnalyzer; read automatically if api_key= is not passed

Python compatibility

Python 3.9+. Async task groups (NarrativeTaskGroup) require no additional dependencies. Type hints use from __future__ import annotations throughout for compatibility with older typing syntax.


More

  • WIKI.md — complete reference: every parameter, every renderer, every event field.
  • CHANGELOG.md — what changed in each release.
  • ROADMAP.md — what's shipped and what's next.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

runtime_narrative-1.3.0.tar.gz (90.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

runtime_narrative-1.3.0-py3-none-any.whl (62.2 kB view details)

Uploaded Python 3

File details

Details for the file runtime_narrative-1.3.0.tar.gz.

File metadata

  • Download URL: runtime_narrative-1.3.0.tar.gz
  • Upload date:
  • Size: 90.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.0

File hashes

Hashes for runtime_narrative-1.3.0.tar.gz
Algorithm Hash digest
SHA256 1943ff4f9975861ea3e2af0da2a0c1f2c8948d9e31ed60289182bb32ffbbbee0
MD5 5da23033660a07b65f7f8fbc04883f44
BLAKE2b-256 ef08c721d2b5e289094e3cd755b4a933295acc16f0fc582b54eb2a4016325590

See more details on using hashes here.

File details

Details for the file runtime_narrative-1.3.0-py3-none-any.whl.

File metadata

File hashes

Hashes for runtime_narrative-1.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 f19e1b8f6669d91e2d2eb6919e8ed17aec835125e2191e3653c13a4bc1a574c1
MD5 ac45e64af64a04916aa0f25b54a79b60
BLAKE2b-256 28814892b3c4307de29d780e6c5804cff53ed317c223d21b11a44e2ef52e4785

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page