Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
23e4d1a
feat(eve): authored compaction strategies and ctx.compact()
ruiconti Oct 26, 2026
3b584d9
fix(eve): settle a failed requested compaction, validate strategy res…
ruiconti Oct 27, 2026
ffcfe27
feat(eve): name the removed compaction.thresholdPercent
ruiconti Oct 27, 2026
f8da26f
Measure authored results like the trigger, isolate strategy history, …
ruiconti Oct 27, 2026
f7f61ba
Take the summary model from the strategy: fixedTail({ model })
ruiconti Oct 27, 2026
89ca4eb
Record the hook that requested a compaction as its cause
ruiconti Oct 27, 2026
8c209a9
Expose the compaction limit as ctx.view.context.limit
ruiconti Oct 27, 2026
bf1e5c2
Subagents without their own compaction use the nearest ancestor's str…
ruiconti Oct 27, 2026
eb59890
Remove agent.ts compaction.model: the summary model comes from fixedT…
ruiconti Oct 27, 2026
51de59f
Brand fixedTail() so it can be the compaction.ts export; fix the stra…
ruiconti Oct 27, 2026
2e6cfc9
Annotate the fixture's fixedTail export with the public strategy type
ruiconti Oct 27, 2026
17f0fe2
Fixture: declare the summary-model strategy with defineCompaction
ruiconti Oct 27, 2026
2c64b6f
Compaction review fixes: invalid results fail once, copy keeps URLs, …
ruiconti Oct 27, 2026
70fcd97
Compaction guard: scale the provider undercount by the share of conte…
ruiconti Oct 27, 2026
89b2dd6
fixedTail: recentTokens and modelContextWindowTokens options
ruiconti Oct 27, 2026
ead5ef4
fixedTail: drop modelContextWindowTokens; the removed compaction sett…
ruiconti Oct 27, 2026
7899e48
Compaction guard: apply the provider undercount only to a result that…
ruiconti Oct 27, 2026
85c1057
Compaction guard: judge a result by its estimate and require it to sh…
ruiconti Oct 27, 2026
1e2e669
fix(eve): keep session view snapshots free of own keys
ruiconti Oct 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/compaction-strategy-interface.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"eve": minor
---

