Skip to content

feat(context): preserve verbatim tail-anchor across compaction#29918

Merged
dvargasfuertes merged 1 commit into
mainfrom
apollo/compact-preserve-tail-anchor
May 8, 2026
Merged

feat(context): preserve verbatim tail-anchor across compaction#29918
dvargasfuertes merged 1 commit into
mainfrom
apollo/compact-preserve-tail-anchor

Conversation

@vellum-apollo-bot
Copy link
Copy Markdown
Contributor

@vellum-apollo-bot vellum-apollo-bot Bot commented May 8, 2026

What

Force-keep the last assistant text from the compactable region by splicing it verbatim into the post-compaction summary message as a tag-wrapped <verbatim_tail>…</verbatim_tail> block.

Why

When _maybeCompact runs in the middle of a long agent work span, the LLM-produced summary doesn't reliably preserve the model's most recent self-narration ("Next step: …", "About to …"). The post-compaction model then falls back to a "where am I?" recovery shape — re-checking NOW.md / scratch/ / signals/ — and drifts off the active thread.

This was the structural root cause of the May 7 surface-action conversation drift: while iterating on PR #29895, compaction fired at 20:58:31 UTC; the assistant's prior turn had stated "next step: file the SSE followup as promised", but that statement landed in the compactable region and was summarized away. The post-compaction model fell into a heartbeat-shaped recovery (ls signals/, ls scratch/, cat HEARTBEAT.md, scan PRs) and drifted onto unrelated bot feedback on PRs #6227 and #29900.

How

In assistant/src/context/window-manager.ts:

  • New extractTailAssistantText(messages, maxChars=1500): walks compactableMessages backward, returns the trimmed text of the most recent assistant message that has at least one non-empty text block. Skips tool_use / tool_result / image / unknown blocks. When the tail exceeds maxChars, clamps from the START with a [...truncated] prefix so the END (where "next step" lines land) wins. Returns null when nothing eligible is found — the caller treats that as "no anchor to splice".
  • New appendTailAnchorToSummary(summary, tailText): idempotent splice. Tag-wrapped block (<verbatim_tail>) is structurally distinct from any ## section the LLM might produce, so it survives clampSummaryAtSectionBoundary (which only runs on the LLM summary, before the splice). If the existing summary already contains a <verbatim_tail> block (e.g. carried forward as existingSummary from a prior compaction), it's replaced rather than stacked.
  • In _maybeCompact: extract tailAnchorText from compactableMessages, splice via appendTailAnchorToSummary when non-null, fall through otherwise. The spliced summary flows into both createContextSummaryMessage (in-context user-role message) and the summaryText returned in the result — so updateConversationContextWindow (DB persistence) and the context_compacted client event both see the anchored tail.

Tests

3 new test groups in assistant/src/__tests__/context-window-manager.test.ts, all passing:

  • describe("extractTailAssistantText") — 5 tests: returns last assistant text; null when none; skips tool_use-only assistants; clamps long text preserving END; ignores whitespace-only.
  • describe("appendTailAnchorToSummary") — 2 tests: appends tag-wrapped block; idempotent (replaces prior tail).
  • describe("compaction tail-anchor") — 3 integration tests via ContextWindowManager.maybeCompact: splices the last compactable assistant text into the summary message + summaryText; omits gracefully when compactable region has only tool_use assistants; clamps long tails preserving the END marker.

Full suite: 61/61 pass on bun test src/__tests__/context-window-manager.test.ts.

Notes

  • This is the structural fix for the compaction drift class. A complementary lighter prompt-side fix (adding "Immediate next action: …" guidance to SUMMARY_PROMPT_FALLBACK and prompts/compact.md) is worth considering as a follow-up — they're orthogonal: the prompt change asks the LLM nicely, the verbatim splice guarantees it.
  • 1500 chars (~375 tokens) is a soft default. Most assistant turns fit comfortably; long ones get the END preserved which is where the "next step" sentence almost always lives.
  • The anchor is also surfaced verbatim through summaryText, so observability/debug consumers (DB row, context_compacted event) match what's in-context.

Open in Devin Review

When `_maybeCompact` runs in the middle of a long agent work span, the
LLM-produced summary doesn't always preserve the model's most recent
self-narration ("Next step: ...", "About to ..."). The post-compaction
model then falls back to a "where am I?" recovery shape — re-checking
NOW.md / scratch / signals — and drifts off the active thread.

Force-keep the last assistant text from the compactable region by
splicing it verbatim into the summary message as a tag-wrapped
`<verbatim_tail>...</verbatim_tail>` block. Hard cap at 1500 chars,
clamped from the START so the END (where "next step" lines land) wins.
Idempotent: a prior tail block in `existingSummary` is replaced, not
stacked, so successive compactions don't accumulate stale tails.

The spliced summary flows through both `createContextSummaryMessage`
(the in-context user-role message) and the `summaryText` returned in
the result, so `updateConversationContextWindow` (DB persistence) and
the `context_compacted` client event both surface the anchored tail.

Tests: 2 new tail-anchor unit tests + 3 integration tests covering the
splice, the no-eligible-text fallback, and the long-tail clamp.
Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 3 additional findings.

Open in Devin Review

@dvargasfuertes dvargasfuertes merged commit 53b28a4 into main May 8, 2026
13 checks passed
@dvargasfuertes dvargasfuertes deleted the apollo/compact-preserve-tail-anchor branch May 8, 2026 13:07
@awlevin
Copy link
Copy Markdown
Contributor

awlevin commented May 8, 2026

Oh nice catch if true

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.

2 participants