111 lines
4.9 KiB
Markdown
111 lines
4.9 KiB
Markdown
# doubleo7
|
||
|
||
A local-first, multi-agent deep-research CLI: give it a topic, it searches
|
||
the web, cross-checks what it finds, and writes up a cited report — entirely
|
||
on infrastructure you control, with no cloud LLM API key and no query ever
|
||
leaving your machine.
|
||
|
||
```
|
||
$ doubleo7 "trends in AI customer-support chatbots"
|
||
🔎 Researching...
|
||
🧐 Reviewing findings...
|
||
✍️ Writing report...
|
||
|
||
# AI Customer-Support Chatbots: 2025–2026 Trends
|
||
...
|
||
```
|
||
|
||
## Why this exists
|
||
|
||
This started as a "does deep research actually work end-to-end" exercise and
|
||
turned into a small case study in building an *agentic* system that survives
|
||
contact with reality: models that hit their turn budget mid-task, search
|
||
providers that rate-limit, and reviewers that reject good-faith work. The
|
||
[case study](./docs/case-study.md) walks through what broke and how each
|
||
failure was fixed, not just papered over.
|
||
|
||
## Architecture
|
||
|
||
Four small agents, each with one job, coordinated by plain Rust control
|
||
flow — not a framework's agent graph, not an LLM deciding when to stop:
|
||
|
||
```
|
||
┌─────────────┐ approve/reject ┌──────────┐
|
||
│ researcher │ ───────────────► │ reviewer │
|
||
│ (tool-using)│ ◄─────────────── │ │
|
||
└──────┬──────┘ gaps/feedback └────┬─────┘
|
||
│ turn budget exhausted │ approved,
|
||
│ mid-investigation │ or out of rounds
|
||
▼ ▼
|
||
┌──────────────┐ ┌──────────────┐
|
||
│ summarizer │──findings───►│ writer │──► report
|
||
│ (recovery) │ │ │
|
||
└──────────────┘ └──────────────┘
|
||
```
|
||
|
||
- **researcher** — a tool-calling agent (`search_web`, `fetch_page`) that
|
||
gathers and cross-checks evidence, capped at a fixed model-call budget so
|
||
a confused model can't loop forever.
|
||
- **reviewer** — a separate, fresh-context agent that checks the researcher's
|
||
conclusions actually follow from its cited sources, and either approves
|
||
the findings or hands back concrete gaps for another pass.
|
||
- **writer** — turns approved (or partial) findings into a structured,
|
||
footnoted report, streamed to the terminal as it's generated.
|
||
- **summarizer** (recovery path) — only runs when the researcher exhausts
|
||
its turn budget before concluding on its own. It reconstructs a proper
|
||
findings dump from the raw tool-call transcript rather than the run
|
||
simply failing; see the case study for why this exists and how it
|
||
degrades gracefully if the summarizer call itself fails.
|
||
|
||
Everything runs against local models via [Ollama](https://ollama.com) and a
|
||
self-hosted [SearXNG](https://searx.space) instance for search — no OpenAI/
|
||
Anthropic/Google API key, no third-party search API, nothing about the
|
||
research topic leaves the host it runs on. That's a deliberate constraint,
|
||
not a limitation: it's the same shape a privacy-sensitive customer
|
||
deployment would need.
|
||
|
||
## Running it
|
||
|
||
Prerequisites:
|
||
- [Ollama](https://ollama.com) running locally with a tool-calling-capable
|
||
model pulled (the researcher and reviewer/writer models are configured in
|
||
[`src/models.rs`](./src/models.rs))
|
||
- A local [SearXNG](https://docs.searxng.org/) instance with its JSON API
|
||
enabled (defaults to `http://localhost:8080`, overridable via
|
||
`SEARXNG_URL`)
|
||
|
||
```
|
||
cargo run -- "your research topic"
|
||
|
||
# or, with tracing spans on stderr instead of the progress spinner:
|
||
cargo run -- -l info "your research topic"
|
||
```
|
||
|
||
## Project layout
|
||
|
||
Split one concern per file rather than one large module:
|
||
|
||
| File | Responsibility |
|
||
|---|---|
|
||
| `main.rs` | Argument parsing, logging setup, and the single top-level call — no orchestration logic |
|
||
| `research.rs` | The research/review round loop |
|
||
| `researcher.rs` | The tool-calling research phase |
|
||
| `review.rs` | The reviewer agent |
|
||
| `summarizer.rs` | Max-turns recovery: reconstructs findings via a model call |
|
||
| `writer.rs` | Turns findings into the final streamed report |
|
||
| `history.rs` | Pure, unit-tested helpers for parsing a rig chat history into usable text |
|
||
| `tools.rs` | `search_web` (SearXNG) and `fetch_page` tool implementations |
|
||
| `stream.rs` | Drains a streaming prompt response to the terminal |
|
||
| `progress.rs` | The terminal spinner and per-phase emoji |
|
||
| `models.rs`, `cli.rs`, `observability.rs` | Small shared config: model names, CLI args, tracing setup |
|
||
|
||
## Testing
|
||
|
||
```
|
||
cargo test # unit tests — pure functions, no network
|
||
cargo test -- --ignored # + a live smoke test against SearXNG
|
||
cargo clippy --all-targets
|
||
```
|
||
|
||
CI (`.forgejo/workflows/ci.yml`) runs formatting, lint, build, and the unit
|
||
test suite on every push and PR.
|