Synapse
An AI research pipeline with three agents — Scout, Scribe, and Critic — that turns a topic into a structured report with inline citations, plus a per-claim verdict on whether the sources actually back it up.

Overview
You give Synapse a topic and it gives you back a cited, fact-checked report. Scout breaks the question down, searches the web, and scores each source for credibility. Scribe drafts the report, wrapping every verifiable claim in an addressable span footnoted to its sources. Critic then re-reads each claim against those sources and marks it supported, partially supported, unsupported, or contradicted.
Progress streams live over WebSockets — sub-questions, sources, sections, and verdicts show up as they're produced — and finished reports export to Markdown or PDF.
The problem
LLMs write confident prose and invent citations. I wanted a system where every sentence that makes a claim can be traced to a real source, and where a second pass explicitly checks whether that source says what the draft claims it says. The reader should get to see the doubt, not just the fluency.
Approach & architecture
The agents form a LangGraph state machine (Scout → Scribe → Critic) driven by a Python/FastAPI backend, with taskiq workers, PostgreSQL, and Redis pub/sub bridging worker events to the browser over WebSockets. The frontend is React 19, with fully-typed API and WebSocket schemas generated from the backend's OpenAPI spec.
Challenges & tradeoffs
Structured output from LLMs fails in predictable ways, so validation happens in layers: Pydantic schemas first, then structural checks for claim-ID sequencing and footnote placement, then a single conversation-aware retry where the model's bad answer becomes an edit target instead of a restart.
Anything the model could drop or invent (the source list, IDs, timestamps) is attached server-side, never generated. Credibility scoring is a hybrid: curated domain priors anchor the known outlets, and an LLM rating handles the unknown ones. Each node catches its own failures, so one bad section can't sink a whole job.
What I'd change
Scribe emits whole sections rather than tokens, so there's no typewriter effect yet, and there's no LangGraph checkpointing — if a worker crashes, the job restarts. I deferred both on purpose and would revisit them first.