# How the Context Pruner Decides What to Keep or Discard in CodebuffAI

> Learn how CodebuffAI's context pruner decides what to keep or discard using a deterministic, budget-driven pipeline with separate token caps for user and assistant messages, ensuring the latest entry is always retained.

- Repository: [Codebuff/freebuff](https://github.com/CodebuffAI/freebuff)
- Tags: internals
- Published: 2026-08-20

---

**The context pruner uses a deterministic, budget-driven pipeline with separate token caps for user messages (50,000 tokens) versus assistant/tool messages (20,000 tokens), always forcing retention of the latest entry even if it exceeds budget.**

The **context pruner** is a hidden agent in the [CodebuffAI/freebuff](https://github.com/CodebuffAI/freebuff) repository that runs before every step of a parent agent. Its job is to compress conversation history into a token budget while preserving critical information for coherent multi-turn interactions. Understanding how the context pruner makes keep-or-discard decisions helps developers debug truncated conversations and optimize their agent configurations.

## When the Context Pruner Activates

The pruner does not run on every turn. Two conditions in [`agents/context-pruner.ts`](https://github.com/CodebuffAI/freebuff/blob/main/agents/context-pruner.ts) trigger its execution:

1. **Token overflow** — when `agentState.contextTokenCount + TOKEN_COUNT_FUDGE_FACTOR` exceeds `maxContextLength` (lines 4005–4007)
2. **Prompt cache expiry** — when the time gap between the last assistant message and current user message exceeds `CACHE_EXPIRY_MS` (default 5 minutes, lines 3984–4002)

Both checks appear in the `contextLimitExceeded` and `cacheWillMiss` functions. The fudge factor provides a safety buffer against token estimation errors.

```typescript
// Trigger logic from agents/context-pruner.ts (~lines 4005-4012)
if (agentState.contextTokenCount + 1_000 > maxContextLength) {
  // Context exceeded → pruner will run
}
if (gapMs > CACHE_EXPIRY_MS) {
  // Prompt cache miss → pruner will run
}

```

## The Seven-Step Pruning Pipeline

Once triggered, the context pruner executes a deterministic pipeline:

### Step 1: Strip Control Messages

The pruner removes **hidden control messages** that have no semantic value for the model. The `shouldExcludeMessage` function (lines 444–447) filters out:

- `INSTRUCTIONS_PROMPT`
- `STEP_PROMPT`  
- `SUBAGENT_SPAWN`

Live-prompt parameters not needed for the next turn are also removed.

### Step 2: Summarize Remaining Messages

Every retained message is compressed into a concise representation with **role-specific length limits**:

| Message Type | Transformation | Character Limit |
|-------------|---------------|-----------------|
| **User** | Truncated text + optional *image* note | 39,000 chars (~13,000 tokens × 3) |
| **Assistant** | Progress notes + tool-call summaries | 3,900 chars (~1,300 tokens × 3) |
| **Tool** | Concise error/command/result summaries | Per `TOOL_ENTRY_LIMIT` |

The `truncateLongText` helper (lines 107–118) and role-specific summarizers (lines 665–1083) enforce these caps.

### Step 3: Merge with Prior Summary

If a previous `<conversation_summary>` exists, its entries are parsed via `parseSummaryIntoEntries` (lines 779–798) and merged with newly generated summaries (lines 868–974). This preserves continuity across multiple pruning cycles.

### Step 4: Apply Independent Token Budgets

The pruner enforces **two strictly separate budgets**:

- **User budget**: 50,000 tokens (`USER_BUDGET`, line 82)
- **Assistant-tool budget**: 20,000 tokens (`ASSISTANT_TOOL_BUDGET`, line 79)

Entries are walked **backwards** (newest first) and added until a role's budget exhausts. This design prevents user messages from starving assistant context or vice versa.

```typescript
// Budget enforcement logic (~lines 877-908)
// Walk entries backwards, newest first
for (const entry of entries.reverse()) {
  const budget = entry.role === 'user' ? USER_BUDGET : ASSISTANT_TOOL_BUDGET;
  if (runningTotal + entry.estimatedTokens <= budget) {
    kept.push(entry);
    runningTotal += entry.estimatedTokens;
  }
  // Entries exceeding budget are dropped
}

```

### Step 5: Force-Keep the Newest Entry

Regardless of budget constraints, the **latest entry is always retained**. The `newestEntryForced` logic (lines 1115–1121) ensures the current turn can continue even if that single entry exceeds its entire role budget.

### Step 6: Handle Mid-Turn Pruning

When pruning occurs mid-turn, the pruner injects a **synthetic continuation prompt** instead of the live user prompt. This maintains consistent turn-boundary semantics for the model.

### Step 7: Emit Pruned History

The final output includes:

1. A new `<conversation_summary>` block with kept entries
2. Original `INSTRUCTIONS_PROMPT` (if present)
3. Either the live user prompt or continuation prompt

The pruner calls `set_messages` (lines 1089–1095) to replace the parent's message history.

## Key Decision Factors in Context Pruning

| Factor | Implementation Detail |
|--------|----------------------|
| **Token count with fudge factor** | `agentState.contextTokenCount + TOKEN_COUNT_FUDGE_FACTOR > maxContextLength` |
| **Cache expiry timing** | `CACHE_EXPIRY_MS` default of 300,000ms (5 minutes) |
| **Role-specific budgets** | Independent 50K/20K token caps prevent starvation |
| **Per-type message limits** | Hard character caps on user, assistant, and tool entries |
| **Agent output blacklist** | Certain agents (`file-picker`, `researcher-web`) excluded from summarization |
| **Mid-turn awareness** | Continuation prompt injection for interrupted turns |

## Where Context Pruning Happens

| File | Purpose |
|------|---------|
| [`agents/context-pruner.ts`](https://github.com/CodebuffAI/freebuff/blob/main/agents/context-pruner.ts) | Core agent implementation—trigger logic, summarization, budgeting, output |
| [`packages/agent-runtime/src/compact-history.ts`](https://github.com/CodebuffAI/freebuff/blob/main/packages/agent-runtime/src/compact-history.ts) | In-process equivalent for agents with `compactContext: true` |
| [`agents/general-agent/general-agent.ts`](https://github.com/CodebuffAI/freebuff/blob/main/agents/general-agent/general-agent.ts) | Shows auto-spawn configuration for typical agents |
| [`__tests__/context-pruner-parity.test.ts`](https://github.com/CodebuffAI/freebuff/blob/main/__tests__/context-pruner-parity.test.ts) | Verifies behavioral parity between agent and in-process versions |
| [`e2e/context-pruner.e2e.test.ts`](https://github.com/CodebuffAI/freebuff/blob/main/e2e/context-pruner.e2e.test.ts) | End-to-end pruner behavior validation |

## Agent Auto-Spawn Configuration

Most agents automatically invoke the context pruner through their `spawnableAgents` array:

```typescript
// From agents/general-agent/general-agent.ts (line 86)
{
  spawnableAgents: ['context-pruner'],
  // Parent agent automatically invokes pruner before each step
}

```

Agents can opt into lighter in-process pruning instead by setting `compactContext: true`, which uses the [`compact-history.ts`](https://github.com/CodebuffAI/freebuff/blob/main/compact-history.ts) implementation without spawning a subagent.

## Summary

- The **context pruner** triggers on token overflow or cache expiry, not every turn
- It removes control messages first, then summarizes remaining content with role-specific length limits
- **Two independent budgets** (50K user, 20K assistant/tool) are enforced by walking entries newest-first
- The **latest entry is always kept**, even if it alone exceeds budget
- Mid-turn pruning injects a continuation prompt to preserve turn semantics
- Implementation spans both the standalone [`context-pruner.ts`](https://github.com/CodebuffAI/freebuff/blob/main/context-pruner.ts) agent and the in-process [`compact-history.ts`](https://github.com/CodebuffAI/freebuff/blob/main/compact-history.ts) utility

## Frequently Asked Questions

### What triggers the context pruner to run?

The pruner runs when either: (1) the token count plus fudge factor exceeds `maxContextLength`, or (2) the time since the last assistant message exceeds `CACHE_EXPIRY_MS` (5 minutes). These checks appear in `cacheWillMiss` and `contextLimitExceeded` within [`agents/context-pruner.ts`](https://github.com/CodebuffAI/freebuff/blob/main/agents/context-pruner.ts).

### Why are there separate budgets for user versus assistant/tool messages?

Independent budgets prevent one message type from monopolizing context window. The 50,000-token user budget and 20,000-token assistant-tool budget ensure both conversation history (what the user asked) and execution context (what the assistant did) remain accessible.

### What happens if the newest message alone exceeds its role's budget?

The `newestEntryForced` logic (lines 1115–1121) retains it regardless. This guarantees the current turn can proceed, though earlier entries in that role's budget may be entirely discarded.

### How does mid-turn pruning differ from end-of-turn pruning?

Mid-turn pruning occurs when the agent has made partial progress (tool calls issued but not completed). Instead of the live user prompt, the pruner injects a synthetic continuation prompt to maintain consistent turn boundaries for the model.