Skip to content

bug(chat): context fill is measured as cumulative spend, so compaction fires early and the user is shown a wrong percentage #17802

Open

Description

@mrveiss

Problem

fill_percentage — the number that triggers compaction and the number the user is shown — is
computed from a cumulative counter, while the value added to it each turn is the size of the
entire prompt for that turn. The meter therefore grows roughly quadratically in turn count while
real context fill grows linearly.

Evidence

SessionTokenTracker.add_message_tokens increments, per turn:

  • autobot-backend/chat_history/context_overflow.py:108-118 — hincrby(key, "total_tokens", prompt_tokens + completion_tokens)
  • autobot-backend/chat_history/context_overflow.py:690-694 — fill_percentage = total_tokens / context_limit

The value it is given is the provider's own per-response usage, whose prompt_tokens is the whole
prompt, not a delta:

  • autobot-backend/llm_shared/providers/ollama_provider.py:166 — prompt_eval_count
  • autobot-backend/llm_shared/providers/vllm.py:112 — len(output.prompt_token_ids)
  • autobot-backend/llm_shared/providers/anthropic.py:357 — response.usage.input_tokens
  • autobot-backend/llm_shared/providers/openai_compatible.py:227 — usage.prompt_tokens

reaching the tracker unmodified:

  • autobot-backend/chat_history/overflow_integration.py:92-105 — usage = llm_response.usage, passed straight through
  • autobot-backend/api/chat.py:836 — the only production call site

Why this is a bug and not a convention

The correct convention is already implemented in the same function. After a compaction,
_create_summary resets the session and re-seeds it with estimate_fast(...) per retained
message
— sum-of-message-sizes, which is the right basis for fill:

  • autobot-backend/chat_history/context_overflow.py:820-836

So one counter carries two incompatible meanings: conversation size immediately after a compaction,
cumulative spend from the next turn onward.

Consequences

  • Compaction fires far earlier and far more often than the 90% threshold implies. Each firing is a
    paid LLM call that collapses history. Worked example: a 2k-token conversation on an 8k model reads
    as over 100% full after ~5 turns, at ~25% actual fill.
  • The 80% warning surfaced to the user is a wrong number:
    autobot-backend/api/chat.py:858-868 (context_warning.fill_percentage).
  • This is a plausible amplifier for bug(chat): a user instruction erodes across repeated compactions — verbatim protection does not compound #14393 (a user instruction erodes across repeated
    compactions): erosion compounds per compaction, and this defect multiplies how many happen.

Fix shape

The Redis hash already holds four fields (total_tokens, prompt_tokens, completion_tokens,
message_count). Keep those as spend, and add an active_tokens field written with HSET from
the latest turn's usage; compute fill_percentage from active_tokens.

Acceptance criteria

  • fill_percentage is computed from the current turn's prompt size, not a running total
  • Cumulative spend remains available under its own field (no telemetry regression)
  • A positive control asserts the meter moves for a growing conversation and does not grow
    when the same-size prompt is sent repeatedly — the case today's code gets wrong
  • A test pins the worked example above (2k prompt, 8k limit, 5 turns → well under threshold)

Relationships

Provenance

Found by a comparative study of an external agent platform, which states compaction pressure in
terms of active tokens and requires a candidate to reduce active tokens before activation. Nothing
external is proposed for import; the finding is entirely in our own code. Full write-up:
docs/research/ai-employee-workforce-graph-platform.md.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions