Quick Start
Wrap a block of code in story(), break it into stage() steps, and let ConsoleRenderer narrate what happened — no configuration required.
Context manager API — success path
The most direct way to use runtime-narrative: open a story(), then nest stage() blocks inside it for each step you want tracked.
from runtime_narrative import story, stage
with story("Import Customers", total_stages=3):
with stage("Load CSV"):
rows = ["alice", "bob", "carol"]
with stage("Validate Data"):
if not rows:
raise ValueError("No rows found")
with stage("Insert Records"):
print(f"Inserted {len(rows)} records")
The total_stages=3 hint lets renderers show accurate progress percentages. Without it, the runtime infers total stages from how many stages have been registered so far, which means percentages are always 100% at completion rather than showing intermediate progress.
ConsoleRenderer is the default renderer and needs no configuration. On failure it prints the exact frame, a source snippet, the exception chain, and a compressed stack summary. When nothing fails, it narrates each stage as it starts and completes.
Decorator API — failure path
Prefer keeping the story/stage boundary next to the function definition rather than at the call site? Use @runtime_narrative_story and @runtime_narrative_stage instead. This example lets an exception in the last stage propagate, triggering FailureOccurred.
from runtime_narrative import runtime_narrative_story, runtime_narrative_stage
@runtime_narrative_stage("Load CSV")
def load_csv() -> list[str]:
return ["alice", "bob"]
@runtime_narrative_stage("Validate Data")
def validate(rows: list[str]) -> None:
if not rows:
raise ValueError("No rows found")
@runtime_narrative_stage("Insert Records")
def insert(rows: list[str]) -> None:
raise ValueError("duplicate customer id") # triggers FailureOccurred
@runtime_narrative_story("Import Customers")
def run() -> None:
rows = load_csv()
validate(rows)
insert(rows)
try:
run()
except Exception:
pass
Both APIs produce identical event streams
The context-manager form and the decorator form emit the same StoryStarted, StageStarted, StageCompleted, FailureOccurred, and StoryCompleted events. Choose whichever fits your call site — decorators are convenient when the instrumented functions live in different modules.
Async context manager
story and stage are dual sync/async: async with works identically for async code, using the same names.
import asyncio
from runtime_narrative import story, stage
async def main():
async with story("Async Pipeline"):
async with stage("Fetch Data"):
await asyncio.sleep(0.1)
async with stage("Process"):
pass
asyncio.run(main())
Next step
Read Core Concepts for the mental model behind stories, stages, events, and renderers, or jump straight to story() for the full constructor reference.