An agent can now choose how its history is compacted. Export a strategy from `agent/compaction.ts` with `defineCompaction` from `eve/compaction`; its `compact` returns the replacement history, and eve keeps the events, memory recall, failure handling and billing. A hook can request a compaction with `ctx.compact()`, and `ctx.view.context.inputTokens` reports the latest model call's input tokens. eve still compacts when a request would fill 90% of the context window, and runs the default `fixedTail()` once if a strategy's result is still over it. The default is unchanged. `compaction.thresholdPercent` is removed.
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Node and JS/TS builds
node_modules/
node_modules
dist/
/build/
coverage/
Expand All @@ -24,7 +24,7 @@ apps/benchmarks/evals/*/PROMPT.md
apps/benchmarks/evals/*/package.json
apps/benchmarks/evals/*/.eve-authoring-bootstrap.json
packages/eve/.workflow-vitest/
packages/eve/.generated/
packages/eve/.generated
packages/eve/src/extensions/code/extension/
packages/eve/src/extensions/git/extension/
packages/eve/src/computer-use/extension/
Expand Down
2 changes: 0 additions & 2 deletions apps/templates/eve-coding-agent-template/agent/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,4 @@ import { defineAgent } from "eve";
export default defineAgent({
model: "anthropic/claude-opus-5.5",
reasoning: "medium",
// Coding sessions run long; compact before the context gets crowded.
compaction: { thresholdPercent: 0.7 },
});
4 changes: 2 additions & 2 deletions docs/agent-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -234,13 +234,13 @@ Gateway models; use `providerOptions.gateway.caching` instead.

## Compaction

Compaction summarizes older turns as you approach the context window. It's on by default, so you only tune when it kicks in. eve adds the estimated fixed checkpoint-prompt envelope to the trigger count, so compaction starts sooner than the conversation-only estimate. Lower `thresholdPercent` to compact sooner:
Compaction shortens the history as a request approaches the context window. It's on by default. eve compacts when a request would fill 90% of the model's context window, or when a hook calls `ctx.compact()`. How the history is shortened is a strategy you export from `agent/compaction.ts`; see [Compaction strategies](./concepts/compaction-strategies). `compaction.model` sets the model that writes summaries:

```ts title="agent/agent.ts"
export default defineAgent({
model: "anthropic/claude-opus-5.5",
compaction: {
thresholdPercent: 0.75, // default 0.9
model: "anthropic/claude-haiku-4.5",
},
});
```
Expand Down
59 changes: 59 additions & 0 deletions docs/concepts/compaction-strategies.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
---
title: "Compaction strategies"
description: "Replace how an agent shortens its history by exporting a strategy from compaction.ts."
---

By default eve compacts with the fixed-tail strategy: a summary of everything but the latest conversation, which stays word for word up to 20,000 tokens. See [Default harness](./default-harness). To compact differently, export a strategy from `agent/compaction.ts`.

```ts title="agent/compaction.ts"
import { defineCompaction } from "eve/compaction";

export default defineCompaction({
async compact(history) {
return history.slice(-6);
},
});
```

## When it runs

eve compacts in two cases, and a strategy decides only how:

- **Overflow guard.** A request that would fill 90% of the model's context window is compacted before the model call. This is fixed.
- **A hook asks.** A [hook](../guides/hooks) calls `ctx.compact()` and the next model call compacts first: the running turn's next call, or the first call of the next turn when no turn is running. Asking twice before it runs compacts once.

```ts title="agent/hooks/trim.ts"
import { defineHook } from "eve/hooks";

export default defineHook({
events: {
"model.settled"(_event, ctx) {
if ((ctx.view.context.inputTokens ?? 0) > 60_000) ctx.compact();
},
},
});
```

`ctx.view.context.inputTokens` is the input tokens of the latest settled model call.

If a strategy's result still leaves the request over the guard, eve runs the default `fixedTail()` once on it, and that result must fit.

## What eve does and what the strategy does

eve measures the next request and owns everything around the strategy: the `context.started` and `context.settled` events, [memory](../memory) capture and recall, failure, and billing of the summary call. The strategy owns one decision.

| Member | Decides |
| ----------------------- | ------------------------------------ |
| `compact(history, ctx)` | The messages that replace `history`. |

`ctx` carries `inputTokens`, `requestEnvelopeTokens`, `threshold`, `recentWindowSize`, and `forced`, which is `true` when a user or a hook asked and the request was not over the limit. `history` is the conversation without memory records; eve places recalled memory ahead of the result, so a strategy never sees or reorders it. A user message you return needs a `kind`; build one with `createCompactionUserMessage(content)` from `eve/compaction`, for example to mark a checkpoint.

`ctx` adds `summarize({ messages, system })`, which runs the compaction model and bills the session, `estimateTokens(messages)`, and, when the turn can be cancelled, an `abortSignal`. A strategy that never summarizes makes no model call.

## Failure

Throw `CompactionOverLimitError` (exported from `eve/compaction`) when no history the strategy could return fits. Any thrown error fails the compaction the way a failed summary does.

## Reusing the default

`fixedTail()` from `eve/compaction` returns the default strategy, so a strategy can wrap it, for example to compact earlier than the threshold.
13 changes: 3 additions & 10 deletions docs/concepts/default-harness.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,16 @@ The default harness is eve's built-in agent loop. It manages model calls, compac

## Compaction

The harness keeps a long session from overflowing the model's context window. Before comparing the conversation with `thresholdPercent` (`0.9` by default), it adds the estimated fixed envelope of the checkpoint prompt used for compaction. It then summarizes the older turns and keeps going. The prompt asks the compaction model to distinguish completed progress and decisions from remaining work and to retain the constraints, preferences, data, and references needed to continue. When eve compacts again, it passes the previous checkpoint separately and without the transcript's per-message truncation, then replaces it with the updated checkpoint. The summary uses the active turn model unless you override it. After compacting, the harness keeps the most recent messages word for word next to the summary, up to 20,000 tokens (or half of the threshold, if that is smaller), with tool results shortened to 2,000 characters. The current user message is always part of that, so when it alone is larger than the budget the harness keeps only its start and its end, with a note where the middle was cut, and summarizes the cut-out middle with the older turns. If a compaction cannot bring the history under the threshold, the session fails with an error instead of compacting again, and the step is not retried: start a new session or send a smaller message. Tune when and how it kicks in under [`compaction`](../agent-config#compaction) in `agent.ts`:

```ts title="agent/agent.ts"
export default defineAgent({
model: "anthropic/claude-opus-5.5",
compaction: {
thresholdPercent: 0.75,
},
});
```
The harness keeps a long session from overflowing the model's context window. Before comparing the conversation with 90% of the context window, it adds the estimated fixed envelope of the checkpoint prompt used for compaction. It then summarizes the older turns and keeps going. The prompt asks the compaction model to distinguish completed progress and decisions from remaining work and to retain the constraints, preferences, data, and references needed to continue. When eve compacts again, it passes the previous checkpoint separately and without the transcript's per-message truncation, then replaces it with the updated checkpoint. The summary uses the active turn model unless you override it. After compacting, the harness keeps the most recent messages word for word next to the summary, up to 20,000 tokens (or half of the threshold, if that is smaller), with tool results shortened to 2,000 characters. The current user message is always part of that, so when it alone is larger than the budget the harness keeps only its start and its end, with a note where the middle was cut, and summarizes the cut-out middle with the older turns. If a compaction cannot bring the history under the threshold, the session fails with an error instead of compacting again, and the step is not retried: start a new session or send a smaller message. Set the summary model under [`compaction`](../agent-config#compaction) in `agent.ts`.

Before summarizing, eve tries to shorten oversized tool results in older history.
It checks whether that reduction is sufficient using the last provider-reported
input token count plus an estimate of new messages. A smaller character estimate
alone cannot satisfy compaction triggered by a higher provider count. If trimming
cannot free enough space, eve summarizes the older history.

To change how the history is shortened, export your own strategy from `compaction.ts`. See [Compaction strategies](./compaction-strategies).

First-class [memory](../memory) stays outside history, so the summarizer never
sees it. eve asks providers to capture before compaction rewrites history, and
what earlier turns recalled follows the checkpoint.
Expand Down
1 change: 1 addition & 0 deletions docs/concepts/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"upgrade-to-v27-events",
"state",
"default-harness",
"compaction-strategies",
"built-in-tools",
"context-control",
"security-model"
Expand Down
5 changes: 5 additions & 0 deletions docs/guides/hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ interface HookContext extends SessionContext {
readonly occurrence: { readonly idempotencyKey: string; readonly abortSignal: AbortSignal };
readonly memo: Memo;
cancel(reason?: string): void;
compact(): void;
}
```

Expand Down Expand Up @@ -224,6 +225,10 @@ An unhandled error fails the running turn before its next model call or as it wo

A hook failure does not trigger a retry. State changes and external side effects made before the exception are not rolled back. If a side effect needs retries or compensation, handle that inside the hook. Runtime failures outside the authored handler, such as failures setting up context or persisting state, still propagate. If a published fact outlives its step's checkpoint, the step's retry does not run the hook again.

## Compact the history from a hook

Call `ctx.compact()` to compact the history before the next model call: the running turn's next call, or the first call of the next turn when no turn is running. Asking twice before it runs compacts once. How the history is shortened is the agent's [compaction strategy](../concepts/compaction-strategies). `ctx.view.context.inputTokens` is the input tokens of the latest settled model call, for hooks that decide by size.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Further down, the cancel section (line 265) still says "Hook-side ctx.compact and ctx.once are not available." That now contradicts this section. Suggest dropping ctx.compact from that sentence.


## Cancel the running turn from a hook

Call `ctx.cancel(reason?)` when a hook finds that the turn cannot proceed. For example, a `turn.started` hook that cannot load the caller's credentials can stop the turn before the model runs, instead of letting every tool call fail:
Expand Down
1 change: 1 addition & 0 deletions docs/reference/agent-files.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ Paths below are relative to the agent directory. Root agents can use every path;
| `hooks/` | Lifecycle and stream-event subscribers | Yes | Module-backed only; recursive directories are supported. |
| `skills/` | On-demand procedures and capability packs | Yes | Flat Markdown, module-backed skills, or packaged skills. |
| `lib/` | Shared authored helper code | Yes | Import-only; not copied into the sandbox. Helpers can import data files such as JSON from here. |
| `compaction.ts` | How the history is shortened | Yes | One strategy for the agent. See [Compaction strategies](/docs/concepts/compaction-strategies). |
| `memory.ts` or `memory/<name>.ts` | Cross-session memory | Yes | Provider-backed slots. See [Memory](/docs/memory). |
| `sandbox.ts` or `sandbox/sandbox.ts` | The agent's sandbox | Yes | The framework default applies when neither is authored. When `sandbox/` exists, only `sandbox/sandbox.ts` is read. |
| `sandbox/workspace/**` | Files seeded into the sandbox | Yes | Copied into the prepared environment; each new session sandbox starts with them in the workspace root. |
Expand Down
Loading
Loading