ADR 0160 — Chat's search filters are hints, and repeated reads are not run again
- Status: Accepted
- Date: 2026-11-08
- Related: ADR 0102 (no-progress patience, which the note feeds), ADR 0157 (research token charges), local models
Context
Section titled “Context”A live run of the chat pipeline on the maintainer’s machine (Windows, Radeon 8060S, Qwen3.5-4B Q4_K_M, chat.n_ctx 65,536) asked six questions whose answers are in 38 indexed docs (~/.cache/imprint-eval/eval_chat.py). Five answers held their facts. The sixth, “What port does the local Qdrant daemon listen on, and which ADR made Qdrant a server?”, named the wrong ADR after 13 tool calls and 421 seconds:
- Every search the model wrote carried
domain: "infra,db", a filter it guessed. ADR 0063, the answer, is not tagged with those domains. The same query ranked an ADR 0063 chunk second without the filter and left ADR 0063 out entirely with it. - The model then ran the identical search (same query, same filter) four times in a row before the no-progress patience ended research. Each repeat cost a model round and a search for the same result.
Decision
Section titled “Decision”- Guessed filters are hints. When chat’s
searchtool is called with any ofdomain,layer,lang,kindortype, it also runs the query without those filters (keepingproject,source,limitand paging) and appends up to three results the filtered search did not return, under “Also matching without(these memories are not tagged that way)”. Searches without such filters run once and are unchanged. The MCP searchtool is unchanged: an agent that sets a filter there gets the filter. - Repeated reads are not run again. Within one reply, a call to a read tool with the same name and the same arguments (compared as sorted JSON) as an earlier call returns
[repeated call] … already ran with these arguments in this reply (call N); its result is above …instead of running. Writes, the personal scheduling tools andask_useralways run. The note counts as no new evidence for the no-progress patience.
Rejected alternatives
Section titled “Rejected alternatives”- Remove the filter parameters from chat’s search tool. Filters are useful when they are right (a Python bug with
lang=python), and the strategy prompt recommends them; the evidence shows a wrong guess, not that filters never help. - Drop the filters silently when they return few results. The model would not learn that its filter was wrong, and a filter returning many weak results would still hide the right one, as here (four results came back).
- Rely on the no-progress patience alone for repeats. It did stop the loop, after four identical rounds; each of those rounds was a full model turn on the 4B model.
Consequences
Section titled “Consequences”A filtered chat search runs two queries and its result is up to three hits longer. With the change the same question took one search and 87 seconds, and ADR 0063’s chunks were in the result; the 4B model still named ADR 0024, which the result also showed, so this fixes retrieval, not the model’s reading of it. A legitimate repeat (re-reading after a write in the same reply) gets the note instead of a fresh result; status-style tools whose output changes during a reply are affected the same way.
Invalidation conditions
Section titled “Invalidation conditions”- If filtered searches routinely push the filtered results out of the model’s context, then show fewer unfiltered extras or only when the filtered search returns weak matches.
- If a reply needs to re-run a read after its own write (for example
kg_queryafterkg_add), then reset the repeat record after a write. - If tag quality improves so that guessed filters rarely exclude the answer (measured on stored replies), then reconsider the extra query.
Validation
Section titled “Validation”tests/test_chat_tool_coverage.py: guessed filters add the unfiltered extras (without the paging footer) and keepproject; unfiltered searches run once unchanged; nothing is added when the filters left nothing out; a repeated read returns the note whatever the argument order; writes and questions always run; the note is not new evidence.tests/test_chat_stream.py: research continues beyond twelve rounds while searches add evidence, with the repeatedstatuscall run once.- The live question above: before, 13 calls, 421 s, wrong ADR; after, 1 call, 87 s, ADR 0063 in the result, answer still citing ADR 0024.