-
v1.13.4: two-tier compaction prune — opencode pattern half-shipped in v1.11.0
released this
2026-05-22 07:02:17 +00:00 | 104 commits to main since this release- message_parts.hidden_at timestamptz column (NULL by default) with a
partial index on (message_id) WHERE hidden_at IS NULL for the common
visible-parts filter. - messages_with_parts view changed from COALESCE(parts, legacy) to
CASE WHEN EXISTS(any parts of kind) THEN visible-parts ELSE legacy.
COALESCE would have leaked hidden parts back via the legacy fallback
when every part was pruned (smoke caught it pre-commit). The CASE
distinguishes "no parts at all → fall back to legacy column for
pre-v1.13.0 history" from "all parts hidden → return null/empty so
the row drops out of the model payload" exactly. - prune.ts: scans tool_result parts newest-first, protects the last 40k
tokens (PROTECTED_TOKENS), marks older candidates hidden when their
combined estimate clears 20k (PRUNE_TRIGGER_TOKENS — equal to
COMPACTION_BUFFER from v1.11.0, so a successful prune is exactly the
budget the summary path would have freed). Stops at chats.tail_start_id
so it doesn't double-erase across the last summary boundary. Pure
decision helper selectPruneTargets exported separately for unit tests. - Wired into maybeFlagForCompaction: prune runs synchronously when
overflow is detected; if it freed >= PRUNE_TRIGGER_TOKENS, the
needs_compaction flag is NOT set and the (expensive) summary inference
call is skipped this turn. The next turn's overflow check re-evaluates
from scratch. - 6 new unit tests in prune.test.ts cover: empty input, protection-only
(no candidates), candidates below trigger, candidates above trigger,
candidates straddling a summary boundary, exactly-protection-tokens.
179 tests total (was 173).
Smoke verified post-rebuild:
- \d message_parts shows hidden_at + partial index.
- View definition shows AND p.hidden_at IS NULL filters on all three
subselects. - Synthetic hide-then-restore confirmed the view drops the tool_result
jsonb to null when its only part is hidden, and restores when un-hidden. - EXPLAIN ANALYZE on the 42-message stress chat: 0.325ms (faster than
v1.13.1-B's 1.018ms — EXISTS short-circuits cleanly for the common
no-parts case). - Normal turn (plain text prompt) completes unaffected.
Closes a v1.11.0 design item that was scoped but never implemented. With
v1.13's parts table the prune is dramatically cheaper to write — pre-parts
it would have meant editing JSON blobs in-place; now it's a hidden_at
flag and a view subselect.Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com
Downloads
- message_parts.hidden_at timestamptz column (NULL by default) with a