phase: 160_tool_budget_telemetry
All green. Final report: **Phase 160 completion pass — verified (final verification; all 5 tasks already in `complete/`)** - **Verified, no code defects found:** budget trailers (`TOOL_BUDGET_REMAINING/LAST/SPENT`), cap-path rejection + `ROUND_CAP_FALLBACK_ANSWER` + `ROUND_CAP_WRAPUP_INSTRUCTION`, `done.budget_exhausted` (ABSENT-when-false), `rounds=/cap_hit=` log fields, `agent_max_rounds=10` default, frontend Continue button + restore + shared-page exclusion, mock markers. - **Tests/lint:** `uv run pytest` 153/153 green (EXIT 0); `uv run pytest --cov=app --cov-report=term-missing` TOTAL **99%** (>90%); `uv run ruff check .` clean; `uv run pyright` 0 errors/0 warnings. - **Criteria:** ① decreasing trailer pinned by unit tests (9→…→spent) ✅ ② cap path ends readable, no tool lines after cap (E2E) ✅ ③ `budget_exhausted` true-only-on-cap, absent otherwise (integration) ✅ ④ Continue button → one fresh `continue` turn, fresh budget, record intact (E2E) ✅ ⑤ flag survives refresh; shared chat never renders it (`shared.js` has no `budget_exhausted` path) ✅ ⑥ `max_rounds=0` byte-identical single `tools=None` request; default still 10 ✅ ⑦ full suite/coverage/lint green ✅ ⑧ `test_tool_budget_telemetry.py` **7/7** in isolation; all 6 regression E2E suites green in isolation ✅ ⑨ commit + phase move = harness (I make no git history/index changes). - **Docs:** README.md — fixed: the "Continue researching" button and cap behavior were undocumented (added a Chat-section paragraph + an Agent-tools-section paragraph, incl. the phase-71 recorded-revision note); env-var table, commands, and architecture all verified consistent otherwise. AGENTS.md — verified consistent, no changes needed. - **Decisions:** no code changes required — phase was fully implemented; only README docs added. No live-infrastructure touched. - **Next pending phase:** `161_reasoning_bubble`.
This commit is contained in:
@@ -386,6 +386,7 @@ def test_realistic_bor_chat_v1_payload_round_trips() -> None:
|
||||
"stopped": None,
|
||||
"failed": None,
|
||||
"error": None,
|
||||
"budget_exhausted": None,
|
||||
},
|
||||
{
|
||||
"who": "brain",
|
||||
@@ -434,6 +435,7 @@ def test_realistic_bor_chat_v1_payload_round_trips() -> None:
|
||||
"stopped": None,
|
||||
"failed": None,
|
||||
"error": None,
|
||||
"budget_exhausted": None,
|
||||
},
|
||||
{
|
||||
"who": "user",
|
||||
@@ -447,6 +449,7 @@ def test_realistic_bor_chat_v1_payload_round_trips() -> None:
|
||||
"stopped": None,
|
||||
"failed": None,
|
||||
"error": None,
|
||||
"budget_exhausted": None,
|
||||
},
|
||||
{
|
||||
"who": "brain",
|
||||
@@ -460,6 +463,7 @@ def test_realistic_bor_chat_v1_payload_round_trips() -> None:
|
||||
"stopped": True, # the owner stopped the generation mid-answer
|
||||
"failed": None,
|
||||
"error": None,
|
||||
"budget_exhausted": None,
|
||||
},
|
||||
]
|
||||
|
||||
@@ -468,7 +472,10 @@ def test_realistic_bor_chat_v1_payload_round_trips() -> None:
|
||||
dumped = payload.model_dump()
|
||||
# the stored shape is byte-identical: every message dict — None-keys
|
||||
# and all — round-trips unchanged (and the top-level record keeps its
|
||||
# ``share`` default).
|
||||
# ``share`` default). The phase-160 additive ``budget_exhausted``
|
||||
# marker joins as an explicit null (the phase-120 ``failed`` /
|
||||
# phase-48 ``stopped`` style — ``None`` flows as null, the
|
||||
# phase-50 byte-identical contract covers the new key).
|
||||
assert dumped["messages"] == messages
|
||||
assert dumped["share"] is False
|
||||
|
||||
@@ -488,8 +495,11 @@ def test_realistic_payload_round_trips_through_update_model() -> None:
|
||||
"stopped": None,
|
||||
"failed": None,
|
||||
"error": None,
|
||||
"budget_exhausted": None,
|
||||
}
|
||||
payload = SavedChatUpdate.model_validate({"messages": [msg]})
|
||||
# Identical stored shape (the phase-160 additive ``budget_exhausted``
|
||||
# marker rides as an explicit null — the phase-120/48 style).
|
||||
assert payload.model_dump()["messages"] == [msg]
|
||||
|
||||
|
||||
@@ -708,8 +718,11 @@ def test_segments_absent_by_default_and_omitted_in_dump() -> None:
|
||||
dumped = msg.model_dump()
|
||||
assert "segments" not in dumped
|
||||
# Byte-identical to the pre-phase stored shape: every bor.chat.v1
|
||||
# optional key present (null), the three serializer-popped keys
|
||||
# (image / answer_image / segments) absent.
|
||||
# optional key present (null) — the phase-160 additive
|
||||
# ``budget_exhausted`` marker included (the phase-120 ``failed`` /
|
||||
# phase-48 ``stopped`` style — explicit null where absent) — and
|
||||
# the three serializer-popped keys (image / answer_image /
|
||||
# segments) absent.
|
||||
assert dumped == {
|
||||
"who": "brain",
|
||||
"text": "ok",
|
||||
@@ -722,6 +735,7 @@ def test_segments_absent_by_default_and_omitted_in_dump() -> None:
|
||||
"stopped": None,
|
||||
"failed": None,
|
||||
"error": None,
|
||||
"budget_exhausted": None,
|
||||
}
|
||||
# An explicit empty list is a different thing: [] is a value, so it
|
||||
# round-trips as [] (present, empty — the pop is None-gated only).
|
||||
|
||||
Reference in New Issue
Block a user