Compaction: the model's notes replace the middle of a long run (#158)
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 1m57s
CI / e2e (pull_request) Successful in 4m57s

Before each driver call, over compact_at (0.6) of the prompt budget, the
same model is asked for notes over the very request it was about to get,
plus one message, so the server's cache covers all but the notes. The notes,
with code's list of the calls they replace, become a compaction event that
renders in place of its range, keeping word for word what started the work
and the last few turns. Rare, large steps keep the prompt's start unchanged
between them. A later compaction folds in the earlier; one from an earlier
run's view is hidden.

Failed notes are logged and fitting takes over. Fitting now applies
compactions before it measures, records what it cuts (Trim) and logs each
cut as context.trimmed. Wired into both agents' drivers and the benchmark;
nisse-dev's helper loop is a follow-up.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J5Utyfzw7gF7UhjRDEtjTk
This commit is contained in:
dongarandClaude Opus 5.5 committed 2026-10-17 22:51:36 +00:00
1 parent fa8d93cf41
commit cb638fe687
20 files changed
+952 -44

No files matched your search

+10 -3
View File
@@ -10,6 +10,7 @@ from nisse_core.knowledge.vault import Vault
from nisse_core.loop.driver import Driver
from nisse_core.loop.expert import ExpertDesk
from nisse_core.models.catalog import ModelCatalog
from nisse_core.models.compact import Compactor
from nisse_core.models.context import BudgetedClient
from nisse_core.models.interruptible import Interruptible
from nisse_core.models.llm import LiteLLMClient
@@ -65,7 +66,7 @@ def check_access(
return account, repos
def llm_client(config: ReviewConfig) -> BudgetedClient:
def llm_client(config: ReviewConfig, oplog: OpLog | None = None) -> BudgetedClient:
"""The one route to the models, every request fitted to its window
(L6), against what LiteLLM says the configured models are (L9)."""
catalog = ModelCatalog.discover(config.llm_base_url, config.llm_token)
@@ -92,6 +93,7 @@ def llm_client(config: ReviewConfig) -> BudgetedClient:
keep_recent_turns=tuning.keep_recent_turns,
safety_margin=tuning.safety_margin,
enforces_tool_choice=lambda m: catalog.facts(m).supports_tool_choice is True,
oplog=oplog, # a cut that fitting has to make is logged
)
@@ -106,7 +108,7 @@ def build(
gitea = gitea or GiteaClient(config.gitea_url, config.gitea_token)
account, repos = check_access(gitea, config.repositories, oplog)
# Every model call, the reviewer's and ask_expert's, can be broken off.
model = Interruptible(client if client is not None else llm_client(config)) # type: ignore[arg-type]
model = Interruptible(client if client is not None else llm_client(config, oplog)) # type: ignore[arg-type]
config.state_db.parent.mkdir(parents=True, exist_ok=True)
store = StateStore(config.state_db, oplog=oplog)
vault = (
@@ -129,7 +131,12 @@ def build(
reviewer = Reviewer(
store,
gitea,
Driver(model, config.roles.resolve("driver"), cancel=cancel), # type: ignore[arg-type]
Driver(
model, # type: ignore[arg-type]
config.roles.resolve("driver"),
cancel=cancel,
compactor=Compactor(model, tuning.context.compaction(), oplog=oplog),
),
account=account,
gitea_url=config.gitea_url,
oplog=oplog,
+15
View File
@@ -22,6 +22,7 @@ from nisse_core.config import (
read_file,
resolve,
)
from nisse_core.models.compact import CompactionTuning, check_tuning
from nisse_core.models.llm import (
DEFAULT_MAX_OUTPUT_TOKENS,
DEFAULT_REQUEST_TIMEOUT_S,
@@ -72,6 +73,19 @@ class ExpertTuning:
class ContextTuning:
keep_recent_turns: int = 6
safety_margin: float = 0.1
# Compaction (L6): over compact_at of the prompt budget, the model's
# notes replace the middle of the run, keeping at most compact_keep_turns
# recent turns, in compact_to of it. compact_at 0: never.
compact_at: float = field(default=0.6, metadata={"zero": True})
compact_to: float = 0.25
compact_keep_turns: int = 3
def compaction(self) -> CompactionTuning:
return CompactionTuning(
compact_at=self.compact_at,
compact_to=self.compact_to,
keep_turns=self.compact_keep_turns,
)
@dataclass(frozen=True)
@@ -118,6 +132,7 @@ class ReviewConfig:
tuning = parse_tuning(tree.get("tuning"), Tuning, errors)
if tuning.context.safety_margin >= 1:
errors.append("tuning.context.safety_margin: must be less than 1")
check_tuning(tuning.context.compaction(), "tuning.context", errors)
vault = parse_vault(tree.get("vault"), errors)
flat = flatten({k: v for k, v in tree.items() if k != "vault"})
flat.pop("tuning", None)
+24
View File
@@ -494,6 +494,30 @@ def test_the_ci_waits_may_be_zero(tmp_path: Path) -> None:
assert (review.ci_wait_s, review.ci_grace_s) == (0, 0)
def test_compaction_is_tuned_in_the_config(tmp_path: Path) -> None:
from nisse_core.config import ConfigError
from nisse_review.config import ReviewConfig
tree: dict[str, Any] = {
"state_db": str(tmp_path / "s.db"),
"llm": {"base_url": "http://llm", "token": "k"},
"roles": {"reviewer": "local", "expert": "frontier"},
"gitea": {"url": "https://g", "token": "t", "operators": ["don"]},
}
assert (
ReviewConfig.from_tree(tree, {}).tuning.context.compaction().compact_at == 0.6
)
tuned = {
**tree,
"tuning": {"context": {"compact_at": 0.7, "compact_keep_turns": 2}},
}
compaction = ReviewConfig.from_tree(tuned, {}).tuning.context.compaction()
assert (compaction.compact_at, compaction.keep_turns) == (0.7, 2)
wrong = {**tree, "tuning": {"context": {"compact_at": 0.2, "compact_to": 0.3}}}
with pytest.raises(ConfigError, match="compact_to must be less than compact_at"):
ReviewConfig.from_tree(wrong, {})
def test_diff_lines_are_the_heads_lines_a_diff_shows() -> None:
diff = (
"--- a/x.py\n+++ b/x.py\n@@ -1,3 +1,4 @@\n a\n-b\n+c\n+d\n e\n"
+2
View File
@@ -163,6 +163,8 @@ def test_build_wires_a_stop_to_every_model_call(tmp_path: Path) -> None:
try:
driver = built.scheduler._reviewer._driver
assert driver._client is built.model
# Compaction's notes are a model call a stop breaks off too.
assert driver._compactor._client is built.model
assert built.scheduler._reviewer._expert._client is built.model
built.stop() # no call in flight: nothing raised
assert driver._cancel.requested and built.scheduler._stop.is_set()