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.
3.8 KiB
3.8 KiB
Task 04 — Mock Flow, Dedicated E2E Suite
Phase: 72_teaching_refusals · Story: .agents/user_stories/agent-document-tools.md
Objective
Prove the self-correction loop deterministically through the real UI: a mock-LLM flow
that reproduces the incident's ls(path='.') misuse, receives the teaching refusal,
corrects to a no-arg ls(), and answers from the catalog — a dedicated Playwright
suite pinning the two-round shape on the SSE wire. (The live real-model acceptance
gate is task 05 — this task is the deterministic half of the proof.)
Work
tests/e2e/mock_llm.py— one new deterministic flow, checked in the flow table before the plainTOOLS_TRIGGERflow (the trigger phrases are disjoint substrings; the ordering rule follows the phase-71 convention):LS_TEACH_TRIGGER = "list the files in this directory"— and the system prompt carries the<tools>section (grounded turn):- request 1 (tools offered, no
tool-role result in the messages yet): stream ONLYtool_callsdeltas —lswith{"path": "."}(synthetic idcall_0),finish_reason: "tool_calls", no content (the incident's misuse, deterministic); - request 2 (a
tool-role result present that is not a catalog listing — i.e. the teaching refusal): stream atool_callsdelta —lswith no arguments (idcall_1); - request 3 (a
tool-role result whose first line matches the^\d+ documents:catalog header): a deterministic content answer —These are the indexed documents: <first catalog line>(thesource: X | path: Y | title: Zline, parsed with the existing_CATALOG_LINE_REmachinery),finish_reason: "stop".
- request 1 (tools offered, no
- Update the module docstring's flow table with the phase-72 note.
tests/e2e/test_tool_path_teaching.py(NEW — the phase's dedicated suite, house pattern, run in isolation; DB up, mock LLM):- Import a small fixture document set (house fixture pattern: one source, two
documents with known
source/path/title) and ask a question containingLS_TEACH_TRIGGER. - Self-correction — the turn settles (composer re-enables,
doneobserved); the answer bubble contains the first document'ssource:andpath:fields (the catalog reached the model and landed in the answer); no error banner. - Two rounds on the wire (the house SSE-capture pattern): the
toolframes arrive in order — firstname:"ls"withargument:".", thenname:"ls"withargument:null— and there is no thirdtoolframe (the loop ended in one correction, not at the round cap). - No regression to the plain flow — a follow-up question containing
TOOLS_TRIGGER(the single-read flow) in the same session still settles with the read flow's answer (the new flow did not swallow the existing trigger).
- Import a small fixture document set (house fixture pattern: one source, two
documents with known
Testing & Quality
- E2E: the dedicated suite proves the loop shape (misuse → teaching refusal → corrected call → answer) through the real UI and the SSE wire; the existing regression E2E suites (mock-driven) stay green — run them as the regression check for this task.
- Coverage: unit/integration coverage of
app/stays >90% (this task adds test-only code;uv run pytest --cov=app --cov-report=term-missingas the check).
Completion Criteria
uv run pytest tests/e2e/test_tool_path_teaching.py -v --no-covgreen in isolation (DB up:podman compose up -d db, mock LLM)- Regression E2E suites green in isolation:
test_harness_aligned_tools.py,test_agent_document_tools.py,test_agent_unlimited_tools.py,test_search_tool.py,test_chat_rag.py uv run pytestgreen;uv run ruff check . && uv run pyrightclean- No commit in this task (the commit happens in task 05, after the real-model gate passes)