runtime-narrative 1.0.1__tar.gz → 1.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- runtime_narrative-1.2.0/PKG-INFO +317 -0
- runtime_narrative-1.2.0/README.md +258 -0
- {runtime_narrative-1.0.1 → runtime_narrative-1.2.0}/pyproject.toml +101 -98
- {runtime_narrative-1.0.1 → runtime_narrative-1.2.0}/runtime_narrative/__init__.py +7 -1
- {runtime_narrative-1.0.1 → runtime_narrative-1.2.0}/runtime_narrative/events.py +32 -1
- runtime_narrative-1.2.0/runtime_narrative/logging_bridge.py +70 -0
- {runtime_narrative-1.0.1 → runtime_narrative-1.2.0}/runtime_narrative/renderer/console.py +143 -4
- runtime_narrative-1.2.0/runtime_narrative/renderer/filter_renderer.py +48 -0
- {runtime_narrative-1.0.1 → runtime_narrative-1.2.0}/runtime_narrative/renderer/json_renderer.py +22 -0
- {runtime_narrative-1.0.1 → runtime_narrative-1.2.0}/runtime_narrative/renderer/otel_renderer.py +4 -0
- {runtime_narrative-1.0.1 → runtime_narrative-1.2.0}/runtime_narrative/story.py +42 -1
- runtime_narrative-1.2.0/runtime_narrative.egg-info/PKG-INFO +317 -0
- {runtime_narrative-1.0.1 → runtime_narrative-1.2.0}/runtime_narrative.egg-info/SOURCES.txt +4 -1
- {runtime_narrative-1.0.1 → runtime_narrative-1.2.0}/runtime_narrative.egg-info/requires.txt +4 -0
- runtime_narrative-1.2.0/tests/test_console_renderer.py +269 -0
- runtime_narrative-1.2.0/tests/test_filter_renderer.py +63 -0
- {runtime_narrative-1.0.1 → runtime_narrative-1.2.0}/tests/test_json_renderer.py +43 -0
- runtime_narrative-1.2.0/tests/test_logging_bridge.py +117 -0
- {runtime_narrative-1.0.1 → runtime_narrative-1.2.0}/tests/test_otel_renderer.py +19 -0
- {runtime_narrative-1.0.1 → runtime_narrative-1.2.0}/tests/test_story.py +68 -0
- runtime_narrative-1.0.1/PKG-INFO +0 -1040
- runtime_narrative-1.0.1/README.md +0 -984
- runtime_narrative-1.0.1/runtime_narrative.egg-info/PKG-INFO +0 -1040
- runtime_narrative-1.0.1/tests/test_console_renderer.py +0 -120
- {runtime_narrative-1.0.1 → runtime_narrative-1.2.0}/LICENSE +0 -0
- {runtime_narrative-1.0.1 → runtime_narrative-1.2.0}/runtime_narrative/analyzers/__init__.py +0 -0
- {runtime_narrative-1.0.1 → runtime_narrative-1.2.0}/runtime_narrative/analyzers/anthropic.py +0 -0
- {runtime_narrative-1.0.1 → runtime_narrative-1.2.0}/runtime_narrative/analyzers/base.py +0 -0
- {runtime_narrative-1.0.1 → runtime_narrative-1.2.0}/runtime_narrative/analyzers/deduplication.py +0 -0
- {runtime_narrative-1.0.1 → runtime_narrative-1.2.0}/runtime_narrative/analyzers/ollama.py +0 -0
- {runtime_narrative-1.0.1 → runtime_narrative-1.2.0}/runtime_narrative/celery.py +0 -0
- {runtime_narrative-1.0.1 → runtime_narrative-1.2.0}/runtime_narrative/cli.py +0 -0
- {runtime_narrative-1.0.1 → runtime_narrative-1.2.0}/runtime_narrative/context.py +0 -0
- {runtime_narrative-1.0.1 → runtime_narrative-1.2.0}/runtime_narrative/decorators.py +0 -0
- {runtime_narrative-1.0.1 → runtime_narrative-1.2.0}/runtime_narrative/diagnostics.py +0 -0
- {runtime_narrative-1.0.1 → runtime_narrative-1.2.0}/runtime_narrative/failure.py +0 -0
- {runtime_narrative-1.0.1 → runtime_narrative-1.2.0}/runtime_narrative/grpc_interceptor.py +0 -0
- {runtime_narrative-1.0.1 → runtime_narrative-1.2.0}/runtime_narrative/instrumentation.py +0 -0
- {runtime_narrative-1.0.1 → runtime_narrative-1.2.0}/runtime_narrative/middleware.py +0 -0
- {runtime_narrative-1.0.1 → runtime_narrative-1.2.0}/runtime_narrative/middleware_django.py +0 -0
- {runtime_narrative-1.0.1 → runtime_narrative-1.2.0}/runtime_narrative/renderer/__init__.py +0 -0
- {runtime_narrative-1.0.1 → runtime_narrative-1.2.0}/runtime_narrative/renderer/alert_renderer.py +0 -0
- {runtime_narrative-1.0.1 → runtime_narrative-1.2.0}/runtime_narrative/renderer/html_renderer.py +0 -0
- {runtime_narrative-1.0.1 → runtime_narrative-1.2.0}/runtime_narrative/renderer/otel_log_renderer.py +0 -0
- {runtime_narrative-1.0.1 → runtime_narrative-1.2.0}/runtime_narrative/renderer/otel_metrics_renderer.py +0 -0
- {runtime_narrative-1.0.1 → runtime_narrative-1.2.0}/runtime_narrative/renderer/persistence_renderer.py +0 -0
- {runtime_narrative-1.0.1 → runtime_narrative-1.2.0}/runtime_narrative/renderer/prometheus_renderer.py +0 -0
- {runtime_narrative-1.0.1 → runtime_narrative-1.2.0}/runtime_narrative/stage.py +0 -0
- {runtime_narrative-1.0.1 → runtime_narrative-1.2.0}/runtime_narrative/task_group.py +0 -0
- {runtime_narrative-1.0.1 → runtime_narrative-1.2.0}/runtime_narrative/testing.py +0 -0
- {runtime_narrative-1.0.1 → runtime_narrative-1.2.0}/runtime_narrative.egg-info/dependency_links.txt +0 -0
- {runtime_narrative-1.0.1 → runtime_narrative-1.2.0}/runtime_narrative.egg-info/entry_points.txt +0 -0
- {runtime_narrative-1.0.1 → runtime_narrative-1.2.0}/runtime_narrative.egg-info/top_level.txt +0 -0
- {runtime_narrative-1.0.1 → runtime_narrative-1.2.0}/setup.cfg +0 -0
- {runtime_narrative-1.0.1 → runtime_narrative-1.2.0}/tests/test_alert_renderer.py +0 -0
- {runtime_narrative-1.0.1 → runtime_narrative-1.2.0}/tests/test_analyzers.py +0 -0
- {runtime_narrative-1.0.1 → runtime_narrative-1.2.0}/tests/test_anthropic_analyzer.py +0 -0
- {runtime_narrative-1.0.1 → runtime_narrative-1.2.0}/tests/test_async_renderer.py +0 -0
- {runtime_narrative-1.0.1 → runtime_narrative-1.2.0}/tests/test_celery.py +0 -0
- {runtime_narrative-1.0.1 → runtime_narrative-1.2.0}/tests/test_decorators.py +0 -0
- {runtime_narrative-1.0.1 → runtime_narrative-1.2.0}/tests/test_deduplication.py +0 -0
- {runtime_narrative-1.0.1 → runtime_narrative-1.2.0}/tests/test_diagnostics.py +0 -0
- {runtime_narrative-1.0.1 → runtime_narrative-1.2.0}/tests/test_dry_run.py +0 -0
- {runtime_narrative-1.0.1 → runtime_narrative-1.2.0}/tests/test_failure.py +0 -0
- {runtime_narrative-1.0.1 → runtime_narrative-1.2.0}/tests/test_grpc_interceptor.py +0 -0
- {runtime_narrative-1.0.1 → runtime_narrative-1.2.0}/tests/test_html_renderer.py +0 -0
- {runtime_narrative-1.0.1 → runtime_narrative-1.2.0}/tests/test_instrumentation.py +0 -0
- {runtime_narrative-1.0.1 → runtime_narrative-1.2.0}/tests/test_instrumentation_phase2.py +0 -0
- {runtime_narrative-1.0.1 → runtime_narrative-1.2.0}/tests/test_issues.py +0 -0
- {runtime_narrative-1.0.1 → runtime_narrative-1.2.0}/tests/test_middleware.py +0 -0
- {runtime_narrative-1.0.1 → runtime_narrative-1.2.0}/tests/test_middleware_django.py +0 -0
- {runtime_narrative-1.0.1 → runtime_narrative-1.2.0}/tests/test_middleware_propagation.py +0 -0
- {runtime_narrative-1.0.1 → runtime_narrative-1.2.0}/tests/test_otel_log_renderer.py +0 -0
- {runtime_narrative-1.0.1 → runtime_narrative-1.2.0}/tests/test_otel_metrics_renderer.py +0 -0
- {runtime_narrative-1.0.1 → runtime_narrative-1.2.0}/tests/test_persistence_renderer.py +0 -0
- {runtime_narrative-1.0.1 → runtime_narrative-1.2.0}/tests/test_prometheus_renderer.py +0 -0
- {runtime_narrative-1.0.1 → runtime_narrative-1.2.0}/tests/test_redaction_extended.py +0 -0
- {runtime_narrative-1.0.1 → runtime_narrative-1.2.0}/tests/test_stage.py +0 -0
- {runtime_narrative-1.0.1 → runtime_narrative-1.2.0}/tests/test_stage_metadata.py +0 -0
- {runtime_narrative-1.0.1 → runtime_narrative-1.2.0}/tests/test_structured_analysis.py +0 -0
- {runtime_narrative-1.0.1 → runtime_narrative-1.2.0}/tests/test_task_group.py +0 -0
- {runtime_narrative-1.0.1 → runtime_narrative-1.2.0}/tests/test_testing_utils.py +0 -0
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: runtime-narrative
|
|
3
|
+
Version: 1.2.0
|
|
4
|
+
Summary: Model execution as human-readable stories with lean/rich failure diagnostics and optional LLM analysis
|
|
5
|
+
Author-email: Shashank Raj <shashank.raj28@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/sraj0501/runtime_narrative
|
|
8
|
+
Project-URL: Repository, https://github.com/sraj0501/runtime_narrative
|
|
9
|
+
Project-URL: Bug Tracker, https://github.com/sraj0501/runtime_narrative/issues
|
|
10
|
+
Keywords: logging,observability,tracing,fastapi,debugging,diagnostics,runtime_narrative
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Classifier: Topic :: System :: Logging
|
|
22
|
+
Classifier: Topic :: System :: Monitoring
|
|
23
|
+
Classifier: Typing :: Typed
|
|
24
|
+
Requires-Python: >=3.9
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Requires-Dist: python-dotenv>=1.2.1
|
|
28
|
+
Provides-Extra: console
|
|
29
|
+
Requires-Dist: typer>=0.9.0; extra == "console"
|
|
30
|
+
Provides-Extra: fastapi
|
|
31
|
+
Requires-Dist: starlette>=0.27.0; extra == "fastapi"
|
|
32
|
+
Provides-Extra: otel
|
|
33
|
+
Requires-Dist: opentelemetry-api>=1.20.0; extra == "otel"
|
|
34
|
+
Requires-Dist: opentelemetry-sdk>=1.20.0; extra == "otel"
|
|
35
|
+
Provides-Extra: prometheus
|
|
36
|
+
Requires-Dist: prometheus-client>=0.19.0; extra == "prometheus"
|
|
37
|
+
Provides-Extra: anthropic
|
|
38
|
+
Requires-Dist: anthropic>=0.25.0; extra == "anthropic"
|
|
39
|
+
Provides-Extra: django
|
|
40
|
+
Requires-Dist: django>=3.2; extra == "django"
|
|
41
|
+
Provides-Extra: celery
|
|
42
|
+
Requires-Dist: celery>=5.0; extra == "celery"
|
|
43
|
+
Provides-Extra: grpc
|
|
44
|
+
Requires-Dist: grpcio>=1.50.0; extra == "grpc"
|
|
45
|
+
Provides-Extra: structlog
|
|
46
|
+
Requires-Dist: structlog>=25.5.0; extra == "structlog"
|
|
47
|
+
Provides-Extra: all
|
|
48
|
+
Requires-Dist: typer>=0.9.0; extra == "all"
|
|
49
|
+
Requires-Dist: starlette>=0.27.0; extra == "all"
|
|
50
|
+
Requires-Dist: opentelemetry-api>=1.20.0; extra == "all"
|
|
51
|
+
Requires-Dist: opentelemetry-sdk>=1.20.0; extra == "all"
|
|
52
|
+
Requires-Dist: prometheus-client>=0.19.0; extra == "all"
|
|
53
|
+
Requires-Dist: anthropic>=0.25.0; extra == "all"
|
|
54
|
+
Requires-Dist: django>=3.2; extra == "all"
|
|
55
|
+
Requires-Dist: celery>=5.0; extra == "all"
|
|
56
|
+
Requires-Dist: grpcio>=1.50.0; extra == "all"
|
|
57
|
+
Requires-Dist: structlog>=25.5.0; extra == "all"
|
|
58
|
+
Dynamic: license-file
|
|
59
|
+
|
|
60
|
+
# runtime-narrative
|
|
61
|
+
|
|
62
|
+
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.
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
▶ Story started: Import Customers
|
|
66
|
+
✔ Load CSV 0.012s
|
|
67
|
+
✔ Validate Data 0.004s
|
|
68
|
+
|
|
69
|
+
❌ Failure at: Insert Records
|
|
70
|
+
|
|
71
|
+
ValueError: duplicate customer id
|
|
72
|
+
Location: app/db.py:47 insert_row
|
|
73
|
+
Code: raise ValueError("duplicate customer id")
|
|
74
|
+
Chain: ValueError ← sqlite3.IntegrityError
|
|
75
|
+
|
|
76
|
+
## Exact Why
|
|
77
|
+
A record with the same customer_id already exists (UNIQUE constraint).
|
|
78
|
+
|
|
79
|
+
## Targeted Fix
|
|
80
|
+
Use INSERT OR IGNORE, or check for an existing row before inserting.
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
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](WIKI.md)**.
|
|
84
|
+
|
|
85
|
+
## Why
|
|
86
|
+
|
|
87
|
+
- **Stories and stages** — `story()`/`stage()` are dual sync/async context managers (or decorators, or auto-instrumentation) that need no restructuring of existing code.
|
|
88
|
+
- **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.
|
|
89
|
+
- **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.
|
|
90
|
+
- **Optional LLM diagnosis** — Ollama, any OpenAI-compatible endpoint, or Anthropic Claude can turn a traceback into an exact-cause explanation and a targeted fix.
|
|
91
|
+
- **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.
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Installation
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
pip install runtime-narrative
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Optional extras unlock additional renderers and integrations:
|
|
102
|
+
|
|
103
|
+
| Extra | What it installs |
|
|
104
|
+
|---|---|
|
|
105
|
+
| `console` | `typer` — colored terminal output in `ConsoleRenderer` |
|
|
106
|
+
| `fastapi` | `starlette` — `RuntimeNarrativeMiddleware` |
|
|
107
|
+
| `otel` | `opentelemetry-api`, `opentelemetry-sdk` — `OtelRenderer`, `OtelLogRenderer`, `OtelMetricsRenderer` |
|
|
108
|
+
| `prometheus` | `prometheus-client` — `PrometheusRenderer` |
|
|
109
|
+
| `anthropic` | `anthropic` — `AnthropicFailureAnalyzer` |
|
|
110
|
+
| `django` | `django` — Django ASGI/WSGI middleware |
|
|
111
|
+
| `celery` | `celery` — `NarrativeTask`, `connect_narrative` |
|
|
112
|
+
| `grpc` | `grpcio` — gRPC server interceptors |
|
|
113
|
+
| `structlog` | `structlog` — richer default `ConsoleRenderer` style for captured `logging` output |
|
|
114
|
+
| `all` | Everything above |
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
pip install "runtime-narrative[console,fastapi,anthropic]"
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Quick start
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
from runtime_narrative import story, stage
|
|
126
|
+
|
|
127
|
+
with story("Import Customers"):
|
|
128
|
+
with stage("Load CSV"):
|
|
129
|
+
rows = load_csv("customers.csv")
|
|
130
|
+
with stage("Validate Data"):
|
|
131
|
+
validate(rows)
|
|
132
|
+
with stage("Insert Records"):
|
|
133
|
+
db.insert(rows)
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
`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.
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Sub-stories: end-to-end call tracing
|
|
141
|
+
|
|
142
|
+
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:
|
|
143
|
+
|
|
144
|
+
```python
|
|
145
|
+
async def execute_query(sql: str):
|
|
146
|
+
async with story(f"DB: {sql}"): # auto-linked to whatever story is active
|
|
147
|
+
async with stage("Execute Query"):
|
|
148
|
+
await conn.execute(sql)
|
|
149
|
+
|
|
150
|
+
async def create_order():
|
|
151
|
+
async with story("POST /orders"):
|
|
152
|
+
async with stage("Persist Order"):
|
|
153
|
+
await execute_query("INSERT INTO orders ...")
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
`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:
|
|
157
|
+
|
|
158
|
+
```
|
|
159
|
+
[ad8cc2] ▶ Story started: POST /orders
|
|
160
|
+
[ad8cc2] ▶ Stage started: Persist Order
|
|
161
|
+
[d17c63] ▶ Story started: DB: INSERT orders
|
|
162
|
+
[d17c63] ▶ Stage started: Execute Query
|
|
163
|
+
[d17c63] ✔ Stage completed: Execute Query (0.021s)
|
|
164
|
+
[d17c63] ▶ Story ended: SUCCESS (0.034s)
|
|
165
|
+
[ad8cc2] ✔ Stage completed: Persist Order (0.034s)
|
|
166
|
+
[ad8cc2] ▶ Story ended: SUCCESS (0.052s)
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
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.
|
|
170
|
+
|
|
171
|
+
Run: `uv run python examples/substory_db_call.py` — full reference: [WIKI §21](WIKI.md#21-sub-stories-and-log-capture)
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## Capture existing `logging` calls
|
|
176
|
+
|
|
177
|
+
`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:
|
|
178
|
+
|
|
179
|
+
```python
|
|
180
|
+
import logging
|
|
181
|
+
from runtime_narrative import NarrativeLogHandler
|
|
182
|
+
|
|
183
|
+
logging.getLogger().addHandler(NarrativeLogHandler(level=logging.WARNING))
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
`extra={...}` becomes structured fields; with the `structlog` extra installed, `ConsoleRenderer` renders them with structlog's own default style (colored level, timestamp, `key=value` fields):
|
|
187
|
+
|
|
188
|
+
```python
|
|
189
|
+
logger.warning("cache miss", extra={"order_id": "ORD-42"})
|
|
190
|
+
# [d9e653] 2026-07-01T16:28:34 [warning ] cache miss order_id=ORD-42 stage=Fetch
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Customize per-level prefixes or plug in your own renderer:
|
|
194
|
+
|
|
195
|
+
```python
|
|
196
|
+
ConsoleRenderer(level_icons={"warning": "⚠ ", "error": "✗ "})
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Route different story families to different styles or destinations with `FilteredRenderer(predicate, renderer)` — every event carries `story_name`:
|
|
200
|
+
|
|
201
|
+
```python
|
|
202
|
+
from runtime_narrative import ConsoleRenderer, FilteredRenderer
|
|
203
|
+
|
|
204
|
+
renderers = [
|
|
205
|
+
FilteredRenderer(lambda e: e.story_name.startswith("GET "), ConsoleRenderer()),
|
|
206
|
+
FilteredRenderer(lambda e: not e.story_name.startswith("GET "), ConsoleRenderer(level_icons={"error": "✗ "})),
|
|
207
|
+
]
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Run: `uv run python examples/logging_bridge.py`, `uv run python examples/structured_log_routing.py` — full reference: [WIKI §21](WIKI.md#21-sub-stories-and-log-capture)
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## Feature tour
|
|
215
|
+
|
|
216
|
+
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.
|
|
217
|
+
|
|
218
|
+
| Area | What you get | Full reference |
|
|
219
|
+
|---|---|---|
|
|
220
|
+
| Decorators | `@runtime_narrative_story` / `@runtime_narrative_stage` — wrap functions without restructuring call sites | [WIKI §7](WIKI.md#7-decorators) |
|
|
221
|
+
| Auto-instrumentation | `@narrative_class`, `@no_stage`, `instrument_module()`, `auto_instrument()` — instrument classes/modules with zero call-site changes | [WIKI §8](WIKI.md#8-auto-instrumentation) |
|
|
222
|
+
| Failure diagnostics | Lean/rich modes, production traceback caps, secret redaction, `FailureDiagnosticsConfig` | [WIKI §9](WIKI.md#9-failure-diagnostics) |
|
|
223
|
+
| Failure analyzers | `OllamaFailureAnalyzer`, `LLMFailureAnalyzer`, `AnthropicFailureAnalyzer`, `DeduplicatingAnalyzer`, `background_analysis=True` | [WIKI §9](WIKI.md#9-failure-diagnostics), [§16](WIKI.md#16-background-analysis) |
|
|
224
|
+
| Renderers | `ConsoleRenderer`, `JsonRenderer`/`RotatingJsonRenderer`, `HtmlReportRenderer`, `SqliteStoryRenderer`, `OtelRenderer`/`OtelLogRenderer`/`OtelMetricsRenderer`, `PrometheusRenderer`, `AlertRoutingRenderer`, `FilteredRenderer` | [WIKI §10](WIKI.md#10-renderers) |
|
|
225
|
+
| Framework integrations | FastAPI/Starlette middleware, Django ASGI/WSGI middleware, Celery task base class, gRPC interceptors | [WIKI §11](WIKI.md#11-framework-integrations) |
|
|
226
|
+
| Async task groups | `NarrativeTaskGroup` — concurrent `asyncio` tasks under one shared story | [WIKI §12](WIKI.md#12-async-task-groups) |
|
|
227
|
+
| Persistence & CLI | `SqliteStoryRenderer` + `runtime-narrative failures` / `runtime-narrative story <id>` | [WIKI §13](WIKI.md#13-sqlite-persistence-and-cli) |
|
|
228
|
+
| Testing | `StoryRecorder` — dual sync/async assertion API, no output produced | [WIKI §14](WIKI.md#14-testing-with-storyrecorder) |
|
|
229
|
+
| `dry_run` mode | Suppress stage-body exceptions; verify instrumentation wiring with no side effects | [WIKI §15](WIKI.md#15-dry_run-mode) |
|
|
230
|
+
| Custom renderers/analyzers | Any `handle(event)` object is a renderer; any `analyze_failure(...)` object is an analyzer | [WIKI §17](WIKI.md#17-custom-renderers), [§18](WIKI.md#18-custom-failure-analyzers) |
|
|
231
|
+
| Utilities | `has_active_story()`, `stage(optional=True)` for library code that may run with or without a story | [WIKI §6](WIKI.md#6-stage--stage-context-managers) |
|
|
232
|
+
| `StoryRuntime.record_failure()` | Record a failure in saga/rollback flows without owning exception propagation | [WIKI §5](WIKI.md#5-story--the-story-context-manager) |
|
|
233
|
+
| Event schema | All seven event dataclasses and their fields | [WIKI §20](WIKI.md#20-event-reference) |
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## Examples
|
|
238
|
+
|
|
239
|
+
Every script under `examples/` is runnable as-is: `uv run python examples/<name>.py`.
|
|
240
|
+
|
|
241
|
+
**Core**
|
|
242
|
+
| Script | Demonstrates |
|
|
243
|
+
|---|---|
|
|
244
|
+
| `success.py` | Minimal `story()`/`stage()` API, no decorators, a success path |
|
|
245
|
+
| `basic.py` | `@runtime_narrative_story`/`@runtime_narrative_stage` decorators, a failure path |
|
|
246
|
+
| `basic_ollama.py` | Same failure path with `OllamaFailureAnalyzer` attached |
|
|
247
|
+
|
|
248
|
+
**Sub-stories and logging (newest features)**
|
|
249
|
+
| Script | Demonstrates |
|
|
250
|
+
|---|---|
|
|
251
|
+
| `substory_db_call.py` | Nested `story()` auto-linking as a sub-story (API call → DB call) |
|
|
252
|
+
| `logging_bridge.py` | `NarrativeLogHandler` folding `logging` calls into the story pipeline |
|
|
253
|
+
| `structured_log_routing.py` | `extra=` fields, `level_icons`, and `FilteredRenderer` per-story-family routing |
|
|
254
|
+
|
|
255
|
+
**Auto-instrumentation**
|
|
256
|
+
| Script | Demonstrates |
|
|
257
|
+
|---|---|
|
|
258
|
+
| `narrative_class.py` | `@narrative_class` and `@no_stage` |
|
|
259
|
+
| `instrument_module.py` | `instrument_module()` on an existing module |
|
|
260
|
+
| `auto_instrument.py` | `auto_instrument()` import-hook, zero call-site changes |
|
|
261
|
+
|
|
262
|
+
**Failure diagnostics and analysis**
|
|
263
|
+
| Script | Demonstrates |
|
|
264
|
+
|---|---|
|
|
265
|
+
| `diagnostics_config.py` | `FailureDiagnosticsConfig` — rich mode, redaction, production caps |
|
|
266
|
+
| `background_analysis.py` | `background_analysis=True` — non-blocking LLM analysis |
|
|
267
|
+
| `anthropic_analyzer.py` | `AnthropicFailureAnalyzer` + `DeduplicatingAnalyzer` |
|
|
268
|
+
|
|
269
|
+
**Renderers and observability**
|
|
270
|
+
| Script | Demonstrates |
|
|
271
|
+
|---|---|
|
|
272
|
+
| `html_report.py` | `HtmlReportRenderer` — self-contained HTML report |
|
|
273
|
+
| `sqlite_persistence.py` | `SqliteStoryRenderer` + the `runtime-narrative` CLI |
|
|
274
|
+
| `otel_tracing.py` | `OtelRenderer`, `OtelLogRenderer`, `OtelMetricsRenderer` |
|
|
275
|
+
| `alert_routing.py` | `AlertRoutingRenderer` — async webhook fan-out |
|
|
276
|
+
|
|
277
|
+
**Framework integrations and concurrency**
|
|
278
|
+
| Script | Demonstrates |
|
|
279
|
+
|---|---|
|
|
280
|
+
| `middleware_skip_if.py` | `RuntimeNarrativeMiddleware(skip_if=...)` for FastAPI/Starlette |
|
|
281
|
+
| `task_group.py` | `NarrativeTaskGroup` — concurrent asyncio tasks under one story |
|
|
282
|
+
| `fastapi_app/` | Full FastAPI demo app (`uv run python -m examples.fastapi_app.run`) |
|
|
283
|
+
|
|
284
|
+
**Testing and lifecycle utilities**
|
|
285
|
+
| Script | Demonstrates |
|
|
286
|
+
|---|---|
|
|
287
|
+
| `story_recorder.py` | `StoryRecorder` test assertion API |
|
|
288
|
+
| `dry_run_mode.py` | `dry_run=True` — verify wiring with no side effects |
|
|
289
|
+
| `optional_stage.py` | `has_active_story()` and `stage(optional=True)` |
|
|
290
|
+
| `saga_record_failure.py` | `StoryRuntime.record_failure()` in a saga/rollback flow |
|
|
291
|
+
| `stage_story_name.py` | `story_name` on `StageStarted`/`StageCompleted` |
|
|
292
|
+
|
|
293
|
+
---
|
|
294
|
+
|
|
295
|
+
## Environment variables
|
|
296
|
+
|
|
297
|
+
| Variable | Values | Default | Effect |
|
|
298
|
+
|---|---|---|---|
|
|
299
|
+
| `RUNTIME_NARRATIVE_ENV` | `development`, `production` | `development` | Production caps tracebacks to 8 000 chars and forces lean mode |
|
|
300
|
+
| `RUNTIME_NARRATIVE_FAILURE_DIAGNOSTICS` | `lean`, `rich` | `lean` | `rich` captures local variable values at the failing frames. Invalid values raise `ValueError` at story construction. |
|
|
301
|
+
| `RUNTIME_NARRATIVE_ALLOW_RICH_IN_PRODUCTION` | `1`, `true` | off | Bypass production safeguard; allow rich diagnostics in production |
|
|
302
|
+
| `RUNTIME_NARRATIVE_MODEL` | model name string | — | Default model for `AnthropicFailureAnalyzer`; also used by example scripts for `OllamaFailureAnalyzer` / `LLMFailureAnalyzer` |
|
|
303
|
+
| `ANTHROPIC_API_KEY` | API key | — | Required by `AnthropicFailureAnalyzer`; read automatically if `api_key=` is not passed |
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
## Python compatibility
|
|
308
|
+
|
|
309
|
+
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.
|
|
310
|
+
|
|
311
|
+
---
|
|
312
|
+
|
|
313
|
+
## More
|
|
314
|
+
|
|
315
|
+
- **[WIKI.md](WIKI.md)** — complete reference: every parameter, every renderer, every event field.
|
|
316
|
+
- **[CHANGELOG.md](CHANGELOG.md)** — what changed in each release.
|
|
317
|
+
- **[ROADMAP.md](ROADMAP.md)** — what's shipped and what's next.
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
# runtime-narrative
|
|
2
|
+
|
|
3
|
+
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.
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
▶ Story started: Import Customers
|
|
7
|
+
✔ Load CSV 0.012s
|
|
8
|
+
✔ Validate Data 0.004s
|
|
9
|
+
|
|
10
|
+
❌ Failure at: Insert Records
|
|
11
|
+
|
|
12
|
+
ValueError: duplicate customer id
|
|
13
|
+
Location: app/db.py:47 insert_row
|
|
14
|
+
Code: raise ValueError("duplicate customer id")
|
|
15
|
+
Chain: ValueError ← sqlite3.IntegrityError
|
|
16
|
+
|
|
17
|
+
## Exact Why
|
|
18
|
+
A record with the same customer_id already exists (UNIQUE constraint).
|
|
19
|
+
|
|
20
|
+
## Targeted Fix
|
|
21
|
+
Use INSERT OR IGNORE, or check for an existing row before inserting.
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
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](WIKI.md)**.
|
|
25
|
+
|
|
26
|
+
## Why
|
|
27
|
+
|
|
28
|
+
- **Stories and stages** — `story()`/`stage()` are dual sync/async context managers (or decorators, or auto-instrumentation) that need no restructuring of existing code.
|
|
29
|
+
- **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.
|
|
30
|
+
- **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.
|
|
31
|
+
- **Optional LLM diagnosis** — Ollama, any OpenAI-compatible endpoint, or Anthropic Claude can turn a traceback into an exact-cause explanation and a targeted fix.
|
|
32
|
+
- **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.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Installation
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install runtime-narrative
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Optional extras unlock additional renderers and integrations:
|
|
43
|
+
|
|
44
|
+
| Extra | What it installs |
|
|
45
|
+
|---|---|
|
|
46
|
+
| `console` | `typer` — colored terminal output in `ConsoleRenderer` |
|
|
47
|
+
| `fastapi` | `starlette` — `RuntimeNarrativeMiddleware` |
|
|
48
|
+
| `otel` | `opentelemetry-api`, `opentelemetry-sdk` — `OtelRenderer`, `OtelLogRenderer`, `OtelMetricsRenderer` |
|
|
49
|
+
| `prometheus` | `prometheus-client` — `PrometheusRenderer` |
|
|
50
|
+
| `anthropic` | `anthropic` — `AnthropicFailureAnalyzer` |
|
|
51
|
+
| `django` | `django` — Django ASGI/WSGI middleware |
|
|
52
|
+
| `celery` | `celery` — `NarrativeTask`, `connect_narrative` |
|
|
53
|
+
| `grpc` | `grpcio` — gRPC server interceptors |
|
|
54
|
+
| `structlog` | `structlog` — richer default `ConsoleRenderer` style for captured `logging` output |
|
|
55
|
+
| `all` | Everything above |
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
pip install "runtime-narrative[console,fastapi,anthropic]"
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Quick start
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
from runtime_narrative import story, stage
|
|
67
|
+
|
|
68
|
+
with story("Import Customers"):
|
|
69
|
+
with stage("Load CSV"):
|
|
70
|
+
rows = load_csv("customers.csv")
|
|
71
|
+
with stage("Validate Data"):
|
|
72
|
+
validate(rows)
|
|
73
|
+
with stage("Insert Records"):
|
|
74
|
+
db.insert(rows)
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`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.
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Sub-stories: end-to-end call tracing
|
|
82
|
+
|
|
83
|
+
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:
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
async def execute_query(sql: str):
|
|
87
|
+
async with story(f"DB: {sql}"): # auto-linked to whatever story is active
|
|
88
|
+
async with stage("Execute Query"):
|
|
89
|
+
await conn.execute(sql)
|
|
90
|
+
|
|
91
|
+
async def create_order():
|
|
92
|
+
async with story("POST /orders"):
|
|
93
|
+
async with stage("Persist Order"):
|
|
94
|
+
await execute_query("INSERT INTO orders ...")
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
`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:
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
[ad8cc2] ▶ Story started: POST /orders
|
|
101
|
+
[ad8cc2] ▶ Stage started: Persist Order
|
|
102
|
+
[d17c63] ▶ Story started: DB: INSERT orders
|
|
103
|
+
[d17c63] ▶ Stage started: Execute Query
|
|
104
|
+
[d17c63] ✔ Stage completed: Execute Query (0.021s)
|
|
105
|
+
[d17c63] ▶ Story ended: SUCCESS (0.034s)
|
|
106
|
+
[ad8cc2] ✔ Stage completed: Persist Order (0.034s)
|
|
107
|
+
[ad8cc2] ▶ Story ended: SUCCESS (0.052s)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
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.
|
|
111
|
+
|
|
112
|
+
Run: `uv run python examples/substory_db_call.py` — full reference: [WIKI §21](WIKI.md#21-sub-stories-and-log-capture)
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Capture existing `logging` calls
|
|
117
|
+
|
|
118
|
+
`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:
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
import logging
|
|
122
|
+
from runtime_narrative import NarrativeLogHandler
|
|
123
|
+
|
|
124
|
+
logging.getLogger().addHandler(NarrativeLogHandler(level=logging.WARNING))
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
`extra={...}` becomes structured fields; with the `structlog` extra installed, `ConsoleRenderer` renders them with structlog's own default style (colored level, timestamp, `key=value` fields):
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
logger.warning("cache miss", extra={"order_id": "ORD-42"})
|
|
131
|
+
# [d9e653] 2026-07-01T16:28:34 [warning ] cache miss order_id=ORD-42 stage=Fetch
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Customize per-level prefixes or plug in your own renderer:
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
ConsoleRenderer(level_icons={"warning": "⚠ ", "error": "✗ "})
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Route different story families to different styles or destinations with `FilteredRenderer(predicate, renderer)` — every event carries `story_name`:
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
from runtime_narrative import ConsoleRenderer, FilteredRenderer
|
|
144
|
+
|
|
145
|
+
renderers = [
|
|
146
|
+
FilteredRenderer(lambda e: e.story_name.startswith("GET "), ConsoleRenderer()),
|
|
147
|
+
FilteredRenderer(lambda e: not e.story_name.startswith("GET "), ConsoleRenderer(level_icons={"error": "✗ "})),
|
|
148
|
+
]
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Run: `uv run python examples/logging_bridge.py`, `uv run python examples/structured_log_routing.py` — full reference: [WIKI §21](WIKI.md#21-sub-stories-and-log-capture)
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## Feature tour
|
|
156
|
+
|
|
157
|
+
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.
|
|
158
|
+
|
|
159
|
+
| Area | What you get | Full reference |
|
|
160
|
+
|---|---|---|
|
|
161
|
+
| Decorators | `@runtime_narrative_story` / `@runtime_narrative_stage` — wrap functions without restructuring call sites | [WIKI §7](WIKI.md#7-decorators) |
|
|
162
|
+
| Auto-instrumentation | `@narrative_class`, `@no_stage`, `instrument_module()`, `auto_instrument()` — instrument classes/modules with zero call-site changes | [WIKI §8](WIKI.md#8-auto-instrumentation) |
|
|
163
|
+
| Failure diagnostics | Lean/rich modes, production traceback caps, secret redaction, `FailureDiagnosticsConfig` | [WIKI §9](WIKI.md#9-failure-diagnostics) |
|
|
164
|
+
| Failure analyzers | `OllamaFailureAnalyzer`, `LLMFailureAnalyzer`, `AnthropicFailureAnalyzer`, `DeduplicatingAnalyzer`, `background_analysis=True` | [WIKI §9](WIKI.md#9-failure-diagnostics), [§16](WIKI.md#16-background-analysis) |
|
|
165
|
+
| Renderers | `ConsoleRenderer`, `JsonRenderer`/`RotatingJsonRenderer`, `HtmlReportRenderer`, `SqliteStoryRenderer`, `OtelRenderer`/`OtelLogRenderer`/`OtelMetricsRenderer`, `PrometheusRenderer`, `AlertRoutingRenderer`, `FilteredRenderer` | [WIKI §10](WIKI.md#10-renderers) |
|
|
166
|
+
| Framework integrations | FastAPI/Starlette middleware, Django ASGI/WSGI middleware, Celery task base class, gRPC interceptors | [WIKI §11](WIKI.md#11-framework-integrations) |
|
|
167
|
+
| Async task groups | `NarrativeTaskGroup` — concurrent `asyncio` tasks under one shared story | [WIKI §12](WIKI.md#12-async-task-groups) |
|
|
168
|
+
| Persistence & CLI | `SqliteStoryRenderer` + `runtime-narrative failures` / `runtime-narrative story <id>` | [WIKI §13](WIKI.md#13-sqlite-persistence-and-cli) |
|
|
169
|
+
| Testing | `StoryRecorder` — dual sync/async assertion API, no output produced | [WIKI §14](WIKI.md#14-testing-with-storyrecorder) |
|
|
170
|
+
| `dry_run` mode | Suppress stage-body exceptions; verify instrumentation wiring with no side effects | [WIKI §15](WIKI.md#15-dry_run-mode) |
|
|
171
|
+
| Custom renderers/analyzers | Any `handle(event)` object is a renderer; any `analyze_failure(...)` object is an analyzer | [WIKI §17](WIKI.md#17-custom-renderers), [§18](WIKI.md#18-custom-failure-analyzers) |
|
|
172
|
+
| Utilities | `has_active_story()`, `stage(optional=True)` for library code that may run with or without a story | [WIKI §6](WIKI.md#6-stage--stage-context-managers) |
|
|
173
|
+
| `StoryRuntime.record_failure()` | Record a failure in saga/rollback flows without owning exception propagation | [WIKI §5](WIKI.md#5-story--the-story-context-manager) |
|
|
174
|
+
| Event schema | All seven event dataclasses and their fields | [WIKI §20](WIKI.md#20-event-reference) |
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## Examples
|
|
179
|
+
|
|
180
|
+
Every script under `examples/` is runnable as-is: `uv run python examples/<name>.py`.
|
|
181
|
+
|
|
182
|
+
**Core**
|
|
183
|
+
| Script | Demonstrates |
|
|
184
|
+
|---|---|
|
|
185
|
+
| `success.py` | Minimal `story()`/`stage()` API, no decorators, a success path |
|
|
186
|
+
| `basic.py` | `@runtime_narrative_story`/`@runtime_narrative_stage` decorators, a failure path |
|
|
187
|
+
| `basic_ollama.py` | Same failure path with `OllamaFailureAnalyzer` attached |
|
|
188
|
+
|
|
189
|
+
**Sub-stories and logging (newest features)**
|
|
190
|
+
| Script | Demonstrates |
|
|
191
|
+
|---|---|
|
|
192
|
+
| `substory_db_call.py` | Nested `story()` auto-linking as a sub-story (API call → DB call) |
|
|
193
|
+
| `logging_bridge.py` | `NarrativeLogHandler` folding `logging` calls into the story pipeline |
|
|
194
|
+
| `structured_log_routing.py` | `extra=` fields, `level_icons`, and `FilteredRenderer` per-story-family routing |
|
|
195
|
+
|
|
196
|
+
**Auto-instrumentation**
|
|
197
|
+
| Script | Demonstrates |
|
|
198
|
+
|---|---|
|
|
199
|
+
| `narrative_class.py` | `@narrative_class` and `@no_stage` |
|
|
200
|
+
| `instrument_module.py` | `instrument_module()` on an existing module |
|
|
201
|
+
| `auto_instrument.py` | `auto_instrument()` import-hook, zero call-site changes |
|
|
202
|
+
|
|
203
|
+
**Failure diagnostics and analysis**
|
|
204
|
+
| Script | Demonstrates |
|
|
205
|
+
|---|---|
|
|
206
|
+
| `diagnostics_config.py` | `FailureDiagnosticsConfig` — rich mode, redaction, production caps |
|
|
207
|
+
| `background_analysis.py` | `background_analysis=True` — non-blocking LLM analysis |
|
|
208
|
+
| `anthropic_analyzer.py` | `AnthropicFailureAnalyzer` + `DeduplicatingAnalyzer` |
|
|
209
|
+
|
|
210
|
+
**Renderers and observability**
|
|
211
|
+
| Script | Demonstrates |
|
|
212
|
+
|---|---|
|
|
213
|
+
| `html_report.py` | `HtmlReportRenderer` — self-contained HTML report |
|
|
214
|
+
| `sqlite_persistence.py` | `SqliteStoryRenderer` + the `runtime-narrative` CLI |
|
|
215
|
+
| `otel_tracing.py` | `OtelRenderer`, `OtelLogRenderer`, `OtelMetricsRenderer` |
|
|
216
|
+
| `alert_routing.py` | `AlertRoutingRenderer` — async webhook fan-out |
|
|
217
|
+
|
|
218
|
+
**Framework integrations and concurrency**
|
|
219
|
+
| Script | Demonstrates |
|
|
220
|
+
|---|---|
|
|
221
|
+
| `middleware_skip_if.py` | `RuntimeNarrativeMiddleware(skip_if=...)` for FastAPI/Starlette |
|
|
222
|
+
| `task_group.py` | `NarrativeTaskGroup` — concurrent asyncio tasks under one story |
|
|
223
|
+
| `fastapi_app/` | Full FastAPI demo app (`uv run python -m examples.fastapi_app.run`) |
|
|
224
|
+
|
|
225
|
+
**Testing and lifecycle utilities**
|
|
226
|
+
| Script | Demonstrates |
|
|
227
|
+
|---|---|
|
|
228
|
+
| `story_recorder.py` | `StoryRecorder` test assertion API |
|
|
229
|
+
| `dry_run_mode.py` | `dry_run=True` — verify wiring with no side effects |
|
|
230
|
+
| `optional_stage.py` | `has_active_story()` and `stage(optional=True)` |
|
|
231
|
+
| `saga_record_failure.py` | `StoryRuntime.record_failure()` in a saga/rollback flow |
|
|
232
|
+
| `stage_story_name.py` | `story_name` on `StageStarted`/`StageCompleted` |
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## Environment variables
|
|
237
|
+
|
|
238
|
+
| Variable | Values | Default | Effect |
|
|
239
|
+
|---|---|---|---|
|
|
240
|
+
| `RUNTIME_NARRATIVE_ENV` | `development`, `production` | `development` | Production caps tracebacks to 8 000 chars and forces lean mode |
|
|
241
|
+
| `RUNTIME_NARRATIVE_FAILURE_DIAGNOSTICS` | `lean`, `rich` | `lean` | `rich` captures local variable values at the failing frames. Invalid values raise `ValueError` at story construction. |
|
|
242
|
+
| `RUNTIME_NARRATIVE_ALLOW_RICH_IN_PRODUCTION` | `1`, `true` | off | Bypass production safeguard; allow rich diagnostics in production |
|
|
243
|
+
| `RUNTIME_NARRATIVE_MODEL` | model name string | — | Default model for `AnthropicFailureAnalyzer`; also used by example scripts for `OllamaFailureAnalyzer` / `LLMFailureAnalyzer` |
|
|
244
|
+
| `ANTHROPIC_API_KEY` | API key | — | Required by `AnthropicFailureAnalyzer`; read automatically if `api_key=` is not passed |
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## Python compatibility
|
|
249
|
+
|
|
250
|
+
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.
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
## More
|
|
255
|
+
|
|
256
|
+
- **[WIKI.md](WIKI.md)** — complete reference: every parameter, every renderer, every event field.
|
|
257
|
+
- **[CHANGELOG.md](CHANGELOG.md)** — what changed in each release.
|
|
258
|
+
- **[ROADMAP.md](ROADMAP.md)** — what's shipped and what's next.
|