feat(ai): summariser selector + summarisation prompt builder - #455
Merged
Conversation
Compaction epic slice A (of ~3): land the two pure building blocks that later slices consume, without wiring them into the run loop. - resolve_summary_model: pick the model/provider that performs the summarisation. Precedence: explicit override > COTHIS_SUMMARY_MODEL env var > the session pair. A "provider/model" spec splits on the first slash; a bare model inherits the session provider. Empty or whitespace-only specs fall through to the next level. - build_summarisation_request: render a window of older turns into a single-user-turn summarisation request (a system instruction plus a labelled transcript). Inline-render (not pass-through) so no tool schemas are shipped and it stays provider-agnostic. tool_use and tool_result semantics are preserved; thinking blocks are skipped; the oldest turns are truncated above a char cap so the most recent survive verbatim. Pure module (no I/O, no SDK imports) -- preserves the lazy-client invariant. Self-contained and unit-testable. The eviction policy (slice B) and the run-loop wiring via SessionPhase="compaction" (slice C) are deferred. Iteration I25 (compaction epic slice A, built on the context-budget primitive). 30 new tests.
gemone
force-pushed
the
feat/compaction-summariser
branch
from
August 6, 2026 20:22
87ff9df to
6486e0d
Compare
gemone
added a commit
that referenced
this pull request
Aug 7, 2026
Compaction epic slice B (of ~3): the pure eviction policy that decides WHICH older turns to compact vs retain, driven by context-window pressure. plan_eviction(*, messages, budget, min_retained_turns=4, high_target_ratio=0.75, critical_floor_ratio=0.50, ...) -> EvictionDecision(window, retained, pressure, evicted_token_estimate, reason). The window is the contiguous prefix slice A's build_summarisation_request consumes; retained is the verbatim tail. Pressure gate: no eviction when the budget is unknown or under NONE/LOW/MEDIUM (MEDIUM is "plan, not act"); HIGH evicts the smallest safe cut that brings the retained estimate down to high_target_ratio*capacity; CRITICAL targets the lower critical_floor_ratio*capacity. Both fall back to the largest safe cut (down to the retention floor) if no cut meets the target -- CRITICAL evicts a token-superset of HIGH (monotonic). The cut respects three invariants: tool_use/tool_result pair closure (no dangling ids -- a safe-cut walk), a retention floor (the last min_retained_turns turn-groups always stay verbatim), and strict role alternation (inherited from the storage layer). Pure module (no I/O, no SDK imports, no cothis.agent import -- duck-typed on list[dict]). Self-contained and unit-testable; run-loop wiring, the actual summarisation call, summary persistence, and auto-trigger are deferred to slice C. 18 new tests. Iteration I26 (compaction epic slice B, built on slice A #455 + the context-budget primitive #448).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Compaction epic slice A (of ~3): land the two pure building blocks for
conversation compaction — a summariser selector and a summarisation
prompt builder — as a self-contained, unit-testable module. Built on
the context-budget primitive (#448). No run-loop wiring yet.
Changes
New pure module
packages/cothis-ai/src/cothis/ai/compaction.py(siblingto
context_budget.py; no I/O, no SDK imports — preserves thelazy-client invariant):
resolve_summary_model— picks the model/provider that performsthe summarisation. Precedence: explicit
overridearg >COTHIS_SUMMARY_MODELenv var > the session pair. A"provider/model"spec splits on the first slash; a bare model inherits the session
provider; empty/whitespace specs fall through to the next level.
build_summarisation_request— renders a window of older turnsinto a one-shot summarisation request (a system instruction + a single
user turn with a labelled transcript). Inline-render (not pass-through)
so no tool schemas are shipped and it stays provider-agnostic.
tool_use/tool_resultsemantics are preserved;thinkingblocks areskipped; the oldest turns are truncated above a char cap so the most
recent survive verbatim. Empty window does not crash.
Re-exported the four new symbols (
SummaryTarget,SummarisationRequest,resolve_summary_model,build_summarisation_request) fromcothis.ai.__init__.Contracts preserved
Pure additions only — no changes to the budget primitive, the agent run
loop, provider call semantics, or any existing test. The eviction policy
(slice B) and the run-loop wiring via
SessionPhase="compaction"(slice C)are deferred.
Tests
New
tests/test_compaction.py— 30 tests: selector precedence / slash-parse / fallback / env-not-read-when-override-set, and builder shape /
tool semantics / thinking-skip / length-cap (oldest truncated, recent
preserved) / empty window /
system_textoverride, plus a static sourceguard that the module has no top-level provider-SDK import. Full suite:
uv run pytest -q→ 1545 passed (2 pre-existing local-only environmentalflakes deselected; both reproduce on clean
main).CI
uv run ruff check— clean.uv run ty check— clean.uv run pytest—green (see Tests).