Skip to content
4 changes: 4 additions & 0 deletions packages/agent/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## [Unreleased]

### Added

- Enforce configurable active-context ceilings for compaction and preserve large-window request-shape stability across turns and session branches.

### Breaking Changes

### Added
Expand Down
19 changes: 19 additions & 0 deletions packages/agent/src/changes.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,24 @@
# Changes

## 2026-08-23 - Harness compaction honors an active-context ceiling

### What changed

- `packages/agent/src/harness/compaction/compaction.ts` carries the optional `maxContextTokens` setting and applies it in the public `shouldCompact()` helper, bounded by the physical context window and reserve.

### Why

- Public harness consumers configuring a smaller active budget on a large-context model otherwise continued to compact only near the physical window limit.

### Why an extension could not handle it

- The exported harness threshold helper is evaluated by agent-core consumers outside the coding-agent extension runtime.

### Expected merge conflict zones

- LOW: `packages/agent/src/harness/compaction/compaction.ts` compaction settings and `shouldCompact()`.


## 2026-08-20 - End the turn when idle after completed Cursor tools

### What changed
Expand Down
8 changes: 7 additions & 1 deletion packages/agent/src/harness/compaction/compaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,8 @@ export interface CompactionSettings {
reserveTokens: number;
/** Approximate recent-context tokens to keep after compaction. */
keepRecentTokens: number;
/** Explicit active context ceiling override. */
maxContextTokens?: number;
Comment thread
codeg-dev marked this conversation as resolved.
}

/** Default compaction settings used by the harness. */
Expand Down Expand Up @@ -246,7 +248,11 @@ export function estimateContextTokens(messages: AgentMessage[]): ContextUsageEst
/** Return whether context usage exceeds the configured compaction threshold. */
export function shouldCompact(contextTokens: number, contextWindow: number, settings: CompactionSettings): boolean {
if (!settings.enabled) return false;
return contextTokens > contextWindow - settings.reserveTokens;
const activeCeiling =
settings.maxContextTokens && settings.maxContextTokens > 0
? Math.min(contextWindow - settings.reserveTokens, settings.maxContextTokens)
: contextWindow - settings.reserveTokens;
return contextTokens > activeCeiling;
}

const ESTIMATED_IMAGE_CHARS = 4800;
Expand Down
2 changes: 2 additions & 0 deletions packages/agent/test/harness/compaction.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,8 @@ describe("harness compaction", () => {
expect(shouldCompact(95000, 100000, settings)).toBe(true);
expect(shouldCompact(89000, 100000, settings)).toBe(false);
expect(shouldCompact(95000, 100000, { ...settings, enabled: false })).toBe(false);
expect(shouldCompact(45_000, 100_000, { ...settings, maxContextTokens: 40_000 })).toBe(true);
expect(shouldCompact(39_000, 100_000, { ...settings, maxContextTokens: 40_000 })).toBe(false);
});

