feat(runtime): replace per-round history trimming with threshold-triggered LLM compaction

Align native context management with the Claude Code / Codex model:
entry-capped tool results, history frozen below the threshold, one
high-quality summary at the wall — instead of the old pipeline that
microcompacted old messages from 60% usage and hid everything past 40
messages behind a snip marker with no summary.

- context pipeline: history below the hard threshold is never rewritten
  (model quality and prompt-cache prefixes depend on byte-identical old
  messages); the 60% tool-aware microcompact and the 40-message history
  snip move to an emergency-only fallback used under overflow pressure
  when the summarizer is unavailable or circuit-broken.
- durable compaction (was a stub): at usage >= context_guard.hard_threshold
  (now 0.90, soft_threshold removed) the old span is folded into a
  9-section summary via the new HistoryCompactor.summarize_runtime_history,
  keeping the system head, the seed user request verbatim on every round
  (injected session-memory/artifact messages shift the stale
  base_prefix_len, so the fold start is structure-aware), and a
  pairing-safe recent tail. A previous summary stays foldable, so exactly
  one summary exists at a time and rounds chain.
- token accounting anchors on the provider-reported prompt size of the
  latest request (max with the local estimate).
- reactive_compaction.circuit_breaker_failures (previously unread) now
  stops repeated summarizer failures; provider overflow errors retry
  through the same pipeline, summary-first.
- tool-result budget clip keeps head and tail instead of tail-chopping.
- chat-side transcripts get the same treatment: new
  MemoryManager.maybe_compact_session_history wires the threshold-gated
  maybe_compact_session into secretary, office_ui dispatcher, and
  context_loader before prompt building, closing the unbounded-growth
  path; dead no-op compactor entries (maybe_compact_after_message,
  should_compact_prompt) removed.

Verified by 13 new tests (history sanctity below threshold, multi-round
single-summary/seed-verbatim/chain invariants, breaker, emergency
fallback, provider-overflow end-to-end recovery) plus a live-provider
probe: multi-round compaction with the model completing correctly from
summarized context. Full suite: 1859 passed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
LZH-YS1998
2026-07-27 00:04:42 +08:00
parent 76c530a9e5
commit 6fc5ad6be9
8 changed files with 848 additions and 53 deletions
+27
View File
@@ -65,6 +65,33 @@ class MemoryManager:
def set_history_compactor(self, compactor: Any | None) -> None:
self.history_compactor = compactor
async def maybe_compact_session_history(
self,
session_id: str,
project_id: str | None = None,
) -> bool:
"""Threshold-gated session-transcript compaction.
Chat-style callers invoke this before building prompt context so a
long transcript is folded into a summary snapshot instead of growing
without bound. Best-effort: failures never block prompt building.
"""
compactor = self.history_compactor
maybe_compact = getattr(compactor, "maybe_compact_session", None) if compactor else None
if not callable(maybe_compact) or not session_id:
return False
try:
return bool(
await maybe_compact(
project_id=self._resolve_project_id(project_id),
session_id=session_id,
force=False,
)
)
except Exception as exc:
logger.debug(f"Session history compaction skipped: {exc}")
return False
def _resolve_project_id(self, project_id: str | None = None) -> str:
return str(project_id or self.project_id or "default")