Daniel StoyanovScribeLangGraph.js · TypeScript

LangGraph.js · TypeScript · Map-reduce graph with parallel section workers

Scribe - Fans out to read, fans in to write

A long-form research agent built as a map-reduce graph in LangGraph.js. It plans sections, researches each one in parallel, then reduces the findings into a single drafted report with the sources kept attached to the sentences they support.


What makes it special


Four things the graph does, each with the place in its source that does it, set in the margin.

  1. Plans before it reads

    Before any research starts, the planner splits the brief into six sections whose scopes do not overlap, and each worker is handed only its own. That boundary is enforced on retrieval, not just requested: a worker’s pool of passages is filtered to its own scope before anything is ranked.1

    1agents/scribe-research/src/tools.ts - planOutline · researchSection“a tag is claimed by at most one section, two workers cannot retrieve the same passage”

  2. Six readers at once, one honest barrier

    All six section workers are dispatched together and report back in whatever order they finish, not in outline order. The join waits for the slowest: fan-out buys throughput, not latency. Inside, each lane is marked with its time and its place in the arrival order, so the section that held the press is the last one back.2

    2agents/scribe-research/src/graph.ts - the research_fan_out node“Results then land in completion order, which is not outline order.”

  3. Every claim keeps its source

    One reducer turns the six findings into a single report and carries each citation key through verbatim, attached to the claim it supports; it is told to invent none. Inside, every sourced sentence carries a numbered marker and its sources are set as marginalia with the section that cites them, so a sentence with no marker plainly has nothing behind it.3

    3agents/scribe-research/src/tools.ts - draftReport“Carry every bracketed citation key through verbatim … Invent no new keys.”

  4. A failed worker ships as a named gap

    If a section’s source is unreachable, or nothing in scope clears the relevance floor, the worker fails rather than writing from memory. The graph still joins, and the report carries that section marked not researched, naming what is missing.4

    4agents/scribe-research/src/tools.ts - researchSection · SourceUnreachable“This section is empty and it is not being filled from memory.”

Inside the demo

3 briefs to try inside

  1. Brief iResearch & draft the reportPlan six scopes, research them concurrently, reduce to one drafted report.
  2. Brief iiShow me the fan-outThe map-reduce shape made visible - and a thin section declared in the prose.
  3. Brief iiiA section worker failsA source goes unreachable. The graph still joins; the gap ships named, not hidden.

Under the hood

the colophon

Stack & pattern

LangGraph.js · TypeScript
Map-reduce graph with parallel section workers

  • @langchain/langgraph
  • @langchain/core
  • @anthropic-ai/sdk

Why this architecture

Research is embarrassingly parallel per section but needs a real join to read as one document. A graph with a fan-out and a reducer expresses that directly, and keeps each worker context small enough to stay accurate. One slow section still gates the join.

Tools in the graph

  • plan_outlinerisk: safePlan report sections for a research brief.
  • research_sectionrisk: safeResearch one section and return findings with sources.
  • draft_reportrisk: safeReduce section findings into one drafted report.

The real source

  • Runnable implementationagents/scribe-research/
  • Entry pointagents/scribe-research/src/graph.ts
  • Written againstapi_documentation/langgraph-javascript.md

What to watch for

The fan-out. Six section workers are dispatched together and come back in whatever order they finish, and each lane shows its time and its place in the arrival order. The join waits for the slowest before one report is drafted: the map-reduce shape is visible in the UI, not just the code.