it("finds a cut point based on token differences", () => {
Expand Down
4 changes: 4 additions & 0 deletions packages/coding-agent/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## [Unreleased]

### Added

- Enforce configurable active-context ceilings for compaction and preserve large-window request-shape stability across turns and session branches.

### Breaking Changes

### Fixed
Expand Down
2 changes: 1 addition & 1 deletion packages/coding-agent/src/core/agent-session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5453,7 +5453,7 @@ export class AgentSession {
usageMessage?.role === "assistant" && this._isAssistantFromBeforeLatestCompaction(usageMessage)
? estimateMessagesTokens(providerMessages)
: estimate.tokens;
return contextTokens > model.contextWindow - settings.reserveTokens;
return shouldCompact(contextTokens, model.contextWindow, settings);
};

if (!settings.enabled || !isOversized()) return;
Expand Down
37 changes: 37 additions & 0 deletions packages/coding-agent/src/core/changes.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,42 @@
# changes

## 2026-08-24 - Enforce the active compaction ceiling during final provider admission

### What changed

- `packages/coding-agent/src/core/agent-session.ts` now routes the final assembled-context admission check through the shared `shouldCompact()` policy instead of comparing only against the physical model window minus reserve tokens.

### Why

- Turn-local custom messages can push a request above the configured or large-model default active ceiling after the earlier persisted-context check. The final admission path must enforce the same ceiling before sending the provider request.

### Why an extension could not handle it

- Final provider admission is a private `AgentSession` guard that runs after context assembly and before dispatch; extensions cannot replace that core oversized-request decision.

### Expected merge conflict zones

- LOW: `packages/coding-agent/src/core/agent-session.ts` around `_enforceFinalProviderAdmission()` and its `isOversized` closure.

## 2026-08-23 - Preserve explicit compaction budget configuration

### What changed

- `packages/coding-agent/src/core/settings-manager.ts` accepts and returns `compaction.maxContextTokens`, and records whether `keepRecentTokens` was explicitly configured rather than normalized from the legacy 20k fallback.

### Why

- Runtime policy needs to distinguish an intentional 20k retention override from an omitted setting so large-context models receive the new 35k default without discarding user configuration.

### Why an extension could not handle it

- Settings parsing and normalization occur in core before extension contexts receive the resolved compaction settings.

### Expected merge conflict zones

- LOW: `packages/coding-agent/src/core/settings-manager.ts` compaction settings interface and getters.


## 2026-08-22 - Retarget OpenAI automatic defaults to GPT-5.6 Sol

### What changed
Expand Down
19 changes: 19 additions & 0 deletions packages/coding-agent/src/core/compaction/changes.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,24 @@
# changes.md — compaction

## 2026-08-23 - Core compaction enforces configurable active-context ceilings

### What changed

- `packages/coding-agent/src/core/compaction/compaction.ts` adds `maxContextTokens` and explicit-retention metadata to compaction settings, and `shouldCompact()` caps large-context sessions at the resolved active budget.

### Why

- Physical model windows can be much larger than the stable request budget, so threshold decisions need a separate active-context ceiling while retaining user overrides.

### Why an extension could not handle it

- Core admission and queue recovery call `shouldCompact()` before extension compaction hooks can replace the threshold decision.

### Expected merge conflict zones

- MEDIUM: `packages/coding-agent/src/core/compaction/compaction.ts` settings and `shouldCompact()`.


## 2026-08-20 - Ignore implausible billed usage for compaction threshold

### What changed
Expand Down
10 changes: 9 additions & 1 deletion packages/coding-agent/src/core/compaction/compaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,9 @@ export interface CompactionSettings {
enabled: boolean;
reserveTokens: number;
keepRecentTokens: number;
/** Whether keepRecentTokens was explicitly configured rather than normalized from a fallback. */
keepRecentTokensConfigured?: boolean;
maxContextTokens?: number;
speculativeEnabled?: boolean;
speculativeFraction?: number;
speculativeCooldownMs?: number;
Expand Down Expand Up @@ -339,7 +342,12 @@ export function estimateContextTokens(messages: AgentMessage[]): ContextUsageEst
*/
export function shouldCompact(contextTokens: number, contextWindow: number, settings: CompactionSettings): boolean {
if (!settings.enabled) return false;
return contextTokens > contextWindow - settings.reserveTokens;
const isLarge = contextWindow >= 500_000;
const defaultCeiling = isLarge ? 384_000 : Math.max(0, contextWindow - settings.reserveTokens);
const configuredCeiling =
settings.maxContextTokens && settings.maxContextTokens > 0 ? settings.maxContextTokens : defaultCeiling;
const maxActive = Math.min(Math.max(0, contextWindow - settings.reserveTokens), configuredCeiling);
Comment thread
codeg-dev marked this conversation as resolved.
return contextTokens > maxActive;
}

// ============================================================================
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,28 @@
# Builtin compaction extension changes

## 2026-08-23 - Active-budget policy and branch-scoped reduction latch

### What changed

- `packages/coding-agent/src/core/extensions/builtin/compaction/policy.ts` resolves a 384k active ceiling and 35k retention default for large-context models while honoring explicit overrides.
- `packages/coding-agent/src/core/extensions/builtin/compaction/speculative.ts` carries explicit-retention metadata into compaction preparation.
- `packages/coding-agent/src/core/extensions/builtin/compaction/context-reduction.ts` adds a sticky generation latch so request-shape reduction remains stable until compaction.
- `packages/coding-agent/src/core/extensions/builtin/compaction/index.ts` releases that latch when session-tree navigation changes the active branch and bumps it after accepted compaction.

### Why

- Large physical windows can exceed stable request budgets, and threshold oscillation or branch navigation must not leave request shaping stuck on history from a different active branch.

### Why an extension could not handle it

- This is the builtin compaction extension's own policy and context-rewrite lifecycle; no outer extension can safely coordinate its private speculative jobs and reduction latch.

### Expected merge conflict zones

- MEDIUM: `policy.ts` threshold/retention resolution, `speculative.ts` preparation settings, and `index.ts` session/context handlers.
- LOW: `context-reduction.ts` latch helper.


## Skip Cursor compaction while the session is not idle (2026-08-19)

Blocking and generated apply refuse `cursor` / `cursor-cli-oauth` when `!ctx.isIdle()`. Mid-run Cursor compact poisons `conversationId`. Idle `agent_end` / `pre_prompt` still compact.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,19 +144,59 @@ export const BUILTIN_CONTEXT_REDUCTION_OPTIONS: ReduceContextOptions = {

export const BUILTIN_CONTEXT_REDUCTION_GATE_RATIO = 0.5;

export interface ContextPressure {
activeTokens: number | null;
requestBodyBytes: number | null;
nonReclaimableTokens?: number | null;
generation: number;
}

export interface ContextReductionLatch {
isLatched: () => boolean;
engage: () => void;
release: () => void;
getGeneration: () => number;
bumpGeneration: () => void;
}

export function createContextReductionLatch(): ContextReductionLatch {
let latched = false;
let generation = 0;
return {
isLatched: () => latched,
engage: () => {
latched = true;
},
release: () => {
latched = false;
},
getGeneration: () => generation,
bumpGeneration: () => {
generation += 1;
latched = false;
},
};
}

export interface ShouldApplyContextReductionInput {
usageTokens: number | null;
contextWindow: number;
gateRatio?: number;
isProviderNativeCompactionPath?: boolean;
latch?: ContextReductionLatch;
}

export function shouldApplyContextReduction(input: ShouldApplyContextReductionInput): boolean {
const gate = input.gateRatio ?? BUILTIN_CONTEXT_REDUCTION_GATE_RATIO;
if (input.isProviderNativeCompactionPath === true) return false;
if (input.latch?.isLatched()) return true;
if (input.usageTokens === null) return false;
if (input.contextWindow <= 0) return false;
return input.usageTokens >= input.contextWindow * gate;
const gate = input.gateRatio ?? BUILTIN_CONTEXT_REDUCTION_GATE_RATIO;
const shouldEngage = input.usageTokens >= input.contextWindow * gate;
if (shouldEngage && input.latch) {
input.latch.engage();
}
return shouldEngage;
}

function approxTextTokens(text: string): number {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import * as checkpointState from "./checkpoint-state.ts";
import * as breaker from "./circuit-breaker.ts";
import {
BUILTIN_CONTEXT_REDUCTION_OPTIONS,
createContextReductionLatch,
reduceContextMessages,
shouldApplyContextReduction,
} from "./context-reduction.ts";
Expand Down Expand Up @@ -193,6 +194,7 @@ export default function compactionExtension(
const lanePolicy = createCompactionLanePolicy();
const restorationDirectiveState = checkpointState.createRestorationDirectiveState();
const emergencyPruneLatch = createEmergencyPruneLatch();
const contextReductionLatch = createContextReductionLatch();
const degradationState = createDegradationMonitorState();
const restorationState = state.restoration ?? restoration.createRestorationTrackerState();
state = { ...state, restoration: restorationState };
Expand Down Expand Up @@ -717,6 +719,7 @@ export default function compactionExtension(
});

pi.on("model_select", (event, ctx) => {
contextReductionLatch.bumpGeneration();
if (lanePolicy.disablesSenpiCompaction(ctx)) {
invalidateSpeculativeCompaction(ctx);
return;
Expand Down Expand Up @@ -746,10 +749,15 @@ export default function compactionExtension(
}
});

pi.on("session_tree", () => {
contextReductionLatch.bumpGeneration();
});

pi.on("session_compact", async (event: SessionCompactEvent, ctx) => {
const compactEvent = event;
invalidateSpeculativeCompaction(ctx);
if (compactEvent.accepted) {
contextReductionLatch.bumpGeneration();
persistAcceptedMetadata(compactEvent.requestId);
const branchEntries = ctx.sessionManager.getBranch();
const firstKeptIndex = branchEntries.findIndex(
Expand Down Expand Up @@ -874,6 +882,7 @@ export default function compactionExtension(
contextWindow,
isProviderNativeCompactionPath:
isOpenAiRemoteCompactionModel(ctx.model) || lanePolicy.disablesSenpiCompaction(ctx),
latch: contextReductionLatch,
Comment thread
codeg-dev marked this conversation as resolved.
Comment thread
codeg-dev marked this conversation as resolved.
})
? reduceContextMessages(event.messages, BUILTIN_CONTEXT_REDUCTION_OPTIONS).messages
: event.messages;
Expand Down
Loading