# Ruchat TODO
Last updated: 2026-08-03
## High Priority
### 1. Configuration & CLI Improvements
- [ ] **`model_options` file/string merge is a silent no-op** (found while removing the double JSON round-trip in `ModelArgs::build_generation_request`, see Done section): `merge_options_json`'s file/string-based override merge never applies. `ModelOptions::default()` serializes to `{}` (every field is `None` and `skip_serializing_if`-omitted), so its `defaults.contains_key(&k)` gate is never true for any key from a `model_options` file/string — those values silently land in the discarded `remain` map instead of `defaults` and never reach the final `ModelOptions`. Only CLI-flag values (set unconditionally, no gate) currently take effect. Affects both `ModelArgs::build_generation_request` and `options::get_options`'s only other caller (`core/agent.rs`). Regression-pinned by `model.rs`'s `config_only_model_options_are_currently_silently_dropped` test. Needs a design decision (drop the `contains_key` gate entirely? pre-populate `defaults` with every known key at `Value::Null` before the check?) rather than a unilateral fix.
- [~] Environment variable support for Chroma/Ollama settings — added `OLLAMA_SERVER` (parity with the existing `CHROMA_SERVER`/`CHROMA_TOKEN`) and `CHROMA_TENANT`/`CHROMA_DATABASE`. Deliberately did NOT env-var every flag (e.g. `--temperature`/`--top-k`/`--seed`/model selection) — those are per-invocation tuning, not deployment config, and `--options <JSON|file>` already exists as the mechanism for persisting generation-parameter presets; env-var-ifying them would be scope creep with no real usage pattern behind it.
- [ ] Deprecate/phase out scattered JSON string hacks in favor of structured sub-configs — includes the generic per-flag CLI/file merge noted in `cli/serde.rs::load_merged_config`'s comment: today each subcommand's `*Args` struct applies its own CLI flags over the config-file `Value` individually (`update_from_json` per struct); a fully generic merge would need every `*Args` struct to serialize itself to `Value`, which doesn't exist yet. Left as the documented, deliberate deferral it already was — not attempted here to avoid exactly the "implemented speculatively" risk that comment warns against.
### 2. Error Handling & Logging
- [ ] **Not done, much larger scope**: `RuChatError::InternalError(String)`/`Is(String)` are used as catch-all buckets at ~85 call sites across `src/core`/`src/providers`, flattening distinct failure causes into two generic variants and losing the ability to pattern-match on specific failure kinds. Fixing this properly means auditing each call site to decide its own dedicated variant (or a `#[source]`-carrying wrapper) — a large, higher-risk pass, not attempted here.
- [ ] Implement graceful degradation when Ollama/Chroma are unavailable
### 3. Agent Orchestration
- [ ] Make agent pipeline fully configurable via JSON (the `Stage` sequence in `orchestrator.rs` is still fixed in code, not data — see `ROADMAP.md` Phase 3)
- [ ] Improve Librarian → Worker document injection further (per-document summarization before Worker, multi-collection queries — reranking/relevance scoring is done, see `providers/vector/chroma/rerank.rs`)
- [ ] Add memory / long-term storage persistence between runs (the `memorize` tool already writes to Chroma via `Agent::embed`, but there's no automatic recall of prior-run memories at session start)
- [ ] `apply_patch` still has no scope check against the Scoper/Architect plan (a patch can touch a file the plan never mentioned) — diff-size cap and rejection rollback are done, see Done section
- [ ] Expose `BuildReport::parsed_diagnostics` (`agent/protocol.rs`) to callers instead of only the flattened diagnostics string — the structured `Diagnostic { level, message, file, line, column }` is already populated per `cargo check` run but currently sits behind `#[allow(dead_code)]`; feeding it to the Worker/Validator directly would let a rejection point at an exact file/line instead of a text blob
### 4. TUI Chat
**Reality check (2026-08-03): there is no interactive chat TUI in the codebase right
now.** The crossterm-based interactive layer (cursor movement, text selection,
history/undo-redo editing — `providers/llm/ollama/chat/{conversation_tree,history,
pos,event_result}.rs`, ~1,260 lines) was deleted 2026-07-31 (`ad0708d "remove more old
code"` and the two commits around it), a few days before the items below were last
touched. `src/tui/` today is just `io.rs` (async stdin/stdout wrapper) and
`render.rs` (a streaming ANSI-colored line renderer for `pipe`/`ask`/`manager run`
output) — 175 lines total, no cursor/selection/editing code at all. The items below
describe bugs in a subsystem that no longer exists; they'd need to be rebuilt from
scratch, not "fixed." Leaving them here as a record of what an interactive TUI would
need if one gets rebuilt, not as active bugs.
- [ ] ~~Fix redraw artifacts and cursor handling edge cases~~ — moot, no cursor handling exists
- [ ] ~~Improve selection + copy/paste reliability~~ — moot, no selection exists
- [ ] ~~Add syntax highlighting for code blocks in chat view~~ — moot, no chat view exists
- [ ] ~~Support multi-line editing with proper indentation~~ — moot, no editable input buffer exists
- [ ] ~~Add command palette / key bindings help screen~~ — moot, nothing to bind keys to
- [ ] `crossterm` (`Cargo.toml`) is now an unused dependency — nothing in `src/` references it. Left in place rather than removed unilaterally, since removing it forecloses rebuilding the interactive TUI without re-adding it; worth a deliberate decision (drop it vs. keep it for a planned rebuild) rather than a silent removal.
- [ ] Wire up an actual producer for `AgentEvent::Progress` (`agent/event.rs`) — the render loop (`tui/render.rs::render_pipeline_stream`) already has a full `Progress(pct)` match arm that draws a "...N%" status line, but nothing in the orchestrator/agent code ever sends one; likely candidates are per-round progress (`round`/`max_iterations`) or per-chunk streaming progress. Still applies — `render.rs` is part of the code that's actually still here.
## Medium Priority
### Code Quality & Maintainability
- [ ] Add integration tests for full agentic flows (using test Ollama/Chroma) — `agent_debug/*.json` already contain ready-made stage sequences (`architect_only`, `worker_and_validator_rejection`, `multiple_critics`, etc.); wire these into `cargo test` against a mocked `LlmClient`/`VectorStore` instead of writing fixtures from scratch
- [ ] Consistent error handling across Chroma subcommands
- [ ] Refactor duplicated JSON update logic (`update_from_json` methods)
- [ ] Three pre-existing test failures are `#[ignore]`d with reasons rather than fixed (deeper logic bugs, not just stale field names — need someone with context on the intended behavior): `chroma::metadata::tests::test_get_metadata_valid` (`parse_metadata` doesn't support the `key:value,key:value` shorthand the test expects), `chroma::tests::test_create_table` (expects a `"DOCUMENT"` header but rendering only ever emits the short `"DOC"` alias), `chroma::tests::test_json_output` (fixture uses an `Include` value the current enum doesn't accept)
- [ ] The ~16 pre-existing dead-code warnings (`cargo clippy --lib`) aren't blocking CI yet — worth cleaning up so a future `-D warnings` gate is actually adoptable
- [ ] `cargo fmt --check` currently flags formatting drift in ~76 files repo-wide (no custom `rustfmt.toml`, so this is default-rustfmt drift accumulated over time, not a style disagreement) — needs a dedicated `cargo fmt` pass across the repo before a `fmt --check` CI gate can be added without blocking unrelated PRs on pre-existing drift
### Chroma / RAG
- [ ] Support automatic collection creation from `db_config.json` on first embed
- [ ] Add progress bar for large embedding jobs
- [ ] Implement caching layer for repeated file embeddings
- [ ] Add `ruchat chroma-import` command for git history / source trees
- [ ] Better metadata normalization and type safety
- [ ] `embed_script.sh`'s ctags chunk-boundary detection has two open `FIXME: improve per lang/kind handling here` markers around its closing-brace search — the language/kind match lists (Rust, Sh, TOML, Markdown) are hand-maintained and incomplete, so other ctags-supported languages fall back to a single-line chunk instead of the real symbol extent
### Performance
- [ ] Streaming response handling in agent orchestrator (currently buffers)
- [ ] Optimize history limit calculation and token counting
- [ ] Review `reqwest` feature flags in `Cargo.toml`
### Security & Production Readiness
- [ ] Never log sensitive data (tokens, prompts with secrets)
- [ ] Add optional authentication for Ollama
- [ ] Rate limiting / retry backoff configuration
- [ ] `cargo_check`/`cargo test` run with timeouts (30s/60s/120s) but no memory/CPU resource limits — there is no generic shell-execution tool (deliberately — the Worker/Scoper only have specific typed tools), so this is scoped to the cargo subprocess, not arbitrary shell sandboxing
## Low Priority / Nice-to-have
- [ ] API versioning for future breaking changes (`/v1/`)
- [ ] Plugin system for custom tools and agents
- [ ] Web UI / server mode
- [ ] Export conversation as Markdown / JSON
- [ ] Voice input / output support
- [ ] Multi-modal support (images via `qwen2.5vl`, etc.)
## Done / Recently Completed
- [x] **v0.2.0 released** (2026-08-03) — version bumped in `Cargo.toml`/`README.md`; this migration of completed items out of the priority sections and into this list is that release's own checklist item. `cargo check --lib`, `cargo test --lib`, `cargo clippy --lib --tests`, and `cargo build --release` all verified clean (pre-existing dead-code warnings only, no new ones) before the bump.
- [x] Fixed a flaky test: `cli::options::tests::test_read_options_file` and `test_get_options_with_file` both wrote/read/deleted the same relative path (`test_options.json`) and ran concurrently under the default multi-threaded test runner, racing on the shared file (intermittent `EOF while parsing a value`). Switched both to `tempfile::tempdir()` (the pattern already used in `cli/config.rs`/`cli/prompt.rs`), giving each test its own isolated file.
- [x] Removed the double JSON round-trip in `ModelArgs::build_generation_request` — it called `get_options` (which already does `ModelOptions::default()` → JSON → merge → `ModelOptions`), then serialized that `ModelOptions` back to JSON a second time just to merge in CLI flags, then deserialized again. Extracted `options::merge_options_json` (the pre-deserialize half of `get_options`) so `build_generation_request` merges CLI flags directly onto the same JSON `Value` and deserializes once. (Surfaced the `model_options` no-op bug above in the process — tracked separately, not fixed here.)
- [x] Implement global config file with profile support — turned out to already exist and work (`cli/config.rs::ConfigArgs`, `~/.config/ruchat/config.json`/`./ruchat.json`/`--config`/`RUCHAT_CONFIG`, `"profiles": {name: {...}}` selected via `--profile`/`RUCHAT_PROFILE`, tested by `test_config_json_profile`). JSON, not TOML, but the item's own wording always allowed either. Also deleted `cli/serde.rs::read_config_file`, a second, dead, never-called JSON-file-reader that duplicated part of this — found via the pre-existing dead-code clippy warning.
- [x] Migrated genuine diagnostic `eprintln!`/`println!` call sites in `src/core`/`src/providers` to `tracing`. The rest of the `println!`s in that tree (`chroma/ls.rs`, `ollama/server.rs::ls`, `manager.rs`, `chroma/query.rs`'s result print) are each command's actual designed stdout output, not debug prints — converting those to `tracing` would hide them by default without `RUST_LOG` set, so they're staying as-is
- [x] Fixed the concrete cases found where an error handler actively discarded useful diagnostic info (`map_err(|_| ...)`) rather than just using a generic variant name: `model.rs::get_model_name_inner` was reporting "model not found" for what could be an unreachable-Ollama-server failure, now includes the real cause and an "is Ollama running?" hint; `tools.rs`'s `ToolParseError::UnknownTool` was a unit variant with no way to tell the caller (or the Worker retry loop) which invalid tool name the model actually output, now carries it, and is split from a new `MissingTool` for the genuinely-no-tool-field case. Also replaced a `.unwrap()` inside `func_struct`'s interactive chat loop (`ollama/func/strukt.rs`) that would crash the whole process on any transient chat failure with `?` (the function already returns `Result`); and removed ~8 `if x.is_string() { x.as_str().unwrap() }`-shaped unwraps across `agent.rs`/`chroma.rs`/`collection.rs`/`include.rs`/`where.rs` in favor of `if let Some(s) = x.as_str()` (same behavior, no unwrap). Audited `ollama_rs`'s chat-stream error path too — its `Item` error type is `()`, no more detail is recoverable there, left as-is.
- [x] Unit tests for parser modules: `include.rs`/`where.rs` had internal parse functions covered but not the `IncludeArgs`/`WhereArgs` `parse()`/`update_from_json()` wrappers CLI code actually calls (now added); `cli/prompt.rs` (`andify_list`, `get_prompt`, `promptless`) had zero tests, now covered including the external-command exit-code path
- [x] `cargo test --lib` was uncompilable (33 errors: `OutputArgs`/`create_table` test code hadn't been updated after a prior refactor to `format`/`render_rows`, and two `where.rs` tests compared a `Result<T>` against a bare `T` after `map_sql_comparison`/`map_sql_to_document_op` started returning `Result`) and, separately, `test_handle_request_default` called `Args::parse_from(["test", "-h"])`, which makes clap call `std::process::exit(0)` mid-test-run — silently killing every other test in the same process depending on thread scheduling. All fixed; the suite is green.
- [x] **Multi-critic consensus review was completely non-functional.** `Orchestrator::new`'s Critics loop passed each critic's flat config object straight to `Agent::new` as `config`, which looks up `config.get(role)` — a key that can never exist in a flat object, so `Agent::new` always errored and `critics` stayed empty regardless of `--critic`/`"Critics"` config, silently. Even fixed, a second bug meant `query_stream` would then fail with `InvalidRole`: `Role::from_str` only recognized the bare string `"critic"`, never the `"Critic_0"`/`"Critic_1"` naming `Orchestrator::new` actually assigns. Both fixed (`orchestrator.rs`, `agent/role.rs`); caught by wiring `agent_debug/multiple_critics.json` into a real test (`core::orchestrator::tests::multiple_critics_dispatches_each_critic_once`) — nothing had ever exercised this path end-to-end before.
- [x] Wired 9 of 10 `agent_debug/*.json` fixtures into `cargo test --lib` (`core::orchestrator::tests::*`) using a new `FakeLlmClient` (`agent/llm_client.rs` — the `FakeVectorStore`/`FakeEmbeddingsClient` fakes already existed but had never actually been wired to anything) alongside the existing `FakeVectorStore`, so the stage machine can be exercised without a live Ollama/Chroma server. Also fixed a fixture bug: `critic.json`/`multiple_critics.json` used `"Critic0"`/`"Critic1"` (no underscore), which doesn't match the `"Critic_N"` naming the code actually expects.
- [x] Added `.github/workflows/ci.yml`: build + `cargo clippy --lib --tests` + `cargo test --lib` on push/PR. Deliberately no `-D warnings` (dead-code warnings not yet cleaned up) and no `fmt --check` yet (repo-wide drift not yet cleaned up).
- [x] Investigated connection pooling for Ollama and Chroma clients — turned out to already be satisfied, not a gap: `Orchestrator::new` (`core/orchestrator.rs`) constructs exactly one `Ollama` client and, when a Librarian is configured, exactly one Chroma client per run, wraps each in `Arc`, and shares that single instance across every agent role (Architect/Worker/Validator/Critics/Summarizer/Librarian) rather than each role independently constructing its own. Neither `ollama_rs::Ollama` nor `chroma::ChromaHttpClient` are wrapped with a custom `reqwest::Client::builder()` anywhere in this codebase that could disable pooling, so both get `reqwest`'s default keep-alive/idle-pool behavior for free. The standalone one-shot CLI subcommands (`chroma-get`, `chroma-search`, etc.) each make exactly one HTTP request per process invocation, so there's nothing to pool against within their lifetime anyway.
- [x] Consolidated TODO files into single `TODO.md`
- [x] Improved model option merging with CLI flags
- [x] env_logger / tracing integration
- [x] Basic multi-agent orchestration with RAG support
- [x] Git auto-commit feature branch on approval
- [x] Robust Chroma CLI with where/include parsing
- [x] TUI chat with history, undo/redo, selection — **later removed** (2026-07-31, `ad0708d` and surrounding commits deleted the ~1,260-line `providers/llm/ollama/chat/{conversation_tree,history,pos,event_result}.rs` this was built on); no longer accurate as a "done" claim, see the "TUI Chat" section above
- [x] Structured tool calling framework (`agent/tools.rs::ToolName`, schema-validated, replaces regex-only parsing) — 13 typed tools including `apply_patch`, `git_*`, `read_file`, `ripgrep`, `read_tags`, `cargo_check`/`cargo_dupes`
- [x] Parallel critic execution (`Orchestrator::run_critics_parallel`, `futures_util::future::join_all`)
- [x] RAG relevance scoring / reranking (`providers/vector/chroma/rerank.rs`, distance+lexical blend)
- [x] Token-aware history management with automatic Summarizer trigger (`Stage::Retry`, `get_dynamic_history_limit`)
- [x] Pre-planning repo-grounding stage (`Scoper` role — not in the original TODO/ROADMAP list at all)
- [x] Structured `Context` event log (`Vec<Turn>` + `TurnKind`) replacing the old flat-string `history`/`context`/`documents`/`rejections` fields
- [x] Reconciled the legacy `Team`/`Manager` pipeline — `ruchat manager` now runs a saved `Team` preset through the real `Orchestrator` stage machine instead of a separate, unvalidated linear engine
- [x] `apply_patch` diff-size cap (`MAX_PATCH_DIFF_BYTES`, `agent/protocol.rs`) and automatic rollback of a rejected round's patch before looping back to `Plan` (`Context::{record_patch,revert_pending_patch}`)
- [x] Confirmed the "remove dead code" item once flagged above for `conversation_tree.rs`/legacy `Team`/`Manager` is fully resolved: `conversation_tree.rs` no longer exists and `team.rs`/`manager.rs` are the reconciled implementation this list already credits — removed the stale duplicate bullet
- [x] Removed an unused `OrchestratorRun` struct (`orchestrator.rs`) whose doc comment described bundling an `Orchestrator` to implement `AgentPipeline`'s "fixed `run(&mut self, ...)` signature" — `AgentPipeline` (`agent/pipeline.rs`) is an enum with its own `run(self)`, not a trait anything implements, so both the struct and its rationale were stale leftovers; `ask.rs`/`manager.rs` already construct `AgentPipeline::Orchestrator` directly
---
**Next milestone:** v0.3.0 (ROADMAP.md Phase 2) — persistent memory auto-recall, further RAG improvements (document summarization, multi-collection queries), automatic Chroma collection management, `apply_patch` scope-check against the Scoper/Architect plan, and resource-limited cargo subprocess sandboxing.
Help welcome on any item — especially testing and configuration refactoring.