Skip to content

fix: compaction fallback to lowest model context limit#2379

Open
shaj13 wants to merge 1 commit intodocker:mainfrom
shaj13:compact-customp
Open

fix: compaction fallback to lowest model context limit#2379
shaj13 wants to merge 1 commit intodocker:mainfrom
shaj13:compact-customp

Conversation

@shaj13
Copy link
Copy Markdown
Contributor

@shaj13 shaj13 commented Apr 11, 2026

Problem

When the provider is not found in the models store, compaction fails to determine the correct context limit for the model.

providers:
  xxx:
    api_type: openai_chatcompletions
    base_url: xxxxx

models:
  claude-opus-4-5:
    provider: xxx
    model: claude-opus-4-5
..... 
image

Fix

Added a fallback that searches all providers for the model and uses the lowest context limit found.
This ensures compaction triggers conservatively regardless of which provider is actually being used at runtime.

image

@shaj13 shaj13 requested a review from a team as a code owner April 11, 2026 02:05
Copy link
Copy Markdown
Contributor

@aheritier aheritier left a comment

Choose a reason for hiding this comment

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

Thanks for identifying this bug — compaction silently failing for custom providers is a real problem worth fixing.

The approach (sentinel error + fallback to lowest context limit across providers) is sound, but unfortunately main has diverged significantly since this PR was opened. The session_compaction.go file was refactored to extract a compactionContextLimit() method, add hook support, and delegate heavy lifting to a new compactor package. This PR has merge conflicts and cannot be merged as-is.

What needs to happen:

  1. Rebase onto main — the store.go change should apply cleanly; the session_compaction.go changes need to be rewritten against the current code.

  2. Target compactionContextLimit() — this is the new single place where model context limits are resolved. The fallback logic belongs there:

func (r *LocalRuntime) compactionContextLimit(ctx context.Context, a *agent.Agent) int64 {
    ...
    m, err := r.modelsStore.GetModel(ctx, summaryModel.ID())
    if err != nil && errors.Is(err, modelsdev.ErrProviderNotFound) {
        // fallback: find lowest context limit across all providers
        db, dberr := r.modelsStore.GetDatabase(ctx)
        if dberr == nil {
            for _, p := range db.Providers {
                if v, ok := p.Models[summaryModel.BaseConfig().ModelConfig.Model]; ok {
                    if m == nil || v.Limit.Context < m.Limit.Context {
                        m = &v
                        err = nil
                    }
                }
            }
        }
    }
    if err != nil || m == nil {
        return 0
    }
    return int64(m.Limit.Context)
}
  1. Add tests — the fallback is non-trivial logic; unit tests for compactionContextLimit with a mock store that triggers ErrProviderNotFound would give confidence.

What's good here:

  • The ErrProviderNotFound sentinel + %w wrapping is the correct Go pattern.
  • "Use the lowest context limit" is a sensible conservative default.
  • The structured logging improvements are a nice touch (though they'll need adapting to the current code which uses slog.DebugContext).

@aheritier
Copy link
Copy Markdown
Contributor

/review

Copy link
Copy Markdown

@docker-agent docker-agent Bot left a comment

Choose a reason for hiding this comment

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

Assessment: 🟡 NEEDS ATTENTION

PR: fix: compaction fallback to lowest model context limit
Verdict: 1 medium finding — zero context limit edge case in the fallback loop.

// Find the lowest context limit for this model, regardless of the provider.
for _, provider := range db.Providers {
if v, ok := provider.Models[summaryModel.BaseConfig().ModelConfig.Model]; ok {
if m == nil || v.Limit.Context < m.Limit.Context {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[MEDIUM] Zero Limit.Context value in models.dev data causes compaction to strip all messages

The minimum-finding loop uses v.Limit.Context < m.Limit.Context without guarding against zero values. If any provider in the models.dev database carries this model with Context: 0 (unset/unknown), it will win the comparison (0 < any positive number) and set m.Limit.Context = 0.

This zero propagates into extractMessagesToCompact as contextLimit = 0, which computes:

contextAvailable := max(0, 0 - maxSummaryTokens - systemPromptLen - userPromptLen) // = 0

…causing compaction to strip all messages rather than triggering conservatively as intended.

Suggested fix — skip entries with a zero or negative context limit:

for _, provider := range db.Providers {
    if v, ok := provider.Models[summaryModel.BaseConfig().ModelConfig.Model]; ok {
        if v.Limit.Context > 0 && (m == nil || v.Limit.Context < m.Limit.Context) {
            m = &v
            err = nil
        }
    }
}

@aheritier aheritier added kind/fix PR fixes a bug (maps to fix: commit prefix) area/providers For features/issues/fixes related to LLM providers (Bedrock, LiteLLM, Qwen, custom, etc.) area/sessions For features/issues/fixes related to session lifecycle (resume, persistence, export) priority:medium Normal priority, standard sprint work labels May 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/providers For features/issues/fixes related to LLM providers (Bedrock, LiteLLM, Qwen, custom, etc.) area/sessions For features/issues/fixes related to session lifecycle (resume, persistence, export) kind/fix PR fixes a bug (maps to fix: commit prefix) priority:medium Normal priority, standard sprint work

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants