Skip to content

feat: improve compaction token estimation and adaptive budgets #83

Description

@ezynda3

Feature Description

Three related improvements to the compaction system (internal/compaction/compaction.go, pkg/kit/compaction.go), based on research comparing Kit against opencode's compaction implementation:

1. Count all message parts in token estimation (and prefer real provider usage)

EstimateSingleMessageTokens currently only counts fantasy.TextPart. Tool-call JSON arguments, tool results, reasoning blocks, and images are invisible to the estimator. In agentic sessions, tool traffic often dominates token usage, so Kit systematically undercounts and triggers compaction too late — or hits hard context overflows.

  • Estimate over the serialized full message including tool calls, tool results, and reasoning parts, plus system prompt and tool definition overhead.
  • Prefer real usage numbers from the provider's last response (Usage.TotalTokens) when available, falling back to the char/4 estimate. (opencode does exactly this: tokens.total || input + output + cache.read + cache.write.)

2. Adaptive reserve and keep-recent budgets

ReserveTokens (16384) and KeepRecentTokens (20000) are fixed defaults regardless of model. A fixed 20k keep budget is wasteful on small-context models and stingy on 1M-context ones. opencode computes:

  • reserved = min(20000, maxOutputTokens(model))
  • recent-tail budget = clamp(2000, usable_context * 0.25, 8000)

Kit should scale both with the model's context/output limits (available via models.ModelInfo.Limit), keeping the existing constants as explicit overrides via CompactionOptions.

3. Anchored/incremental summaries on re-compaction

When a session compacts multiple times, each compaction re-summarizes from scratch, losing detail across generations. opencode feeds the previous summary back into the summarization prompt: "Update the anchored summary below using the conversation history above. Preserve still-true details, remove stale details, and merge in the new facts."

Kit already threads PreviousCompaction through for file tracking — extend it to pass the prior summary text into the summarization prompt the same way, so summaries are incrementally updated rather than regenerated.

Motivation / Use Case

Long agentic sessions are where compaction matters most, and they are exactly where the current estimator is weakest (tool-heavy traffic is uncounted). Users on large-context models waste budget with fixed constants; users on small-context models get late compaction and overflows. Repeated compactions progressively lose task context without anchored summaries.

Proposed Implementation

  • internal/compaction/compaction.go: extend EstimateMessageTokens / EstimateSingleMessageTokens to serialize all part types; add helpers to compute adaptive reserve/keep budgets from ModelInfo; accept previous summary text in the summarization prompt builder.
  • pkg/kit/compaction.go: wire last-known Usage from the previous assistant response into ShouldCompact; pass previous compaction summary into compactInternal.
  • All three changes are localized to these two files plus small plumbing, and can ship as one PR.

Checklist

  • I've searched existing issues and this hasn't been requested yet
  • This feature aligns with Kit's design philosophy (TUI-first, extension-based)

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions