phase: 95_read_truncation_cap
All gates are green. Final verification is complete — no fixes were needed; the phase is fully implemented and passing. **Phase 95 final verification report** - Verified all 3 tasks already implemented (task files in `complete/`): read cap + notice + `ToolResultPiece` (task 01), `tool_result` SSE + live/saved/shared marker (task 02), E2E suite (task 03). - Code checks passed: `BOR_READ_MAX_CHARS` (default 128 000, validator), pinned `READ_TRUNCATION_NOTICE` + `TRUNCATION_MARKER`, byte-identical at/under-cap read, holder `read_truncations` (no `tool_calls` impact), piece order (after `tool`, before next round), `ChatToolResultEvent`, `ToolCall` back-compat fields, `app.js` live/restore handlers, `shared.js`, `.truncated-note` CSS, `.env.example` entry, mock-LLM echo script. - **Tests:** `uv run pytest` → 1966 passed; `uv run pytest --cov=app --cov-report=term-missing` → all green, TOTAL **99%** (>90% gate). - **E2E:** `uv run pytest tests/e2e/test_read_truncation_cap.py -v --no-cov` → **3 passed** (frame order + live marker + LLM notice via echo; save→shared fidelity; short-read control). - **Regression (isolated):** `test_agent_document_tools` 4 ✓, `test_chat_history` 5 ✓, `test_share_chat` 4 ✓, `test_big_read_progress` 4 ✓, `test_stop_generation` 3 ✓. - **Lint/types:** `uv run ruff check .` → All checks passed; `uv run pyright` → 0 errors, 0 warnings. **Completion criteria:** ① over-cap read → first-cap-chars + marker + pinned notice — ✓ (unit-pinned: at-cap/cap+1/notice tests); ② user marker live/saved/shared — ✓ (E2E + frontend tests); ③ at/under cap byte-identical, no frame — ✓ (unit + control E2E); ④ top-2 `<documents>` retrieval untouched — ✓ (`app/rag/retriever.py` unmodified vs HEAD); ⑤ suite green, >90% coverage, ruff+pyright clean — ✓; ⑥ no completed-phase behavior change — ✓ (all gates green; commit left to harness per pass rules). - No defects found; no changes made this pass. Next pending phase: none in `todo/` (96 is the next free number).
This commit is contained in:
@@ -152,6 +152,23 @@ class Settings(BaseSettings):
|
||||
#: request with ``tools=None`` (the pre-phase-37 path — the kill
|
||||
#: switch). Negative values are rejected at startup (validator).
|
||||
agent_max_rounds: int = 10
|
||||
#: Cap in characters on the agent ``read`` tool's result (phase 95,
|
||||
#: ``BOR_READ_MAX_CHARS``): a document LONGER than this is cut at the
|
||||
#: cap and the shared ``[…truncated…]`` marker plus the grep-pointer
|
||||
#: notice (``app.rag.agent``) are appended; a document at or under the
|
||||
#: cap is read whole, byte-identical to the pre-phase-95 result. Spec
|
||||
#: rationale (pinned): 128 000 chars ≈ **32 000 tokens** at the
|
||||
#: ~4-chars/token house estimate (``app.rag.llm``'s embed batching
|
||||
#: notes ~3 chars/token for code-dense text, 4 for prose) — a quarter
|
||||
#: of the 128k-token **minimum** context the owner's LLMs all have, so
|
||||
#: a truncated read still leaves ~96k tokens for the system prompt, the
|
||||
#: top-2 ``<documents>``, the tool rounds, and the 32 768-token answer
|
||||
#: cap (``max_output_tokens``). Char-based (no tokenizer in the repo —
|
||||
#: the ``BOR_SUMMARY_MAX_CHARS`` precedent) and env-tunable in both
|
||||
#: directions. This is the ONLY truncated read path (owner permission
|
||||
#: 2026-09-10, ``TODO.md`` L5): A7's never-truncated contract is for
|
||||
#: the retrieval ``<documents>`` path, which stays whole.
|
||||
read_max_chars: int = 128_000
|
||||
|
||||
# --- Hybrid retrieval (A7, revised 2026-08-21) ---
|
||||
# cosine top-N ∪ Postgres FTS top-N, fused with Reciprocal Rank Fusion
|
||||
@@ -274,6 +291,17 @@ class Settings(BaseSettings):
|
||||
raise ValueError("agent_max_rounds must be >= 0 (0 = no tools)")
|
||||
return v
|
||||
|
||||
@field_validator("read_max_chars")
|
||||
@classmethod
|
||||
def _read_max_chars_non_negative(cls, v: int) -> int:
|
||||
"""A negative cap is a typo — it would slice from the END of the
|
||||
content (negative indexing) instead of failing. Fail loud at
|
||||
startup (the ``agent_max_rounds`` pattern). ``0`` is legal (every
|
||||
non-empty read truncates to the marker + notice)."""
|
||||
if v < 0:
|
||||
raise ValueError("read_max_chars must be >= 0 (chars)")
|
||||
return v
|
||||
|
||||
@field_validator("llm_retries")
|
||||
@classmethod
|
||||
def _llm_retries_non_negative(cls, v: int) -> int:
|
||||
|
||||
Reference in New Issue
Block a user