diff --git a/context.lua b/context.lua index 71058cc..095d110 100644 --- a/context.lua +++ b/context.lua @@ -319,11 +319,22 @@ function Context:to_messages() return msgs end --- Evict the oldest pair (user + assistant) while we exceed max_turns. Returns --- total turns evicted. Caller is responsible for rendering the §8 status line. +-- Evict the oldest pair (user + assistant) while we exceed max_turns +-- OR token_budget (Phase 8 pillar 5). Returns total turns evicted. +-- Caller is responsible for rendering the §8 status line. +-- +-- R2 guard: when system_prompt alone exceeds token_budget, the OR +-- condition stays true even when turns are empty — would spin +-- forever calling table.remove on a 0-length list. The `and +-- #self.turns > 0` clause ensures we exit when there's nothing +-- left to evict. Over-budget system_prompts (large [project] +-- blocks, etc.) are then on the user to shrink via :tree off / +-- :memory clear / etc. function Context:enforce_budget() local evicted = 0 - while #self.turns > self.max_turns do + while (#self.turns > self.max_turns + or self:estimate_tokens() > self.token_budget) + and #self.turns > 0 do -- Collect evicted slice (pair: user + assistant) local pair = {} pair[#pair + 1] = self.turns[1]