Skip to content

Agentic AI · LangGraph · Grounded critique

Atlas — Autonomous multi-agent research platform

A multi-agent research platform: parallel researchers, a critic grounded in the sources it retrieved, and a live view of the agent graph as it runs.

Core complete · Evals in progress
PLANRESEARCH × NSYNTHESIZECRITICplannerr1r2r3r4synthesizercriticTARGETED RETRYCITED≤ 2 ROUNDS · TOKEN BUDGET ENFORCEDSchematic
Problem
Single-pass LLM research is slow when sequential and unreliable when unchecked — and a minute-long agent run shouldn't die with the browser connection.
Approach
A LangGraph planner fans out parallel researchers; a critic checks the answer against retrieved sources and sends back only the faulty branches; runs execute on a worker and stream to the UI over resumable SSE.

Key results

5 parallel branches · 93.4 s if run back to back
23.3 s
submit to first visual feedback
1.14 s
backend · frontend tests, no network
168 · 34

01

Overview

Atlas is a multi-agent research platform. A planner decomposes a question into independent subtasks, researcher agents investigate them in parallel against live web search, a critic reviews the findings against the sources actually retrieved and can send specific branches back, and a synthesizer writes a cited answer. The whole run streams to the browser as it happens.

Built and verified against live providers through Phase 4. A graded regression eval suite (Phase 5) is in progress and Langfuse tracing (Phase 6) comes next, so no quality scores are reported yet. Atlas runs locally, with Postgres and Redis in Docker Compose.

02

Problem & motivation

I built Atlas to practice the engineering that separates an agent demo from an agent system: concurrency that is genuinely concurrent, a quality gate that can reject work, cost limits that are enforced rather than hoped for, and runs that survive a dropped connection.

03

My role

  • Sole engineer — architecture, backend, frontend and verification.
  • Documented every phase: design decisions, defects found, and how each fix was verified against live providers.

04

System architecture

  1. Stage 1:Plan

    • Planner

      Splits the question into up to five self-contained subtasks

  2. Stage 2:Research

    • Researcher × N

      Parallel branches via LangGraph Send, Tavily search

    • Grounded citations

      Sources kept per subtask id

  3. Stage 3:Synthesize

    • Synthesizer

      Cited answer, with a concision rule and token backstop

  4. Stage 4:Critique

    • Critic

      Structured verdict per subtask, judged against retrieved sources

    • Budget checkpoint

      Max 2 rounds within a 100k-token budget

    Loops back to stage 02 (Research). Faulted branches go back to their researchers; a write-up-only fault goes back to the synthesizer.

  5. Stage 5:Runtime

    • FastAPI + arq worker

      Runs belong to the worker, not the request

    • Redis Stream → SSE

      Last-Event-ID replay after reconnect

    • Postgres checkpointer

      LangGraph state persisted per thread

The API enqueues and relays; an arq worker executes the LangGraph run and writes events to a Redis Stream that the UI follows over SSE.

05

Technical implementation

  1. Fan-out that is genuinely concurrent

    The planner emits self-contained subtasks and LangGraph's Send dispatches one researcher per subtask. On a live five-branch run the slowest branch took 23.3 s against 93.4 s summed — the fan-out costs the slowest branch, not the sum.

  2. A critic that can say no

    The critic returns a structured verdict per subtask, judged against the sources actually retrieved rather than the model's own knowledge. Routing is derived from the verdict: accept, re-research only the faulted branches, or rewrite the synthesis alone.

  3. Cost enforced, not hoped for

    A per-run token counter in Redis is checked at the critic — the only node from which a new iteration can begin. If the budget runs out, the answer ships with an explicit “unverified” note instead of silently looping.

  4. Runs that outlive the connection

    The API enqueues and relays; an arq worker executes. Nine typed event kinds go to a Redis Stream and an SSE endpoint relays them with Last-Event-ID replay. Killing the API mid-run was tested: the run completed with nothing listening, and the UI converged to the identical final state on restart.

  5. A graph view that cannot drift

    The frontend draws topology from an endpoint derived from the compiled LangGraph. The critic → researcher retry edge is declared, so it is drawn dormant from the first frame and lights up when it fires. A test pins the node and edge sets.

06

Engineering decisions & challenges

  • Targeted retry over full re-runs

    Re-running the whole graph would cost ~72 s and ~31k tokens per iteration, and re-planning produces a different decomposition, so per-subtask feedback has nothing to attach to. Targeted retry costs the slowest faulted branch plus one synthesis.

  • Upsert results, don't append them

    Once a branch can be retried, appending produces two contradictory results for one subtask. Results are upserted by subtask id — proven with a negative-control test that fails under the old reducer.

  • arq over Celery

    Atlas is async end to end. Celery would mean a synchronous worker spinning up an event loop to call an async graph, plus a second broker; arq is async-native and uses the Redis already in the stack.

  • A fixed pipeline before ReAct

    Each subtask arrives self-contained, so researchers use bounded search rather than an open-ended tool loop. Swapping in a ReAct researcher changes nothing about the graph shape or the tests — a decision to make with eval data, not speculation.

07

Evaluation & results

Measured on local runs against live providers, as documented in the repository. These are latency and cost measurements, not answer-quality scores.

five parallel branches93.4 s if run back to back
23.3 s
typical full runDominated by provider token throughput
67–92 s
SSE relay overheadQueue pickup ~1.0 s
0.18 s
submit to graph on screenTime to first visual feedback
1.14 s
synthesis after a concision rule4,392 → 2,752 output tokens
40.5 → 27.1 s
backend · frontend testsNo network calls, no spend
168 · 34

Where the time goes

Running research branches concurrently costs the slowest branch, not the sum; a concision rule cut synthesis time by a third.

Five research branchesWall-clock seconds · lower is better
  • Run in parallelSlowest branch23.3 s
  • Back to backSum of all five branches93.4 s
Show data table
ItemValue
Run in parallel23.3 s (Slowest branch)
Back to back93.4 s (Sum of all five branches)
Synthesis stepSeconds · lower is better
  • Before concision rule4,392 output tokens40.5 s
  • After concision rule2,752 output tokens27.1 s
Show data table
ItemValue
Before concision rule40.5 s (4,392 output tokens)
After concision rule27.1 s (2,752 output tokens)

Source: Atlas README, “Measured” (local runs against live providers)

  • In progress: a graded question set with regression runs against known-good answers. Langfuse tracing follows. Quality numbers will be published once a baseline exists.

08

Lessons learned

  1. A fallback that always fires looks like a working system

    An explicit temperature the primary model rejected sent every call to the fallback while every answer still looked fine. A regression test now asserts the primary path works, not just the fallback.

  2. Live verification finds what unit tests can't

    Every Phase 1 defect — including a search tool that reported 401 errors in-band as “no results” — was a failure wearing the costume of success, and none was caught by tests.

  3. Declare structure instead of inferring it

    On a clean run the critic never retries, so a graph inferred from events would hide the most interesting edge exactly when the system works well.

09

Technologies used

  • LangGraph
  • Claude Sonnet 5
  • GPT-4o (failover)
  • FastAPI
  • arq
  • Redis
  • PostgreSQL
  • Next.js
  • React Flow
  • Tavily
  • Docker Compose