• v1.13.4: two-tier compaction prune — opencode pattern half-shipped in v1.11.0

    indifferentketchup 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