Research mode — cyberine research (plan -> search -> verify -> synthesize)
For questions that need current web evidence — comparisons, market scans, "what is the state of
X in 2026?", trade-off write-ups — use cyberine research. The orchestrator decomposes the
question into angle sub-questions, fans them out to parallel loop workers backed by the Perplexity
sonar-pro native web-search tool, adversarially verifies every claim, then synthesizes the
survivors into a single cited markdown Report on disk. No code is written, no working tree is
touched: research is read-only and writes only the Report file.
When to use research vs compete / swarm. Pick by intended output, not task size:
compete | swarm | research | |
|---|---|---|---|
| Intended output | one winning code diff | merged code diff across many chunks | cited markdown Report (no diff) |
| Best for | single design choice with multiple implementations to compare | mass multi-file scope decomposed into disjoint chunks | open-ended "what / how / why" questions that need fresh web evidence |
| Topology | N writers race, DiffJudge picks ONE | plan -> N disjoint-chunk writers -> SwarmMergeJudge merges all | plan -> N angle workers (web_search + fetch_url) -> per-claim skeptics -> reducer synthesizer |
| Working tree | competes in isolated worktrees, winner applied via cyberine apply | merged diff applied via cyberine apply | never touched — Report written to disk |
| Command | cyberine run "..." | cyberine run --mode swarm "..." | cyberine research "..." |
The flow (plan -> search -> verify -> synthesize). The workflow is
src/engine/researchWorkflow.ts:
- plan — fans out to the same thinking fleet
implement-task's design step uses (DESIGN_FLEET: the strongest ENABLED workers, widened one strength band at a time until at least two compete, so a retired or disabled id never leaves the step with too few candidates) and rewrites the raw question into one sharp, self-contained, answerable formulation (core question + 2-3 sentences of scope). A standard text-judge panel picks the best rewrite as theplantemplate every downstream angle worker inherits — so each worker reads the refined question, not the raw input. The run records which workers were picked and which subscription, if any, that bills. - search — N parallel loop workers (N from the
--depthpreset:quick= 3,deep= 5,max= 7) each receive ONE angle sub-question from the multi-modal sweep (by-entity, by-time, by-source-type, by-perspective, by-counter, by-domain-specific, by-edge-case — first N taken in that order). Each worker usesweb_search(Perplexity sonar-pro) to find authoritative sources, thenfetch_url(SSRF-safe HTML-to-text) to read the most relevant page for a concrete quote, and returns a structuredFinding(claim + cited sources + confidence). The step'swinnerOutputis{ findings: [...] }. - verify — every Finding runs through
--depth-tied independent skeptics (quick= 2,deep= 3,max= 5) via in-processClaimVerifier. Default-refuted-if-unsupported: a claim survives unless a strict majority of its skeptics vote to refute. Survivors land in{ verified: [...], dropped: [...], totalSkepticCalls }for the next step. - synthesize —
SynthesizerReducermakes ONE LLM call combining every verified claim into a single cited markdown Report (sections + inline[N]citation markers + numbered Sources list). The reducer has a no-LLM fallback (one section listing every verified claim verbatim) so a missing key never silently drops the research.winnerOutputis the{ report, markdown, verifiedCount, droppedCount, degradationNote? }envelope;markdownis written to--outas the final Report.
The command. --depth is the effort knob; --out overrides the default report path:
# Default (deep): 5 angles, 3 skeptics per claim
cyberine research "What are the trade-offs between SQLite and DuckDB for embedded analytics?"
# Quick: 3 angles, 2 skeptics — fast, narrow coverage
cyberine research --depth quick "How does WebGPU adoption compare across browsers in 2026?"
# Max: 7 angles, 5 skeptics — pays the verify cost for ambiguous / open-ended questions
cyberine research --depth max "Compare LiteLLM vs Portkey for production LLM routing"
# Explicit output path
cyberine research --depth deep --out ./reports/duckdb-vs-sqlite.md \
"Trade-offs between SQLite and DuckDB for embedded analytics?"Default --out is <cwd>/.local/research/<slug>.md (slug = kebab-case of the question, capped at
60 chars). The Report is the rendered markdown only — HTML rendering is left to a downstream
viewer. The run also lands in .local/runs/<runId>/ the same way every other dispatch does, so
cyberine agents shows it from any directory and cyberine digest <runId> can re-inspect the
synthesized envelope. The CLI prints runId, state, depth preset, verified / dropped claim
counts, and both the report path and the run dir on completion.
Search backend (kx1bv): a Claude seat by default, Perplexity selectable. web_search returns
one shape whatever runs it — {query, summary, sources[{url, title}]}, the URLs the verify step
needs — and picks its backend on every call:
| Backend | What runs | Spends |
|---|---|---|
seat | claude -p on a Claude subscription seat with only the WebSearch tool allowed and a --json-schema for the summary + sources; token passed by file descriptor like every seat-pinned worker | one claude -p turn of that seat's quota per search |
perplexity | Perplexity's OpenAI-compatible chat completions with a sonar-* model (search_results / citations become the sources) | one Perplexity request per search |
auto (the default) takes seat when one is usable and falls back to perplexity when no seat
is and PERPLEXITY_API_KEY is set. A usable seat is, in order: the seat CYBERINE_WEB_SEARCH_SEAT
names; otherwise every enabled claude seat in fleet.json whose tokenFile exists, rotated per
call (seat-rotation.json, profile web-search); otherwise the logged-in claude CLI itself.
cyberine doctor prints the choice under Web search::
cyberine doctor | grep -A1 '^Web search:'
CYBERINE_WEB_SEARCH_BACKEND=perplexity cyberine research "<q>"
CYBERINE_WEB_SEARCH_SEAT=<seat-name> cyberine research "<q>"| Env | config.json key | Default | Used by |
|---|---|---|---|
CYBERINE_WEB_SEARCH_BACKEND | research.searchBackend | auto | auto, seat or perplexity; any other value makes web_search return a "not configured" note naming the valid ones |
CYBERINE_WEB_SEARCH_SEAT | research.searchSeat (global config only) | (empty) | pins the seat backend to one fleet.json seat; not settable from a project .cyberine/config.json, because it names which account pays |
CYBERINE_WEB_SEARCH_MODEL | research.searchModel | haiku | the --model of the seat backend's claude -p turn |
CYBERINE_WEB_SEARCH_TIMEOUT_MS | research.searchTimeoutMs | 180000 | per-search timeout of the seat backend |
PERPLEXITY_API_KEY | perplexity.apiKey | (empty) | the perplexity backend; also enables the loop-perplexity-build worker |
PERPLEXITY_MODEL | perplexity.model | sonar-pro | model id sent to the Perplexity endpoint. sonar-pro has native web search; override only to A/B another sonar variant |
PERPLEXITY_BASE_URL | perplexity.baseUrl | https://api.perplexity.ai | endpoint base. Rarely overridden |
fetch_url needs no key (SSRF-safe stripped-to-text fetch, 50k char cap, 15s timeout, 5 redirect
hops max — private / loopback / link-local / cloud-metadata addresses are refused). The planner +
verifier + synthesizer reuse the standard DEEPSEEK_API_KEY / OPENROUTER_API_KEY the rest of
the fleet uses.
Depth presets. The CLI flag maps to two env vars the workflow reads at module load:
--depth | CYBERINE_DEPTH | CYBERINE_SKEPTICS | Angles | Notes |
|---|---|---|---|---|
quick | 1 | 2 | 3 (by-entity, by-time, by-source-type) | fast; pick when the question is narrow |
deep (default) | 2 | 3 | 5 (+ by-perspective, by-counter) | recommended for most questions |
max | 3 | 5 | 7 (+ by-domain-specific, by-edge-case) | thorough; pays the verify cost |
CYBERINE_DEPTH = N requires at least N loop adapters available in the fleet — smaller fleets
must lower it (the engine throws a clear count-mismatch error, same contract as
sampleSearchFanOutWorkflow.ts). CYBERINE_SKEPTICS is the per-claim verifier count; override
between presets if you want non-standard verification cost.
Run the workflow directly (no cyberine research parent) for dogfooding:
CYBERINE_TASK="What are the trade-offs between SQLite and DuckDB for embedded analytics?" \
CYBERINE_DEPTH=2 CYBERINE_SKEPTICS=3 \
npm run wf -- src/engine/researchWorkflow.tsFailure modes. When web_search has no usable backend (no seat, no logged-in claude, no
PERPLEXITY_API_KEY), the search step still runs — web_search returns the "not configured" note, the verifier drops the
unverifiable findings, and the synthesizer either emits an empty-but-honest Report or surfaces a
degradationNote on the envelope. When the synthesizer LLM is unreachable, the reducer's no-LLM
fallback lists every verified claim verbatim. When no markdown was produced at all, the CLI
exits non-zero and points at cyberine digest <runId> for inspection.
Also reachable from other surfaces (kz5sr, khh1q). cyberine research is the foreground CLI,
but the same workflow is wired into two more entry points, both non-blocking (return a runId
immediately, poll the outcome): the research MCP tool (see "Non-blocking research" under
"Expose Cyberine as an MCP server" above) and cyberine chat's /xmode research + /run (see
/xmode under the chat command reference above). Both spawn a fresh detached child process per
dispatch rather than running in-process, because researchWorkflow.ts reads its question/depth
from env vars at module load time and both callers are long-lived processes — see
src/engine/researchAsync.ts's doc comment for the full reasoning.
From inside Claude Code — /cyberine:research. The bundled plugin skill
(plugin/skills/research/) is the front door for the operator's daily "spawn some agents and find
out X" habit: it preflights the web-search backend with cyberine doctor (reads the
web search backend: line under Web search:), refuses before spending when that line is
[WARN], otherwise picks a --depth from the question's shape, states the spend including the
search backend, runs the command above, then reads the
report back and returns the verified findings with their citations plus the verified/dropped
counts. It is disable-model-invocation: true (spends quota, so plain language does not reach it
directly — only an explicit /cyberine:research "<question>") and deliberately web-only: a
question about this repo's own code stays with Claude Code's own agents, never this skill.
/cyberine:research "What are the trade-offs between SQLite and DuckDB for embedded analytics?"
/cyberine:research --depth max "Compare LiteLLM vs Portkey for production LLM routing"A loop worker is required, and research says so up front. Every research step dispatches through
a raw-API loop worker, and CLI / seat workers are build-fleet-only: a machine whose fleet.json
enables only seats and CLIs builds ZERO adapters for cyberine research, however good its
credentials are. Since k1ggk the run refuses with the reason and the fix rather than
check your credentials — it names the loop worker(s) the fleet does resolve and the exact pin:
CYBERINE_BUILD_FLEET="loop-commandcode-free-build" cyberine research --depth quick "<question>"
# or permanently: cyberine fleet list, then cyberine fleet enable <provider>Known Limitations and Deferred Work. The seat backend was built and checked offline against a
stub claude (flags, token by descriptor, source filtering); no full research run has produced a
verified claim yet, and kf15g covers running one end to end. The backend is resolved per
web_search call, not per run, so a seat that runs out of quota mid-run shows up as failed
searches rather than a preflight refusal; the seat backend is not a fleet worker, so its turns do
not appear in cyberine agents or the spend ledger. web_search is the same tool in cyberine chat and every loop worker, so the default seat backend spends seat quota there too. Anthropic API
and OpenRouter search backends are not built; the selector is shaped for them. The
skill's depth heuristic (narrow fact -> quick, ordinary question -> deep, open-ended/market-scan
-> max) is a starting point an explicit --depth in the request always overrides, not a
classifier. It never promotes a finding onto the task board — that is /cyberine:tasks.
Operating modes — --mode compete|swarm (+ CYBERINE_MODE env)
Two preset bundles that drive WorkflowEngine behaviour end-to-end: fleet size cap, judge chain selection, verify gate, governor, and M5 escalation. Selected via flag, env, or auto-routing.
Website rebuild target (Next.js + Fumadocs) — site/
The rebuild (T666) lives in site/ as a separate buildable target, and since the T670 cutover (2026-09-25) it is the only docs site.