ADR 0013 — LangGraph for background orchestration — and nowhere else
Context
The hourly hunt was a make-chained sequence of CLI invocations with -
prefixes swallowing errors: no per-stage retry, no structured run state,
failures visible only in log files. Kelyn wants the system prod-like and
asked for LangGraph explicitly.
Blunt assessment first, because it shaped the scope: karani’s hunt is a LINEAR, IDEMPOTENT pipeline whose durable state machine already lives in Postgres. LangGraph does not make qualification smarter, and re-platforming the thin adapters (CLI/MCP/Slack) onto it would be architecture theater. What LangGraph legitimately buys at the orchestration layer: explicit graph structure, per-node retry and error containment, one structured run-state object instead of four exit codes, failure alerting, and a growth path to branching flows (deep-research routing, human-interrupt nodes) without re-platforming later.
Decision
orchestration/ — a LangGraph StateGraph for the hunt pass:
START -> ingest -> qualify -> autopilot -> notion -> report -> END
Scope rules:
- Nodes wrap, never re-implement. Each node calls the same runner the CLI and MCP server call. The graph is a fourth thin adapter.
- Failures are contained per node (one retry, then the error lands
in state); later nodes still run, and
reportalerts Slack when anything failed. The best-effort semantics of the make chain are preserved, but now observable and alertable. - No checkpointer, deliberately. Every node is idempotent against the system of record (upsert dedup, resume-hash qualification, drafted_at budget), so re-running a crashed pass from START is safe and loses nothing — a checkpointer would add a psycopg dependency and schema for zero recovery value. Revisit the moment the graph gains branching or human-interrupt nodes, where mid-run state is real.
- Optional dependency (
uv sync --extra orchestrator).make hourlyruns the graph and falls back to the legacy chain (hourly-legacy) when langgraph isn’t installed — the scheduler never breaks over a missing extra. - LangGraph stays out of the pre-filter (deterministic, rule 4.9), the qualification agent loop (a working ~100-line tool loop; porting it is churn without capability), and the interface adapters.
Consequences
- Positive: one structured pass with retries, contained failures,
Slack alerting, and a mermaid-renderable topology
(
python -m orchestration --show). - Positive: the future branching work (route top candidates through deep research before drafting; pause a pass on a human interrupt) now has a home that supports it natively.
- Negative: a heavyweight dependency tree (langchain-core et al.) for what is today a linear chain. Contained: optional extra, one package, fallback preserved.
- Negative: two orchestration paths (graph + legacy chain) until the legacy one is retired after a few weeks of scheduled graph runs.