doubleo7/README.md
Austin Schaefer 5bb4ed165d
All checks were successful
CI / test (pull_request) Successful in 3m11s
chore: rebrand project
2026-08-18 14:26:50 +02:00

111 lines
4.9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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: 20252026 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.