Skip to content

feat(compaction): bound the summary at 8k tokens and emit per-cut metrics (PR-2) - #1128

Closed
philmerrell wants to merge 1 commit into
feature/compaction-model-relative-thresholdsfrom
feature/compaction-summary-budget
Closed

philmerrell wants to merge 1 commit into
feature/compaction-model-relative-thresholdsfrom
feature/compaction-summary-budget

Conversation

@philmerrell

Copy link
Copy Markdown
Contributor

Stacked on #1125 (PR-1). Merge that first; this PR's diff is the one commit on top.

Why

The compaction summary is an unbounded join of AgentCore Long-Term Memory ConversationSummary records. In the #833 incident it was 165k chars (~40k tokens) against a 100k threshold, so compaction could never get back under the ceiling and every turn re-wrote a ~200k prefix. The 2026-09-15 prod audit found 23k–40k-token summaries in 6 of 20 audited sessions, and all 16 over-100k sessions were on the agent-cache bypass path where the restore slice already runs and is defeated by exactly this. Spiral-spec PR-2; thresholds spec §3.6.

What

  • compaction_summary.bound_summary() holds the persisted summary at COMPACTION_SUMMARY_TOKEN_BUDGET (8,000 tokens, chars/4 — the same estimate the admin SUMMARY_OVER_BUDGET diagnosis uses).
    • Within budget → unchanged (small sessions see no difference).
    • Over budget → one Nova Micro converse call, side-channel like titles and tool-batch summaries (never touches agent.messages), with a prompt that keeps standing user instructions, decisions, the current state of the work, open items and exact identifiers, and drops narration and superseded drafts.
    • Model failure, a ceiling-hit generation or an overshoot → newest-first truncation: keep the newest records that fit; if none fit, the tail of the newest. Never oldest-first.
    • Runs once at checkpoint advance (the turn already paying a prefix re-write) and the result is persisted verbatim, so the byte-stability contract is unchanged.
  • Kill switch AGENTCORE_MEMORY_COMPACTION_SUMMARY_MODEL_ENABLED=false (truncate only); AGENTCORE_MEMORY_COMPACTION_SUMMARY_MODEL_ID selects the model.
  • Provenance on the persisted compaction.policy map: summarySource, summaryOutcome, summaryTokensBefore/After, summaryTokenBudget.
  • One content-free EMF record per cut in AgentCoreStack/Compaction: CompactionCut, CompactionForced, CompactionInputTokens, CompactionRetainedTokens, CompactionSummaryTokens, CompactionSummaryOverBudget, with policy source / window / ceiling / floor / summary outcome as queryable properties. No conversation or summary text is emitted. Silenced by PROMPT_CACHE_OBSERVABILITY_ENABLED=false with the rest of the cost observability layer.

Tests

Newest-first truncation; within-budget passthrough; model compression; model failure, ceiling-hit and overshoot fallbacks; kill switch; env loading; an oversized LTM join bounded and persisted through update_after_turn with the restore prepending the same bytes; EMF record shape and kill switch. Full backend suite: 8643 passed, 3 skipped.

Not in this PR

Applying the pending cut in place on warm agents with paid-when-free scheduling (PR-3), and offload escalation when the protected tail alone exceeds the floor (PR-4). The spec's §7.2 lists the further data points worth collecting.

🤖 Generated with Claude Code

…rics (PR-2)

The compaction summary was an unbounded join of AgentCore LTM
ConversationSummary records (165k chars / ~40k tokens in the #833 incident):
a summary that is 40% of the threshold guarantees compaction can never get
back under it. Spiral-spec PR-2; thresholds spec §3.6 / §7.1.

- compaction_summary.bound_summary(): hold the persisted summary at
  COMPACTION_SUMMARY_TOKEN_BUDGET (8,000 tokens, chars/4 — the same estimate
  the admin SUMMARY_OVER_BUDGET diagnosis uses). Within budget → unchanged.
  Over budget → one Nova Micro converse call (side-channel, never touches
  agent.messages) with a prompt that keeps standing instructions, decisions,
  current state of the work, open items and exact identifiers, and drops
  narration and superseded drafts. Model failure, a ceiling-hit generation
  or an overshoot → newest-first truncation (keep the newest records that
  fit; if none fit, the tail of the newest). Runs once at checkpoint advance
  — the turn that already pays a prefix re-write — and the result is
  persisted verbatim, so the byte-stability contract is unchanged.
- Kill switch AGENTCORE_MEMORY_COMPACTION_SUMMARY_MODEL_ENABLED=false skips
  the model and truncates; AGENTCORE_MEMORY_COMPACTION_SUMMARY_MODEL_ID
  selects the model.
- Provenance on the persisted compaction.policy map: summarySource
  (ltm|fallback), summaryOutcome, summaryTokensBefore/After,
  summaryTokenBudget.
- One content-free EMF record per cut in AgentCoreStack/Compaction:
  CompactionCut, CompactionForced, CompactionInputTokens,
  CompactionRetainedTokens, CompactionSummaryTokens,
  CompactionSummaryOverBudget, with policySource/window/ceiling/floor/
  summaryOutcome as queryable properties. Silenced by
  PROMPT_CACHE_OBSERVABILITY_ENABLED=false with the rest of the layer.
- forced flag narrowed to "ran while disarmed" (same hunk as the PR-1 fix).

Tests: newest-first truncation, within-budget passthrough, model
compression, model failure / ceiling / overshoot fallbacks, kill switch,
env loading, oversized LTM join bounded and persisted through
update_after_turn, EMF record shape and kill switch.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@philmerrell
philmerrell force-pushed the feature/compaction-summary-budget branch from c7b96a4 to 667d43c Compare September 16, 2026 05:05
@philmerrell
philmerrell deleted the branch feature/compaction-model-relative-thresholds September 16, 2026 14:45
philmerrell added a commit that referenced this pull request Sep 16, 2026
Resolves the one integration conflict with the compaction stack (#1125,
#1128, #1129, #1131, #1132) in ``update_after_turn``.

Both sides append to the same post-cut block: the stack added
``_emit_compaction_metrics`` and refactored to a local ``state`` alias
(``state = self.compaction_state``, so the two save calls were already
equivalent); this branch added the ledger's ``checkpoint`` event. Keep
both, and route the event through the stack's ``_record_ledger_event``
seam instead of calling ``record_compaction_event`` directly, so the
recorder stays resolved-by-attribute like every other cut decision.

``test_no_ledger_is_a_noop`` asserted the recorder was *absent* — true
only while this branch was unmerged. It now simulates a ledger-less
build via ``monkeypatch.delattr`` so it still guards the getattr seam.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@philmerrell

Copy link
Copy Markdown
Contributor Author

Landed on develop via #1129, not through this PR.

Merging #1125 with --delete-branch removed this PR's base branch (feature/compaction-model-relative-thresholds), which auto-closed this PR. GitHub won't reopen a PR whose base branch is gone, so it can't be retargeted to develop and merged on its own.

No work was lost: this stack was linear, so PR-2's commit 667d43c4 ("bound the summary at 8k tokens and emit per-cut metrics") was already contained in #1129's branch and merged with it. Verified on develop:

  • git merge-base --is-ancestor 667d43c4 origin/develop → true
  • summary_token_budget / COMPACTION_SUMMARY_TOKEN_BUDGET (default 8000) present in compaction_models.py

Closing as superseded by #1129.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant