Event Reference
All events are plain dataclasses and are part of the stable public API. Every renderer's handle(event) receives one of these seven types.
from runtime_narrative import (
StoryStarted, StageStarted, StageCompleted,
FailureOccurred, StoryCompleted, LLMAnalysisReady, LogRecorded,
Event, # Union[StoryStarted, StageStarted, StageCompleted, FailureOccurred, StoryCompleted, LLMAnalysisReady, LogRecorded]
)
They are emitted in a fixed sequence within each story:
StoryStarted → (StageStarted → StageCompleted)* → [FailureOccurred] → StoryCompleted
↓
[LLMAnalysisReady] (background_analysis only)
StoryStarted
Emitted when the story context is entered.
| Field | Type | Description |
|---|---|---|
story_id | str | UUID for this story execution |
story_name | str | The name passed to story(name) |
timestamp | datetime | Wall-clock time at story start |
parent_story_id | str | None | story_id of the enclosing story, or None for a root story |
root_story_id | str | story_id of the top-most ancestor (itself, if this is a root story) |
StageStarted
Emitted when a stage() context is entered.
| Field | Type | Description |
|---|---|---|
story_id | str | UUID of the enclosing story |
story_name | str | Name of the enclosing story |
stage_name | str | The name passed to stage(name) |
timestamp | datetime | Wall-clock time at stage start |
stage_index | int | Zero-based position of this stage in the story (default 0) |
parent_stage_name | str | None | Name of the enclosing stage, or None for top-level stages |
root_story_id | str | story_id of the top-most ancestor story (lets a renderer group stages by call tree, not just by story) |
StageCompleted
Emitted when a stage exits without exception (or in dry_run mode even if it raised).
| Field | Type | Description |
|---|---|---|
story_id | str | UUID of the enclosing story |
story_name | str | Name of the enclosing story |
stage_name | str | Stage name |
timestamp | datetime | Wall-clock time at stage exit |
duration_seconds | float | Elapsed time from stage enter to stage exit |
stage_index | int | Zero-based position (default 0) |
parent_stage_name | str | None | Name of the enclosing stage, or None |
root_story_id | str | story_id of the top-most ancestor story |
story_name on stage events means a renderer can filter by story without tracking a story_id → story_name side table populated from StoryStarted/StoryCompleted.
FailureOccurred
Emitted when an exception propagates out of the story block. Contains the full enriched failure payload.
| Field | Type | Description |
|---|---|---|
story_id | str | UUID of the story |
story_name | str | Story name |
stage_name | str | Name of the stage where the failure occurred |
error_type | str | Exception class name, e.g. "ValueError" |
error_message | str | str(exc) |
filename | str | Source file of the primary frame |
lineno | int | Line number of the primary frame |
function | str | Function name of the primary frame |
source_line | str | Source text of the failing line |
exception_chain | str | Human-readable chain of chained exceptions (A <- B <- C), up to 5 levels. Follows __cause__ (raise X from Y) unconditionally; follows __context__ (implicit chaining) only when the raiser didn't suppress it with raise X from None — matching Python's own traceback output, so this never surfaces an exception the raiser deliberately hid |
exact_cause | str | Inferred one-line cause description. For TypeError: unsupported operand type(s) for OP: 'A' and 'B' in rich mode, this names the specific operands instead of restating the exception message — e.g. `promo` = {'buy': 1, ...} (dict) and `item['price']` = 29.99 (float) can't be combined with '*' - float and dict are incompatible operand types. A bare variable name or a one-level constant-key subscript (item["price"]) is resolved against the already-captured frame locals (same redaction rules as the locals section — a secret-looking name shows (type, redacted) instead of its value); anything more complex (function calls, attribute chains) shows the expression text without a value. Falls back to the generic one-line message in lean mode or for any other error shape |
llm_analysis | str | None | Analyzer output; None if no analyzer or background_analysis=True |
stage_timeline | str | Text summary of all stages and their states |
progress_percent | int | Percentage of stages completed at failure time |
completed_stages | int | Count of StageCompleted events before this failure |
total_stages | int | Total stages seen (or declared via total_stages=) |
timestamp | datetime | Wall-clock time of failure |
traceback_text | str | Full formatted traceback (may be truncated in production) |
diagnostics_mode | str | "lean" or "rich" (default "lean") |
primary_frame_reason | str | Why this frame was chosen: "leaf", "innermost_app", or "innermost_non_stdlib" |
stack_frames | list[dict] | Structured list of all frames; each dict has index, filename, lineno, function, kind, line, is_primary |
source_snippet | str | None | Multi-line source context around the primary frame (±snippet_context_lines lines) |
compressed_stack_summary | str | One-line summary, e.g. "2 app frame(s), 5 other/hidden in full stack (7 total)" |
hidden_frame_count | int | Number of non-app frames in the full stack |
traceback_truncated | bool | True if traceback_text was capped |
locals_by_frame | dict | None | Local variables per frame (rich mode only); keys are "frame_0", "frame_1", etc. |
redaction_removed_keys | int | Count of local variable keys that were redacted |
parent_story_id | str | None | story_id of the enclosing story, or None for a root story |
root_story_id | str | story_id of the top-most ancestor story |
StoryCompleted
Emitted unconditionally at story exit, after any FailureOccurred.
| Field | Type | Description |
|---|---|---|
story_id | str | UUID of the story |
story_name | str | Story name |
success | bool | True if the story exited without exception |
progress_percent | int | Final percentage of stages completed |
completed_stages | int | Final count of completed stages |
total_stages | int | Total stages seen or declared |
timestamp | datetime | Wall-clock time at story exit |
duration_seconds | float | Elapsed time from story enter to story exit |
parent_story_id | str | None | story_id of the enclosing story, or None for a root story |
root_story_id | str | story_id of the top-most ancestor story |
outcome | str | Short result label set via StoryRuntime.set_outcome() (e.g. "200 OK"); "" when unset. The HTTP middlewares set it automatically from the response status (http_outcome(status_code)), and ConsoleRenderer then renders a self-contained ended line: Story ended: GET /api/call - SUCCESS (200 OK, 0.023s) — making a separate server access log (e.g. uvicorn's) redundant, so it can be disabled with access_log=False. |
LLMAnalysisReady
Emitted after StoryCompleted only when background_analysis=True and the analyzer returned a non-None result.
| Field | Type | Description |
|---|---|---|
story_id | str | UUID of the story that failed |
story_name | str | Story name |
stage_name | str | Stage where the failure occurred |
llm_analysis | str | Analyzer output text |
timestamp | datetime | Wall-clock time when the analysis completed |
LogRecorded
Emitted by NarrativeLogHandler when it captures a stdlib logging record while a story is active.
| Field | Type | Description |
|---|---|---|
story_id | str | UUID of the story active when the record was captured |
story_name | str | Name of that story |
root_story_id | str | story_id of the top-most ancestor story |
stage_name | str | Name of the active stage, or "" if none |
level | str | record.levelname, e.g. "WARNING" |
logger_name | str | record.name |
message | str | record.getMessage() |
timestamp | datetime | record.created as a datetime |
exc_text | str | None | Formatted exception text if the record carried exc_info |
fields | dict[str, Any] | Caller-supplied extra={...} fields from the logging call, minus standard LogRecord attributes |
Next step
Browse the Examples page for a runnable script demonstrating almost every event, renderer, and diagnostics feature described across these docs.