---
title: "Pruning Old Results"
description: "Use pruneMessages to remove old tool call/result pairs while keeping recent context."
canonical_url: "https://vercel.com/academy/build-ai-agent-harness/pruning-old-results"
md_url: "https://vercel.com/academy/build-ai-agent-harness/pruning-old-results.md"
docset_id: "vercel-academy"
doc_version: "1.0"
last_updated: "2026-10-30T14:12:59.853Z"
content_type: "lesson"
course: "build-ai-agent-harness"
course_title: "Build Your Own AI Coding Agent Harness"
prerequisites:  []
---

<agent-instructions>
Vercel Academy — structured learning, not reference docs.
Lessons are sequenced.
Adapt commands to the human's actual environment (OS, package manager, shell, editor) — detect from project context or ask, don't assume.
The lesson shows one path; if the human's project diverges, adapt concepts to their setup.
Preserve the learning goal over literal steps.
Quizzes are pedagogical — engage, don't spoil.
Quiz answers are included for your reference.
</agent-instructions>

# Pruning Old Results

# Pruning Old Results

The fix is three lines.

That's the part that's going to feel anticlimactic. You measured the problem in the last lesson, watched the input tokens climb, sketched out the disaster scenario at step thirty. Now we add three lines and the curve flattens.

The lines themselves are easy. Where they go and why is the lesson.

## Outcome

`prepareStep` runs `pruneMessages` before every step of the loop, removing tool call/result pairs older than the last three messages. The token-growth curve from the last lesson plateaus instead of climbing forever.

## Fast Track

1. Import `pruneMessages` from `ai`
2. Add a `prepareStep` to your `ToolLoopAgent` config
3. Inside it, return `{ messages: pruneMessages({ messages, toolCalls: "before-last-3-messages" }) }`

## Hands-on Exercise 5.2

Wire pruning into the agent and re-run the same multi-step task from lesson 5.1.

**Requirements:**

1. Add `prepareStep: ({ messages }) => ({...})` to the agent config
2. Return a pruned `messages` array from it
3. Use `toolCalls: "before-last-3-messages"` for now (the simplest reasonable strategy)
4. Confirm input tokens plateau across steps instead of climbing

**Implementation hints:**

- `prepareStep` runs before each step, and it receives the `messages` the SDK is about to send for that step. You're modifying them on the way in
- Return only what you want to change. Anything you leave out (`model`, `tools`, `instructions`) keeps its current value
- `messages` is always an array in `prepareStep`, even on step 0, so there's no undefined case to guard

### The fix

```ts title="index.ts" {1,5-7}
import { ToolLoopAgent, isStepCount, tool, pruneMessages } from "ai";

const agent = new ToolLoopAgent({
  // ... existing config
  prepareStep: ({ messages }) => ({
    messages: pruneMessages({ messages, toolCalls: "before-last-3-messages" }),
  }),
});
```

Three lines, one import.

### What's actually happening

Before each step, `prepareStep` runs. It receives the messages the SDK is about to send for that step. We return a pruned version, dropping every tool call and result that's older than the last three messages. The SDK uses that array for this step and builds the next step on top of it.

```
Before pruning at step 15:
  [user prompt]
  [assistant + tool_call] -> [tool_result]    (old, will be pruned)
  [assistant + tool_call] -> [tool_result]    (old, will be pruned)
  ... 12 more pairs ...
  [assistant + tool_call] -> [tool_result]    (recent, kept)
  [assistant + tool_call] -> [tool_result]    (recent, kept)
  [assistant] -> [user]                       (recent, kept)

After pruning:
  [user prompt]                               (kept, original prompt)
  [assistant + tool_call] -> [tool_result]    (recent)
  [assistant + tool_call] -> [tool_result]    (recent)
  [assistant] -> [user]                       (recent)
```

The original user prompt always survives. The recent tool interactions survive. The middle of the conversation, where the tool results pile up, gets dropped on each call.

\*\*Warning: Why not prepareCall?\*\*

`ToolLoopAgent` also has a `prepareCall` hook, and it's tempting to prune there. It runs once per `agent.generate()` call, before the loop starts, not before each step. Our harness passes a `prompt`, so `messages` is `undefined` at that point. A `prepareCall` pruner has nothing to prune. The steps pile up exactly like they did in 5.1, and the curve never plateaus. Per-step work goes in `prepareStep`.

### Why three messages

The `toolCalls: "before-last-3-messages"` setting keeps the last three messages of conversation, not just the last three tool pairs. That's enough context for the model to know where it is in a multi-step task without keeping the whole history.

You can tune this. `before-last-1-messages` is more aggressive and saves more tokens. `before-last-5-messages` is gentler and keeps more context. Three is a reasonable default that works well across task shapes. Start there. Tune later if you have a specific task that needs it.

## Try It

Run the same multi-step task from lesson 5.1 and compare the token curves:

```bash title="Terminal"
bun run index.ts . "Read package.json, tsconfig, index.ts, then summarize"
```

You should see something like:

```
Step 0: 1,200 input, 450 output
Step 1: 2,800 input, 200 output
Step 2: 3,100 input, 180 output    (old results pruned)
Step 3: 3,400 input, 350 output    (growth plateaus)
Step 4: 3,200 input, 600 output    (stays flat)
```

The exact numbers depend on your project. The shape is what matters. Input tokens plateau by step 2 or 3 instead of climbing forever.

```bash title="Terminal"
npx tsc --noEmit
```

\*\*Note: The proof is the shape, not the digits\*\*

Don't expect identical numbers to the example. Token counts depend on file sizes, model choice, and the exact wording of the prompt. The thing to verify is the curve shape: linear before, plateau after.

## Commit

```bash
git add index.ts
git commit -m "feat(context): prune old tool results in prepareStep"
```

## Done-When

- [ ] `pruneMessages` is imported from `ai`
- [ ] `prepareStep` is wired into the agent config
- [ ] `prepareStep` returns the pruned `messages` array
- [ ] On a 4+ step task, input tokens plateau instead of growing linearly
- [ ] `npx tsc --noEmit` passes

\*\*Note: Find your task's pruning threshold\*\*

The default `before-last-3-messages` is a guess. Pick a task that needs the agent to remember something it read several steps earlier (a config value, a function name, a TODO it found). Run with `before-last-1-messages`, `before-last-3-messages`, and `before-last-5-messages` and see when the agent loses the thread. The right number for your harness depends on the kind of work it does.

## Solution

```ts title="index.ts"
import { ToolLoopAgent, isStepCount, tool, pruneMessages } from "ai";

const agent = new ToolLoopAgent({
  model: "anthropic/claude-haiku-4.5",
  instructions: buildSystemPrompt({ /* ... */ }),
  tools,
  stopWhen: isStepCount(15),
  onStepEnd: ({ usage, stepNumber }) => {
    console.error(
      `Step ${stepNumber}: ${usage.inputTokens} input, ${usage.outputTokens} output`,
    );
  },
  prepareStep: ({ messages }) => ({
    messages: pruneMessages({ messages, toolCalls: "before-last-3-messages" }),
  }),
});
```


---

[Full course index](/academy/llms.txt) · [Sitemap](/academy/sitemap.md)
