Standardize on the .agents/ directory (shared with project skills): phases/, user_stories/, reports/, screenshots/, validate.sh, and phase-sessions/ + pipeline.log all move to .agents/ (git mv preserves history; runtime artifacts move alongside). Updates every reference in AGENTS.md, README.md, .gitignore, app docstrings, and test story headers. Historical KB content in data/ and the runtime pipeline.log transcript are left untouched.
4.9 KiB
4.9 KiB
Phase 68 — Agent Search Tool: grep the Indexed Documents
Source: TODO.md L4 — "Add a search tool that allows the LLM to grep through the uploaded documents for a given string"
Story: n/a (TODO-derived — owner roadmap confirmation 2026-09-01)
Context:
app/rag/agent.py—AGENT_TOOLScurrently defines two OpenAI functions (list_documents,read_document);_execute_toolruns them server-side against the DB with fixed refusal strings (ALREADY_IN_CONTEXT,UNKNOWN_TOOL,MISSING_READ_ARGS);AgentHoldercounts executed calls (tool_calls) and context additions (read_docs).documentstable (app/models.py) —contentis the FULL document text (Textcolumn), so a grep is a plain in-process scan: no new index, no migration.app/schemas.py—ChatToolEvent({type: "tool", name, argument}):argumentis"source/path"forread_document, null otherwise.frontend/assets/app.js—runTurn'stoolbranch builds the status label (${brand()} is reading …/… is listing documents) andappendToolLine(L796) renders the per-call line (📄 Reading <code>path</code>/🔎 Listing documents); both special-case by tool name.tests/e2e/mock_llm.py— the marker-driven deterministic tool flow (use your tools→ list → read → answer) is the template for a search flow;tests/e2e/test_agent_document_tools.pyis the pattern for the E2E assertions (.tool-calllines,#send-statusrecording).- Phases 37/45/63 (complete) established the tool infrastructure: native tool-calling, unlimited calls bounded by the round cap, labeled
source:/path:catalog lines.
Objective
A third agent tool, search_documents, lets the model grep every indexed document (or one named document) for an exact string and get back path:line: text matches — so it can LOCATE content cheaply and then read_document the winner, instead of reading whole documents hoping the string is in them.
Dependencies
67_llm_retry(todo, preceding — no functional dependency; ordering by number)
Tasks
01_search_tool_backend.md— the tool definition, thegrep_documenthelper, and the_execute_toolbranch.02_search_tool_api_ui.md— the SSEtoolargument mapping and the frontend status/tool-line for the search.03_e2e_and_commit.md— the mock search flow, the dedicated E2E suite, regressions, commit.
Testing & Quality
- Unit:
tests/unit/test_agent.py— match semantics (case-insensitivity, 1-based line numbers, the 20-match cap, 200-char line truncation, scoped single-doc search, no-match/missing-arg/unknown-doc refusals,tool_callscounting,read_docsuntouched). - Integration:
tests/integration/test_agent_tools.py— the tool appears inAGENT_TOOLSwith the locked parameter shape;tests/integration/test_chat_api.py(or the SSE pin file) — asearch_documentscall streamsargument = pattern. - E2E (mandatory, house rule):
tests/e2e/test_search_tool.py, run in isolation (deterministic mock flow: the model searches, sees the match line, answers from it). - Coverage: >90% on
app/(validate.sh gate).
Completion Criteria
search_documentsis the third entry inAGENT_TOOLS; a model call withpattern(optionallysource+path) returns grep-style matches or a no-match line.- The UI shows
Brain is searching for '…'in the status line and a🔎 Searching for '<pattern>'tool line, persisted/restored like the other tool lines. uv run pytestgreen; coverage TOTAL >90%;uv run ruff check . && uv run pyrightclean.uv run pytest tests/e2e/test_search_tool.py -v --no-covgreen in isolation (DB up).- Regression E2E suites green in isolation:
test_agent_document_tools.py,test_agent_unlimited_tools.py. - One
--no-gpg-signcommit; phase dir moved to.agents/phases/complete/.
Locked decisions
- Owner-locked (2026-09-01, roadmap confirmation, A5): the match is a case-insensitive fixed substring (no regex — no ReDoS surface, a simple contract for the model); output is grep-style
source/path:LINE: textlines; 20 matches per call maximum (global cap across documents, in catalog order), each line truncated to 200 chars; a search does not add the document to the answer context (read_documentremains the only context-adder —holder.read_docsis untouched by a search). - Owner-locked (2026-09-01, roadmap confirmation, A6): the tool name is
search_documents(alongsidelist_documents/read_document). - Scope: search is offered on grounded (HIGH) turns only, exactly like the existing tools — deflected turns keep
tools=None(A8 byte-identical deflection path), andBOR_AGENT_MAX_ROUNDS=0stays the no-tools kill switch (phase 45).
Commit
git add -A .agents/ app/ tests/ frontend/ && git commit --no-gpg-sign -m "feat(agent): search_documents tool — the model can grep the indexed documents for an exact string"