Five-pass research pipeline for a small reasoning model
nwhitehouse built a full multi-step research orchestrator on top of the Olava-001 vLLM endpoint - query expansion, parallel search fan-out, triage, per-result extraction, and streaming synthesis, all coordinated by a 302-line pipeline with hard caps of 25 Olava calls and 45 seconds wall-clock. Tested end-to-end against real CourtListener data, it produced a 2KB synthesis with inline citations where the single-pass path emitted only whitespace.
The implementation lives in eight new files under backend/src/lib/research/. Each pass is its own module: queryExpander.ts turns the user's question into 3-6 specialised queries; searchFanOut.ts runs legal and web searches in parallel and deduplicates by URL; triage.ts picks the top-N most relevant results; extractor.ts fires parallel Olava calls to produce 2-3 sentence tailored summaries for each result; synthesizer.ts streams the final markdown answer. budget.ts enforces the call and wall-clock caps and reports which one tripped - callers get a research.cap_hit SSE event and whatever synthesis has completed so far rather than an abort.
chat.ts auto-routes to the orchestrator whenever sources.legal is non-empty or sources.web is true. That trigger is fork-specific to the nwhitehouse UI source-selector. The shape of the pipeline is more general and separates cleanly by concern.
On the frontend, a new onit-status-icon.tsx Drift Grid ripple loader cross-fades to the Olava logo when streaming stops. AssistantMessage.tsx gains a ResearchStepBlock showing dot + label + metadata per step ("top 5 selected"), and ReferenceBlock renders results numbered and indented under "Ranked results" with a continuous vertical connector. Two bug fixes landed in the same commit: deduplication of research_step events by key on reload (so a refresh doesn't show both running and done states for the same step), and a guard dropping empty-whitespace content events that were splitting the UI into duplicate "Completed in N steps" cards.
The Olava adapter was extended to forward delta.reasoning and delta.reasoning_content via a new onReasoningDelta callback so the UI can show a live "Thinking..." indicator during long model think-times. The reasoning-forwarding note in the diff is worth reading: a prior security commit had dropped these fields from output because of PII concerns about backend logs, but nwhitehouse's read is that surfacing them to the user's own UI is within scope.
Spotted something wrong? Or know the PR text has fresher detail than the writeup above?