# How Continue's Edit Aggregation Works for Multi-File Changes

> Learn how Continue's edit aggregation clusters keystrokes into diffs for multi-file changes. Understand how edits are isolated and unified for LLM context to improve code editing.

- Repository: [Continue/continue](https://github.com/continuedev/continue)
- Tags: internals
- Published: 2026-06-24

---

**Continue's edit aggregation system clusters rapid keystrokes into meaningful diffs using the `EditAggregator` class, ensuring that edits across multiple files are isolated into separate clusters before being combined into a unified context for the LLM.**

Continue is an open-source AI code assistant (continuedev/continue) that tracks user edits in real-time to provide context-aware suggestions. The **edit aggregation** mechanism transforms streams of individual keystrokes into logical change clusters, handling multi-file scenarios by finalizing clusters when users switch between files.

## The Core Architecture of Edit Aggregation

The `EditAggregator` class in [`core/nextEdit/context/aggregateEdits.ts`](https://github.com/continuedev/continue/blob/main/core/nextEdit/context/aggregateEdits.ts) serves as the central engine for transforming raw text edits into structured diffs.

### File-Specific State Management

The aggregator maintains isolated state for each file using a `Map<string, FileState>` structure stored in `EditAggregator.fileStates`. Each `FileState` tracks:

- Current file content
- A queue of pending edit tasks (`processingQueue`)
- Active clusters of edits for that specific file

This isolation ensures that edit history from one file never contaminates another during the clustering process.

### Cluster Formation and Thresholds

A **cluster** represents a group of consecutive small edits forming a single logical change. Defined by the `ClusterState` interface, each cluster records:

- The *before* snapshot of the file (`beforeState`)
- Line ranges affected by the edits
- Timestamps and cursor positions
- The complete list of individual edits

The `EditClusterConfig` interface defines configurable thresholds that determine cluster boundaries:

- `deltaT`: Time gap (in seconds) before forcing a new cluster
- `deltaL`: Maximum line distance between edits to remain in the same cluster
- `maxEdits`: Maximum number of edits allowed per cluster
- `maxDuration`: Total time limit for a cluster's lifespan

## Multi-File Edit Aggregation Pipeline

When processing edits across multiple files, the aggregator follows a strict pipeline to maintain file isolation while enabling unified context generation.

### File Switch Detection

The critical mechanism for multi-file support resides in the `processEdit` method (lines 16-22 of [`aggregateEdits.ts`](https://github.com/continuedev/continue/blob/main/aggregateEdits.ts)). When an edit arrives for a different file than the previous one, the aggregator immediately finalizes all pending clusters for the previous file:

```typescript
// core/nextEdit/context/aggregateEdits.ts
if (this.lastProcessedFilePath && this.lastProcessedFilePath !== filePath) {
  await this.finalizeClustersForFile(this.lastProcessedFilePath);
}
this.lastProcessedFilePath = filePath;

```

This ensures that **file A's edits never mix with file B's edits** within the same cluster, preserving the integrity of per-file diffs.

### Edit Processing Workflow

1. **Edit Arrival**: Each small edit (`RangeInFileWithNextEditInfo`) enters via `processEdit`
2. **Queue Management**: Edits wrap into tasks and enter the file-specific `processingQueue`, processed in batches of up to five (`_processQueue`)
3. **Cluster Assignment**: `_processEditInternal` calls `findSuitableCluster` to determine if the edit fits within an existing cluster based on proximity, time gaps, and edit counts
4. **Cluster Expansion**: If suitable, the cluster updates its line range and timestamps; otherwise, a **new cluster** initializes with the file's pre-edit content
5. **Structural Boundaries**: Edits inserting newlines or spanning multiple lines trigger immediate finalization of overlapping clusters to prevent mixing unrelated logical changes

### Diff Generation and Finalization

When `finalizeCluster` executes, it generates a unified diff using `createDiff` and passes it to `EditAggregator.onComparisonFinalized`. The system filters out whitespace-only diffs or excessively large changes. The `NextEditProvider` sets this callback to receive finalized diffs for prompt construction.

## Integrating with NextEditProvider

The `NextEditProvider` class ([`core/nextEdit/NextEditProvider.ts`](https://github.com/continuedev/continue/blob/main/core/nextEdit/NextEditProvider.ts)) consumes aggregated diffs to build LLM prompts. It merges finalized diffs with in-progress editing context:

```typescript
// core/nextEdit/NextEditProvider.ts (excerpt)
const inProgressDiff = EditAggregator.getInstance().getInProgressDiff(
  helper.filepath,
);
if (inProgressDiff) {
  combinedDiffContext.push(inProgressDiff);
}

```

This integration occurs in `NextEditProvider._generatePrompts` (lines 96-107), where the provider combines:

- All finalized diffs from previous edits (`this.diffContext`)
- The current in-progress diff from the active file (`inProgressDiff`)

The resulting `combinedDiffContext` presents the LLM with a complete, multi-file view of recent changes while maintaining clear boundaries between files.

## Practical Implementation Examples

### Configuring the EditAggregator

Instantiate the singleton with custom thresholds for specific aggregation behavior:

```typescript
import { EditAggregator } from "core/nextEdit/context/aggregateEdits";

const aggregator = EditAggregator.getInstance({
  deltaT: 1.5,      // seconds before forcing new cluster
  deltaL: 3,        // line distance threshold
  maxEdits: 200,    // maximum edits per cluster
});

const edit = {
  filepath: "/project/src/util.ts",
  fileContents: "const newValue = 42;",
  fileContentsBefore: "const oldValue = 0;",
  range: { start: { line: 10, character: 0 }, end: { line: 10, character: 18 } },
  editText: "const newValue = 42;",
  beforeCursorPos: { line: 10, character: 0 },
  afterCursorPos: { line: 10, character: 20 },
};

await aggregator.processEdit(edit);

```

### Retrieving In-Progress Diffs

Access current editing context before finalization:

```typescript
const currentDiff = EditAggregator.getInstance().getInProgressDiff(
  "/project/src/main.ts"
);

```

### Manual Finalization

Force immediate finalization of all pending clusters, useful during application shutdown or context switches:

```typescript
await EditAggregator.getInstance().finalizeAllClusters();

```

## Key Source Files

| File | Role |
|------|------|
| [`core/nextEdit/context/aggregateEdits.ts`](https://github.com/continuedev/continue/blob/main/core/nextEdit/context/aggregateEdits.ts) | Core aggregation engine implementing `EditAggregator`, clusters, and diff creation |
| [`core/nextEdit/NextEditProvider.ts`](https://github.com/continuedev/continue/blob/main/core/nextEdit/NextEditProvider.ts) | Consumes aggregated diffs and builds LLM prompts via `_generatePrompts` |
| [`core/nextEdit/context/processSmallEdit.ts`](https://github.com/continuedev/continue/blob/main/core/nextEdit/context/processSmallEdit.ts) | Helper recording finalized diffs into the provider context |
| `core/edit/searchAndReplace/*` | Validation utilities feeding edit objects to the aggregator |
| [`core/edit/streamDiffLines.ts`](https://github.com/continuedev/continue/blob/main/core/edit/streamDiffLines.ts) | Unified diff generation used by `createDiff` |

## Summary

- **EditAggregator** ([`aggregateEdits.ts`](https://github.com/continuedev/continue/blob/main/aggregateEdits.ts)) clusters rapid keystrokes into logical diffs using configurable time, line, and count thresholds.
- **Multi-file isolation** occurs through `finalizeClustersForFile`, which flushes pending clusters when users switch files.
- **Cluster state** tracks before/after snapshots, line ranges, and edit metadata via the `FileState` map structure.
- **NextEditProvider** merges finalized diffs with in-progress edits using `getInProgressDiff` before sending context to the LLM.
- **Structural edits** (newlines, multi-line changes) trigger immediate cluster finalization to preserve logical boundaries.

## Frequently Asked Questions

### How does Continue prevent edit clusters from mixing between different files?

When `EditAggregator.processEdit` receives an edit for a new file path, it detects the change via `lastProcessedFilePath` comparison and immediately calls `finalizeClustersForFile` for the previous file. This forces all pending clusters from the previous file to generate diffs before processing begins on the new file, ensuring complete isolation between file contexts.

### What triggers a cluster to finalize into a diff?

Clusters finalize when they exceed thresholds defined in `EditClusterConfig`: time gaps exceeding `deltaT`, line distances beyond `deltaL`, edit counts surpassing `maxEdits`, or total duration exceeding `maxDuration`. Additionally, structural edits spanning multiple lines or inserting newlines trigger immediate finalization of overlapping clusters to prevent mixing unrelated changes.

### Can I access edits before the cluster finalizes?

Yes. The `EditAggregator.getInProgressDiff` method returns the current diff for an active file before finalization. `NextEditProvider` uses this in `_generatePrompts` to combine in-progress changes with already-finalized diffs, ensuring the LLM receives real-time context including unsaved modifications.

### Where does the actual diff generation happen?

The `finalizeCluster` method inside `EditAggregator` calls `createDiff` to generate unified diffs. This utility leverages logic from [`core/edit/streamDiffLines.ts`](https://github.com/continuedev/continue/blob/main/core/edit/streamDiffLines.ts) to produce the diff format, which is then passed to the `onComparisonFinalized` callback configured by `NextEditProvider`.