# Index-free MiniCPM search This experimental mode uses **MiniCPM5-1B Q4_K_M** to choose searches and reads of a repository's current files. It requires no SQLite index, embeddings, or repository training. The initial file listing is computed for each request. Model weights remain loaded in a local Ollama process for 30 minutes after a request. The default Ollama command uses **off-the-shelf weights**. A separate Transformers backend supports reference inference and locally trained LoRA adapters. The earlier fine-tuned MiniLM model remains available through the indexed commands. See the [measured results](../reports/minicpm5-v1/README.md) before choosing a backend. ## Setup The initial implementation requires Linux/POSIX, Python 3.11–3.13, `ripgrep` on PATH, and a running local Ollama server. The filesystem reader uses `openat`, `O_NOFOLLOW`, and directory descriptors; Windows is not supported by this mode. ```bash uv sync --extra live --extra mcp --extra dev ollama pull openbmb/minicpm5:q4_K_M uv run --no-sync python -m micro_scout.prepare_live ``` `prepare_live` downloads only the approximately 10 MB tokenizer from a pinned OpenBMB revision, verifies its SHA-256, and caches it in `~/.cache/micro-scout/minicpm5-tokenizer.json`. Later searches need no network access beyond the loopback connection to Ollama. Without this cache, the harness uses a conservative UTF-8 byte bound for context size, which can reject larger observations. The `live` extra installs the lightweight tokenizer library and does not install PyTorch or a training stack. The measured laptop already had Ollama 0.20.4 and the official model downloaded. The local GGUF blob was verified against OpenBMB's published artifact: - Repository: `openbmb/MiniCPM5-1B-GGUF` - Revision: `3d55fac80935ae6456986ad2384b5cbcc4d6c948` - File: `MiniCPM5-1B-Q4_K_M.gguf`, 688,065,920 bytes - SHA-256: `81b64d05a23b17b34c475f42b3e72fbde62d4b92cc34541f7a8031d0752deafa` - Tokenizer revision: `87179e5c1f455ef22e6223592d2d61351b525bfc` Ollama tags can change. Verify the model artifact when reproducing the baseline. ## CLI ```bash uv run --no-sync micro-scout live /path/to/repository \ "Find where credentials are removed before following a redirect" \ --trace runs/my-search.json ``` Options include `--max-rounds 6`, `--timeout 90`, `--context 8192`, `--max-chars 6000`, `--model`, and a loopback-only `--endpoint`. Each model generation is limited to 512 tokens. All output source ranges are one-based and inclusive. `max_chars` limits returned source characters, not the entire response or model tokens. A trace is written only when explicitly requested and contains the query, generated calls, source observations, errors, and runtime counters. The `status` field distinguishes `completed`, `abstained`, `budget_exhausted`, and `model_error`. Completed means the references passed verification; it does not mean the model found the right implementation. Empty results and failures are reported without silently falling back to the old indexed search. ## Protocol and tools The harness uses MiniCPM5's native `` syntax, with no-think ChatML framing. It parses the XML itself through Ollama's raw generation API. It does not depend on Ollama detecting native tool-call support in the model's installed template. Sampling uses temperature 0 and seed 42; these settings do not guarantee bitwise determinism across runtimes. Available model actions: | Action | Purpose | | --- | --- | | `files(glob)` | List at most 100 visible paths, prioritizing `src/` and `lib/`. | | `grep(pattern, glob)` | Case-insensitive Rust regex, up to 8 matches per file and 30 returned matches. | | `read(path, start_line, end_line)` | Read at most 120 numbered lines and approximately 8,000 characters. | | `finish(path, start_line, end_line)` | Select previously read lines for the caller. | | `not_found()` | Explicitly finish without evidence. | The model may issue up to three actions in a response. The first implementation executes filesystem actions sequentially. It performs one additional initial file listing, counted in `tool_calls`. This counter counts filesystem action attempts; `finish` and `not_found` do not increment it. Searches respect the default ignore rules; positive globs are checked against the default visible-file inventory. A trailing directory slash in a glob means all files under that directory. Repository contents are untrusted model input. The executor accepts only the listed read-only operations, uses argument arrays instead of a shell, rejects hidden/escaping read paths and symlinks, and caps source files at 1 MB. Each ripgrep subprocess has a three-second deadline and bounded captured output. Direct reads of explicitly named non-hidden ignored files are possible; ignore rules govern search discovery, not access control. The root directory is the access boundary. Final references must be covered by this search's read observations. The harness reopens the source and compares its hash before returning it. Changed files, invented ranges, and excess source output cause an error that the model can try to correct within its remaining round budget. This verifies provenance, not semantic relevance. Token counts use the pinned tokenizer; older exchanges can be removed with a warning to fit the context. Oversized remaining prompts fail explicitly rather than relying on silent runtime truncation. ## MCP ```bash uv run --no-sync micro-scout serve-live /path/to/repository ``` This stdio server exposes `scout_live_search(query, max_chars)` and serializes requests to the shared local model. Configure the host with absolute paths and a tool timeout above the chosen search timeout. Source queries are not logged by the MCP adapter. Starting this server does not replace an existing indexed MCP configuration. ## Reproducing the development evaluation The suite in `evals/live-search-v1.json` contains 30 hand-authored English tasks, 10 each for Requests, Flask, and Click. Their revisions and target source hashes are pinned. Labels were fixed before running the evaluation and are never passed to the model. The JSON-decoding query used to develop the protocol is excluded. ```bash mkdir -p data/search-eval git clone --depth 1 --branch v2.32.5 https://github.com/psf/requests.git data/search-eval/requests git clone --depth 1 --branch 3.1.2 https://github.com/pallets/flask.git data/search-eval/flask git clone --depth 1 --branch 8.2.1 https://github.com/pallets/click.git data/search-eval/click uv run --no-sync python -m micro_scout.eval_live --output runs/live-eval-001 uv run --no-sync python -m micro_scout.eval_live --backend keyword --output runs/keyword-eval-001 ``` The evaluator verifies clean repository revisions and source hashes, saves its configuration before inference, warms the model, records all 30 traces, and reports failures alongside successes. It measures file hits, target hits (at least three executable-body lines, or the whole body when shorter), line precision/recall, end-to-end latency, runtime-reported tokens, and GPU memory sampled once per second. Function body ranges exclude their leading docstrings; they are approximate relevance labels and are not exhaustive multi-file context. These are public, mature Python projects. They may have appeared in MiniCPM's pretraining, and the tasks were authored during development. This is not a contamination-free benchmark, a multilingual evaluation, or evidence of improved coding-task success with Astra. It also does not compare against Astra using grep. The `keyword` evaluation backend is a fixed, non-neural control: up to 12 literal term searches, followed by at most three 25-line reads. It ranks windows by distinct query terms, weighted by their observed match counts. It uses the same bounded filesystem executor and source verification, without an index. This is a simple heuristic, not a simulation of a large model choosing and refining searches. The model backend and keyword control have different action counts; reports show those counts alongside latency and relevance. ## Reference inference and adapter training The `policy` extra requires an NVIDIA CUDA GPU for this initial implementation. Do not run Ollama inference and training on the same 4 GB GPU simultaneously. Downloading the pinned original checkpoint needs approximately 2.16 GB on disk, in addition to dependencies and the optional GGUF copy. ```bash uv sync --extra policy --extra train --extra mcp --extra dev HF_HUB_DISABLE_XET=1 uv run --no-sync python -m micro_scout.prepare_live --weights ollama stop openbmb/minicpm5:q4_K_M uv run --no-sync micro-scout live /path/to/repository "Find retry handling" \ --backend transformers --bf16 ``` The Transformers backend uses the pinned original checkpoint, greedy decoding, and the same prompt and tool protocol. Without `--bf16` it uses NF4 double quantization; it keeps the model resident for the lifetime of `serve-live`. The XML delimiters are special tokens in MiniCPM's tokenizer: they must be preserved when decoding tool calls. Only terminal end-of-turn tokens are removed. Prepare the audited CodeSearchNet subset using the indexed model's [data preparation instructions](TRAINING.md), then build executed demonstrations: ```bash uv run --no-sync python -m micro_scout.live_data --output data/live-policy-windows-v1 uv run --no-sync python -m micro_scout.train_policy \ --data data/live-policy-windows-v1 --output runs/minicpm5-policy-v2 --epochs 1 ``` This is a small **supervised QLoRA experiment**, not RL or training from scratch. It uses 256 training trajectories and 32 validation trajectories from disjoint CodeSearchNet repositories. Requests, Flask, and Click are excluded by repository name. Each demonstration constructs a three-file synthetic repository, with two functions per file. It preserves source filenames and varies line offsets and the position of the target relative to a distractor. An oracle uses the known label to select a query word and a visible target match. It reads a fixed window (40 lines before and 60 after the match), then selects the target from the actual read output. Candidates without an observable match are rejected. Some trajectories include a failed search before the successful one. Tool observations are real executor outputs. Only assistant action tokens and the turn-ending token contribute to training loss; prompts and source observations are masked. The trainer drops overlength examples instead of truncating actions, records dataset hashes and settings, and selects the adapter by validation action loss. It computes output logits only for the supervised suffix to reduce memory. Tests compare that loss and its gradients against ordinary masked causal loss. These demonstrations teach protocol and short search sequences. Oracle-selected files and final ranges, three-file repositories, and documentation-derived queries are substantial simplifications. Validation action loss is not repository search accuracy. The pipeline does not collect an online reward, update a serving model, or establish improved coding-task performance. No teacher or solver API is called. Evaluate with identical backend and search settings before and after training: ```bash uv run --no-sync python -m micro_scout.eval_live \ --backend transformers --output runs/live-nf4-base uv run --no-sync python -m micro_scout.eval_live \ --backend transformers --adapter runs/minicpm5-policy-v2/best \ --output runs/live-nf4-adapter uv run --no-sync micro-scout serve-live /path/to/repository \ --backend transformers --adapter /absolute/path/to/runs/minicpm5-policy-v2/best ``` The earlier `function-ranges` recipe requested exact function boundaries before reading their contents. Its low validation loss did not translate into target hits; it is retained only for reproducing the first failed adapter experiment: ```bash uv run --no-sync python -m micro_scout.live_data \ --recipe function-ranges --output data/live-policy-v4 uv run --no-sync python -m micro_scout.train_policy \ --data data/live-policy-v4 --output runs/minicpm5-policy-v1 --epochs 1 --max-length 2048 ``` The current default is `read-windows`, with a 2,560-token training limit. Both recipes are oracle-generated demonstrations, not trajectories from an autonomous expert agent. The first recipe's prepared train/validation hashes were reproduced exactly after adding the recipe switch. Keep the development suite out of training and use a new untouched suite before selecting a model for deployment. Model binaries, full source traces, and prepared data stay outside Git. Training currently has no optimizer resume; interrupted runs should use a new output directory. References: [MiniCPM5](https://huggingface.co/openbmb/MiniCPM5-1B), [native chat template](https://huggingface.co/openbmb/MiniCPM5-1B/blob/87179e5c1f455ef22e6223592d2d61351b525bfc/chat_template.jinja), [SWE-grep](https://cognition.com/blog/swe-grep), [CodeScout](https://arxiv.org/abs/2603.17829).