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


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.2.1.tar.gz (88.3 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.2.1-py3-none-any.whl (61.0 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: runtime_narrative-1.2.1.tar.gz
  • Upload date:
  • Size: 88.3 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.2.1.tar.gz
Algorithm Hash digest
SHA256 d181858d053ddebf5bfe6d6a82c041d5197842de3c3850076016da7518e2100e
MD5 70d7a4fcf75e808a9f81a8dae4e7a60a
BLAKE2b-256 fbef59b01cae43b3dc66e6bef0f80a234209184fd6e0311003dd535e6388f862

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for runtime_narrative-1.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 1805d1a5ff3af642984a1c333fbf5dec8c6552d55fec91cd03236cfb7675d021
MD5 3a47d8897f71ae8b93a2339c6ec78c22
BLAKE2b-256 c54a2e9900396e25d0dd6887005cf904f04d3c8964d197739bb3f5701d8740e7

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