▶ runtime-narrative

Background Analysis

By default, when a failure_analyzer is configured, story.__aexit__ calls the analyzer and blocks until it returns before emitting FailureOccurred. For remote LLM APIs this can add seconds of latency to the failure path.

Setting background_analysis=True changes this to a non-blocking two-event sequence:

  1. FailureOccurred is emitted immediately with llm_analysis=None.
  2. The story emits StoryCompleted and the context exits — the HTTP response (or story block) returns.
  3. An asyncio.Task running the analyzer completes later and emits LLMAnalysisReady.

Requirements

background_analysis=True only works with async with story(...). The asyncio event loop must remain running after the story exits for the background task to complete. In a web server context (FastAPI, Django ASGI) this is always the case.

Code example

python
import asyncio
from runtime_narrative import story, stage
from runtime_narrative.analyzers.ollama import OllamaFailureAnalyzer
from runtime_narrative.renderer.console import ConsoleRenderer

analyzer = OllamaFailureAnalyzer(model="llama3", timeout_seconds=30.0)

async def main():
    try:
        async with story(
            "Checkout",
            renderers=[ConsoleRenderer()],
            failure_analyzer=analyzer,
            background_analysis=True,  # FailureOccurred fires immediately
        ):
            async with stage("Charge Card"):
                raise ValueError("card number invalid")
    except ValueError:
        pass
    # FailureOccurred and StoryCompleted have already fired.
    # Give the background task time to complete.
    await asyncio.sleep(35)

asyncio.run(main())

Observing the two-event sequence

python
events_log = []

class SequenceRecorder:
    async def handle(self, event):
        events_log.append(type(event).__name__)

# After a failure with background_analysis=True, events arrive as:
# StoryStarted → StageStarted → FailureOccurred → StoryCompleted → LLMAnalysisReady

When background_analysis=True but no failure_analyzer is set, the story behaves identically to background_analysis=False — no LLMAnalysisReady is ever emitted.

Next step

Continue to Renderers to see how LLMAnalysisReady and the rest of the event stream are consumed and displayed.