LangGraph as the cyclic-graph substrate
We will build the QA pipeline (see ADR-0001) as an explicit LangGraph graph
rather than hand-rolling the loop in the existing audiobook_creator
orchestration. LangGraph is the industry-standard substrate for cyclic, stateful
agent graphs in Python, and it makes the back-edges first-class, legible objects
in the code (add_conditional_edges pointing backward), which is the point: the
DAG→cyclic shift becomes inspectable rather than an emergent property of
control flow.
Status
accepted
Considered options
Hand-rolled
whileloops in the current orchestrator. No new dependency, but the cyclic graph lives implicitly in control flow — harder to reason about as cycles multiply, and the topology stops being inspectable.LangGraph (chosen). A dependency and some lock-in, in exchange for the graph topology being a first-class, inspectable artifact and for using the substrate the wider ecosystem has standardised on for cyclic agent graphs.
Consequences
Adds a LangGraph dependency to a project that is currently plain Python + API clients.
Pipeline state (per-chapter retry budgets, best-so-far WER, flags for the report) becomes an explicit shared state object — the thing the graph carries between nodes and across back-edges.
Skeleton scope (deferred behaviours)
The skeleton expresses the legacy linear orchestration as an acyclic graph and
deliberately defers anything cyclic. Two legacy behaviours map onto the
Quality Checks defined in CONTEXT.md at the repo root and are therefore
deferred to the cyclic-detector PRs, not reimplemented inside the skeleton:
Per-chapter duration check (
check_chapter_during_synthesis,verification_integration.py): maps to the Fidelity check plus the short-LLM-output signal of the Script-validity check. The skeleton would reimplement it; the cyclic detectors replace it.Post-M4B audiobook verification (
verify_complete_audiobook,verify.py): is the seed of the Coverage check (CONTEXT.md says so explicitly). Same reasoning.
Three legacy behaviours are not Quality Checks and are wired into the skeleton as ordinary infrastructure rather than deferred:
TTS preflight (
_verify_tts_provider_available) — runs inrun_graphbefore invoking the compiled graph, conditioned oncreator.mode != "process".TOC preview + confirm prompt — encapsulated in
confirm_node, which sits betweenreadandscript_genin thefullandprocessgraphs. An abort returns{"chapters": []}, and downstream nodes naturally no-op on empty state (no conditional edges needed).Process / synthesize modes —
build_graph(mode)dispatches to three topologies:full,process(read → confirm → script_gen → report), andsynthesize(load_scripts → synthesize → assemble → report). The--graphflag therefore honours--process-onlyand--synthesize-onlyidentically to the legacy orchestrator.