chore(agent): phase roadmap from TODO.md, 5 phases
Protocol B append (phases 141-145) for the five TODO items: the read-range dedupe false "Already in context" bug (141), the stale KB overview recency ordering + honest intro (142), the mobile keyboard pop after answer completion (143), the document modal back-button history entry (144), and the git original-source links in answers + the doc modal (145). TODO.md cleared (items now live in .agents/phases/todo/). LLM-Generated: true
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
# Phase 142 — the KB overview stays fresh: newest documents survive the cap, and the model is told the outline is partial
|
||||
|
||||
**Source:** `TODO.md` L4 — "The agent is really struggling with questions like 'Summarize the last 10 phases of brain of Reese'. When I asked that to the current deployment it said 'I'll pull the details for the last 10 phases (104–113).' when it clearly had phase 137 loaded in the RAG… 'The <knowledge_base> section in the system prompt only generated summaries up to phase 113 at import time. The phases 128–137 exist as full markdown files… but their summaries weren't included in the initial KB index. This is a KB sync/generation gap, not a retrieval logic bug.'"
|
||||
**Story:** n/a (TODO-derived phase, Protocol B append).
|
||||
**Context:** `app/rag/overview.py` — `regenerate_overview` (the document-list query `order_by(Document.source, Document.path)` ~L232, best-effort change-gated per the module docstring), `build_overview_prompt` (one line per doc `source — path — title — {first summary line}`, capped at `max_chars` with the shared `TRUNCATION_MARKER`), `load_kb_overview`; `app/config.py` L216 — `overview_input_max_chars` (`BOR_OVERVIEW_INPUT_MAX_CHARS`, default 40 000); `app/rag/prompts.py` — `_KB_INTRO` (L128–132: literally says "(generated at import time)") and `build_kb_section` (L399+); `app/api/sync.py` ~L590 — the overview regeneration gate (`summary.added + summary.updated > 0` — the sync IS change-gated correctly; the staleness is inside the generation, not the trigger); `tests/e2e/test_kb_overview.py` (the mock's `KB_OVERVIEW_MODE` digest = first 8 tokens of the document list — ORDER-DEPENDENT, must be re-pinned), `tests/integration/test_import_docs_overview.py` (the import-time trigger), `app/models.py` — `Document.created_at` (L123 — the DOCUMENT's date, phase 106: git-history/mtime-derived and owner-correctable, the same date the `read` result's `date:` line shows — the "when is this document from" signal, exactly what "the last N …" means). Root cause (traced): on the owner's 1 000-doc KB the document list is ≈100k+ chars; the 40k cap cuts a LEXICOGRAPHIC prefix (`order_by (source, path)` — "113_…" sorts before "128_…", and "131_…"–"140_" sort after "13_…"), so the newest phases never reach the lite model and the stored outline confidently describes a KB that ends mid-11x; the model then anchors "the last 10 phases" on that stale image.
|
||||
**Locked (owner-confirmed 2026-09-30, chat):** recency ordering (newest documents first so they survive the cap; the cap itself is unchanged) + the honest `<knowledge_base>` intro (drops "(generated at import time)"; tells the model the outline is compact/possibly partial and to verify "latest/complete" questions with `ls`/`grep` first). No map-reduce rewrite this phase.
|
||||
|
||||
## Objective
|
||||
"Summarize the last 10 phases" stops being a guess: the overview's input is ordered newest-first so recent documents are the ones the outline covers when the cap bites, and the `<knowledge_base>` intro stops teaching the model that a partial outline is the whole KB — it now says the outline is a possibly-partial picture and points at `ls`/`grep` for "what's the latest / the complete set" questions.
|
||||
|
||||
## Dependencies
|
||||
- `141_read_range_dedupe_fix` (todo) — pipeline order only; no code dependency.
|
||||
|
||||
## Design (shared by all tasks — the executor reads this, not the chat)
|
||||
- **Ordering (task 02):** `regenerate_overview`'s query becomes `order_by(Document.created_at.desc(), Document.source, Document.path)` — newest documents FIRST (`created_at` is the document's date per phase 106 — the same date the `date:` line in a read result shows; the owner's phase-128–137 files carry newer git dates than the 113-era files, so they lead the list and survive the cap). The (source, path) secondary keys keep the tie-break deterministic for same-date docs (the common case within one commit/import — those fall back to the old lexicographic order, so same-date fixtures stay byte-identical). The cap (`BOR_OVERVIEW_INPUT_MAX_CHARS`, default 40 000) and the cap's cut+`TRUNCATION_MARKER` behavior are UNCHANGED (house truncation convention; the env knob stays the owner's lever). Zero other call-site changes: the sync's change-gate (task-31/32 trigger) already regenerates whenever the KB changes — the staleness was purely inside the list.
|
||||
- **Intro (task 03):** `_KB_INTRO` rewritten (it is a prompt constant — PLAN §4's convention: persona/prompt text changes ship with the phase's prompt tests; the `<knowledge_base>` TAG and the section's cap behavior in `build_kb_section` stay byte-stable): the outline is a COMPACT, possibly partial picture of the KB that favors RECENT documents — and for questions about the latest state or the complete set ("the last N …", "everything about …"), verify with `ls` (drill down) / `grep` before answering from the outline. One short paragraph, same tone as `SUGGEST_INTRO` (L152).
|
||||
- **Repro (task 01):** before any fix, a failing test proves the traced root cause: `build_overview_prompt` given a doc list whose joined lines exceed `max_chars` keeps the lexicographic FIRST lines and drops the last; and (integration) a KB where the newest docs' paths sort lexicographically LAST is regenerated with the pre-fix ordering → the stored outline (the mock's 8-token digest) names the OLD docs, not the new ones. The repro is written against the CURRENT code (red), then tasks 02/03 flip it green.
|
||||
- **E2E (task 04):** dedicated file — a fixture KB whose document count × line length exceeds a FORCED small cap (`BOR_OVERVIEW_INPUT_MAX_CHARS` set low via the E2E settings fixture) → regenerate through the mock → the stored outline's digest tokens come from the NEWEST document (recency ordering visible end-to-end through the real prompt path), and the served chat prompt's `<knowledge_base>` section carries the NEW intro copy (asserted from the echo path or the direct prompt-build surface — the phase-31 E2E already exposes the section to the UI via the first-bullet echo).
|
||||
|
||||
## Tasks
|
||||
1. `01_cap_ordering_repro.md` — the red repro tests (unit: cap keeps the lexicographic prefix; integration: newest-sorting-last docs missing from the regenerated outline).
|
||||
2. `02_recency_ordering.md` — the `created_at.desc()` ordering + the order-dependent digest re-pins.
|
||||
3. `03_honest_kb_intro.md` — the `_KB_INTRO` rewrite + the prompt tests.
|
||||
4. `04_e2e_outline_freshness.md` — the dedicated E2E (forced cap → newest doc in the outline; the new intro served).
|
||||
|
||||
## Testing & Quality
|
||||
- Unit: `build_overview_prompt` cap behavior unchanged (the cut + marker, the pathological budget); the new ordering (regeneration with mixed `created_at` docs → newest first; same-date tie-break by (source, path)); the intro copy pin.
|
||||
- Integration: the import-time trigger still regenerates exactly on KB change (the pre-existing `tests/integration/test_import_docs_overview.py` green); the repro flipped green.
|
||||
- E2E: one dedicated Playwright file, run in isolation, mock-only; `tests/e2e/test_kb_overview.py` (phase 31) green with re-pinned digest.
|
||||
- Coverage: **>90%** on new/modified code (`app/`).
|
||||
|
||||
## Completion Criteria
|
||||
- [ ] with the cap forced, the regenerated outline is built from the NEWEST documents (the 1 000-doc "ends at phase 113" failure mode is gone: the newest sort-last docs are the ones represented)
|
||||
- [ ] the served `<knowledge_base>` section carries the new honest intro (no "(generated at import time)"; the verify-with-`ls`/`grep` teaching present)
|
||||
- [ ] `uv run pytest --cov=app --cov-report=term-missing` green, `app/` coverage >90%; `uv run pytest tests/e2e/test_kb_overview_freshness.py -v --no-cov` green in isolation; `tests/e2e/test_kb_overview.py` still green in isolation; `uv run ruff check . && uv run pyright` clean
|
||||
- [ ] no behavior change in completed phases (the cap env knob, the change-gated trigger, the `build_kb_section` cap/marker behavior, the deflect path)
|
||||
Reference in New Issue
Block a user