All projects

Synapse

An AI research pipeline: three agents, one report. Scout researches a topic, Scribe writes it up with inline citations, and Critic goes back through every claim to check whether the sources actually say what the draft says they say.

  • React
  • Python
  • LangGraph
Synapse — screenshot
Synapse

What it is

You hand Synapse a topic. Scout decomposes it into sub-questions, 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 exact sources and labels it supported, partially supported, unsupported, or contradicted.

The whole run streams to the browser over WebSockets, so you watch sub-questions, sources, sections, and verdicts appear as they're produced. Finished reports export to Markdown or PDF.

Why bother

Because language models cite things that don't exist, in prose confident enough that you don't check. I didn't want a model that sounds right. I wanted a pipeline where sounding right isn't enough, where a second model has to go find the source and read it, and where a shaky claim gets flagged in the final document rather than polished away.

Under the hood

The three agents form a LangGraph state machine driven by a FastAPI backend, with taskiq workers doing the actual runs and Redis pub/sub carrying worker events out to the browser. Storage is PostgreSQL. The React 19 frontend consumes API and WebSocket types generated straight from the backend's OpenAPI spec, so the two sides can't silently drift apart.

What kept breaking

Structured output from a model fails constantly, and always in the same few ways, so validation is layered: Pydantic first, then structural checks on claim-ID sequencing and footnote placement, and finally one conversation-aware retry where the model is shown its own broken output and asked to fix it rather than start over.

A rule I arrived at early: if the model could plausibly drop something or invent it, the server attaches it instead. Source lists, IDs, timestamps all come from code, never from generation. Credibility scoring is half curated (domain priors for outlets I know) and half model-judged for the long tail. Each graph node catches its own failures too, so one bad section degrades the report instead of killing the job.

What I'd change

Scribe emits whole sections at a time, so there's no token-by-token typewriter effect. There's also no LangGraph checkpointing yet, which means a crashed worker restarts its job from zero. Both were conscious cuts to get it shipped.