@myagentroam/agent 0.11.62 → 0.11.63

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -30,6 +30,7 @@ export declare function serializeModel(model: CliModelConfiguration): {
30
30
  defaultReasoningEffort?: "low" | "medium" | "high" | "xhigh" | "max" | "ultra" | undefined;
31
31
  titleGeneration: boolean;
32
32
  codeMode: boolean;
33
+ gcEnabled?: boolean | undefined;
33
34
  fastMode?: boolean | undefined;
34
35
  serviceTier?: "default" | "auto" | "flex" | "priority" | "fast" | "ultrafast" | undefined;
35
36
  hostedWebSearch: boolean;
@@ -65,6 +66,7 @@ export declare function serializeModel(model: CliModelConfiguration): {
65
66
  defaultReasoningEffort?: "low" | "medium" | "high" | "xhigh" | "max" | "ultra" | undefined;
66
67
  titleGeneration: boolean;
67
68
  codeMode: boolean;
69
+ gcEnabled?: boolean | undefined;
68
70
  fastMode?: boolean | undefined;
69
71
  serviceTier?: "default" | "auto" | "flex" | "priority" | "fast" | "ultrafast" | undefined;
70
72
  hostedWebSearch: boolean;
package/dist/cli/main.js CHANGED
@@ -147,6 +147,7 @@ async function modelsCommand(home, arguments_, codexAuthFile) {
147
147
  const defaultReasoningEffortOption = option('--default-reasoning-effort');
148
148
  const model = {
149
149
  useResourceUrl: existing?.useResourceUrl ?? false,
150
+ gcEnabled: existing?.gcEnabled ?? true,
150
151
  id,
151
152
  name: required(option('--name', existing?.name), '--name'),
152
153
  protocol,
@@ -56,6 +56,7 @@ declare const rawModelSchema: z.ZodObject<{
56
56
  subagentModel: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
57
57
  titleGeneration: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
58
58
  codeMode: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
59
+ gcEnabled: z.ZodOptional<z.ZodBoolean>;
59
60
  useResourceUrl: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
60
61
  fastMode: z.ZodOptional<z.ZodBoolean>;
61
62
  serviceTier: z.ZodOptional<z.ZodEnum<{
@@ -38,6 +38,7 @@ const modelFieldsSchema = z
38
38
  subagentModel: z.boolean().optional().default(false),
39
39
  titleGeneration: z.boolean().optional().default(false),
40
40
  codeMode: z.boolean().optional().default(false),
41
+ gcEnabled: z.boolean().optional(),
41
42
  useResourceUrl: z.boolean().optional().default(false),
42
43
  fastMode: z.boolean().optional(),
43
44
  serviceTier: z.enum(['auto', 'default', 'flex', 'priority', 'fast', 'ultrafast']).optional(),
@@ -0,0 +1,5 @@
1
+ import type { ModelUsage } from '../../model/contracts.js';
2
+ export declare const COMPACT_THRESHOLD_RATIO: number;
3
+ export declare function contextGcMinimumReclaimedTokens(contextWindowTokens: number): number;
4
+ export declare function contextGcEligibleUsage(usage: ModelUsage, contextWindowTokens: number): number | undefined;
5
+ export declare function modelContextUsage(value: ModelUsage | Record<string, unknown>): number | undefined;
@@ -0,0 +1,23 @@
1
+ import { AGENT_EXECUTION_POLICY } from '../execution-policy.js';
2
+ const CONTEXT_GC_START_RATIO = 0.5;
3
+ export const COMPACT_THRESHOLD_RATIO = AGENT_EXECUTION_POLICY.backgroundCompactionPercent / 100;
4
+ export function contextGcMinimumReclaimedTokens(contextWindowTokens) {
5
+ return Math.floor(contextWindowTokens / 10) + 1;
6
+ }
7
+ export function contextGcEligibleUsage(usage, contextWindowTokens) {
8
+ const contextUsageTokens = modelContextUsage(usage);
9
+ if (contextUsageTokens === undefined ||
10
+ !Number.isSafeInteger(contextWindowTokens) ||
11
+ contextWindowTokens <= 0)
12
+ return undefined;
13
+ const usageRatio = contextUsageTokens / contextWindowTokens;
14
+ if (usageRatio < CONTEXT_GC_START_RATIO || usageRatio >= COMPACT_THRESHOLD_RATIO)
15
+ return undefined;
16
+ return contextUsageTokens;
17
+ }
18
+ export function modelContextUsage(value) {
19
+ const context = value.contextInputTokens ?? value.inputTokens;
20
+ return typeof context === 'number' && Number.isSafeInteger(context) && context >= 0
21
+ ? context
22
+ : undefined;
23
+ }
@@ -1,8 +1,7 @@
1
1
  import { retainedCodeInputs } from '../../session/context-gc-observations.js';
2
2
  import { contextGcTargetKey } from '../../session/context-gc-checkpoint.js';
3
3
  import { codeCellCompletionSequence, contextGcCodeModeInputReplacement, contextGcCodeModeOutputReplacement, hasGenericContextGcPreview, normalizeContextGcCodeModeInput } from '../../session/context-gc-replacements.js';
4
- import { CONTEXT_GC_POLICY } from './gc-economics.js';
5
- import { contextGcProtectionMature } from '../../session/context-gc-protection.js';
4
+ import { CONTEXT_GC_TEXT_PROTECTION_ROLLOUTS, contextGcProtectionMature } from '../../session/context-gc-protection.js';
6
5
  import { targetFor, isRecord } from './gc-candidates.js';
7
6
  export function collectCodeModeCandidates(input, groups, existingTargets, candidateSavings) {
8
7
  const eligible = [];
@@ -44,9 +43,9 @@ export function collectCodeModeCandidates(input, groups, existingTargets, candid
44
43
  continue;
45
44
  if (contextGcProtectionMature(protection))
46
45
  eligible.push(candidate);
47
- else if (candidate.observedRollouts < CONTEXT_GC_POLICY.largeExecProtectionRollouts) {
46
+ else if (candidate.observedRollouts < CONTEXT_GC_TEXT_PROTECTION_ROLLOUTS) {
48
47
  protectedTokens += reclaimedTokens;
49
- retryAfterRollouts = Math.min(retryAfterRollouts, CONTEXT_GC_POLICY.largeExecProtectionRollouts - candidate.observedRollouts);
48
+ retryAfterRollouts = Math.min(retryAfterRollouts, CONTEXT_GC_TEXT_PROTECTION_ROLLOUTS - candidate.observedRollouts);
50
49
  }
51
50
  }
52
51
  if (group.failed || !group.completed)
@@ -107,9 +106,9 @@ export function collectCodeModeCandidates(input, groups, existingTargets, candid
107
106
  continue;
108
107
  if (contextGcProtectionMature(protection))
109
108
  eligible.push(candidate);
110
- else if (observedRollouts < CONTEXT_GC_POLICY.largeExecProtectionRollouts) {
109
+ else if (observedRollouts < CONTEXT_GC_TEXT_PROTECTION_ROLLOUTS) {
111
110
  protectedTokens += reclaimedTokens;
112
- retryAfterRollouts = Math.min(retryAfterRollouts, CONTEXT_GC_POLICY.largeExecProtectionRollouts - observedRollouts);
111
+ retryAfterRollouts = Math.min(retryAfterRollouts, CONTEXT_GC_TEXT_PROTECTION_ROLLOUTS - observedRollouts);
113
112
  }
114
113
  }
115
114
  return { eligible, protectedTokens, retryAfterRollouts };
@@ -1,7 +1,6 @@
1
1
  import { contextGcTargetKey } from '../../session/context-gc-checkpoint.js';
2
2
  import { contextGcExecInputReplacement, contextGcExecOutputReplacement, normalizeContextGcExecResult } from '../../session/context-gc-replacements.js';
3
- import { CONTEXT_GC_POLICY } from './gc-economics.js';
4
- import { contextGcProtectionMature } from '../../session/context-gc-protection.js';
3
+ import { CONTEXT_GC_TEXT_PROTECTION_ROLLOUTS, contextGcProtectionMature } from '../../session/context-gc-protection.js';
5
4
  import { targetFor, isRecord } from './gc-candidates.js';
6
5
  import { terminalExecLifecycles } from '../../session/context-gc-exec-lifecycle.js';
7
6
  export function collectExecCandidates(input, groups, existingTargets, candidateSavings) {
@@ -85,9 +84,9 @@ export function collectExecCandidates(input, groups, existingTargets, candidateS
85
84
  mature.push(replacement);
86
85
  observedRollouts = Math.min(observedRollouts, protection.rollouts);
87
86
  }
88
- else if (protection.rollouts < CONTEXT_GC_POLICY.largeExecProtectionRollouts) {
87
+ else if (protection.rollouts < CONTEXT_GC_TEXT_PROTECTION_ROLLOUTS) {
89
88
  awaitingRollouts.push(replacement);
90
- retryAfterRollouts = Math.min(retryAfterRollouts, CONTEXT_GC_POLICY.largeExecProtectionRollouts - protection.rollouts);
89
+ retryAfterRollouts = Math.min(retryAfterRollouts, CONTEXT_GC_TEXT_PROTECTION_ROLLOUTS - protection.rollouts);
91
90
  }
92
91
  }
93
92
  if (awaitingRollouts.length)
@@ -1,8 +1,7 @@
1
1
  import { contextGcLogicalPathKey, contextGcTargetKey } from '../../session/context-gc-checkpoint.js';
2
2
  import { contextGcApplyPatchInputReplacement, contextGcPatchResultFromCompletedPayload, contextGcPatchResultFromFailedPayload, contextGcStaleReadReplacement, contextGcReadRangeCovers, contextGcCoveredReadReplacement } from '../../session/context-gc-replacements.js';
3
3
  import { parseReadContextOutput } from '../../prompts/read-context.js';
4
- import { CONTEXT_GC_POLICY } from './gc-economics.js';
5
- import { contextGcProtectionMature } from '../../session/context-gc-protection.js';
4
+ import { CONTEXT_GC_TEXT_PROTECTION_ROLLOUTS, contextGcProtectionMature } from '../../session/context-gc-protection.js';
6
5
  import { targetFor } from './gc-candidates.js';
7
6
  export function collectDeterministicCandidates(input, groups, existingReplacements, existingTargets, candidateSavings) {
8
7
  const eligible = [];
@@ -108,9 +107,9 @@ export function collectDeterministicCandidates(input, groups, existingReplacemen
108
107
  mature.push(replacement);
109
108
  observedRollouts = Math.min(observedRollouts, protection.rollouts);
110
109
  }
111
- else if (protection.rollouts < CONTEXT_GC_POLICY.largeExecProtectionRollouts) {
110
+ else if (protection.rollouts < CONTEXT_GC_TEXT_PROTECTION_ROLLOUTS) {
112
111
  awaitingRollouts.push(replacement);
113
- retryAfterRollouts = Math.min(retryAfterRollouts, CONTEXT_GC_POLICY.largeExecProtectionRollouts - protection.rollouts);
112
+ retryAfterRollouts = Math.min(retryAfterRollouts, CONTEXT_GC_TEXT_PROTECTION_ROLLOUTS - protection.rollouts);
114
113
  }
115
114
  }
116
115
  if (awaitingRollouts.length) {
@@ -4,7 +4,7 @@ export declare function collectWebCandidates(input: ContextGcPlannerInput, group
4
4
  protectedTokens: number;
5
5
  retryAfterRollouts: number;
6
6
  };
7
- /** Generic successful tool results share output economics, without inferring resource state. */
7
+ /** Generic successful tool results share output retention rules, without inferring resource state. */
8
8
  export declare function collectToolOutputCandidates(input: ContextGcPlannerInput, groups: ReadonlyMap<string, ToolGroup>, existingTargets: ReadonlySet<string>, candidateSavings: CandidateSavings): {
9
9
  eligible: ContextCandidate[];
10
10
  protectedTokens: number;
@@ -1,8 +1,7 @@
1
1
  import { contextGcTargetKey } from '../../session/context-gc-checkpoint.js';
2
2
  import { completedTodoWrite, todoInputReplacement, todoSnapshotReplacement, visibleTodoSnapshot } from '../../session/context-gc-todo.js';
3
3
  import { contextGcWebOutputReplacement, contextGcToolOutputReplacement, contextGcSkillOutputReplacement, hasGenericContextGcPreview, isGenericContextGcTool } from '../../session/context-gc-replacements.js';
4
- import { CONTEXT_GC_POLICY } from './gc-economics.js';
5
- import { contextGcProtectionMature } from '../../session/context-gc-protection.js';
4
+ import { CONTEXT_GC_TEXT_PROTECTION_ROLLOUTS, contextGcProtectionMature } from '../../session/context-gc-protection.js';
6
5
  import { targetFor, isRecord } from './gc-candidates.js';
7
6
  export function collectWebCandidates(input, groups, existingTargets, candidateSavings) {
8
7
  const eligible = [];
@@ -49,14 +48,14 @@ export function collectWebCandidates(input, groups, existingTargets, candidateSa
49
48
  continue;
50
49
  if (contextGcProtectionMature(protection))
51
50
  eligible.push(candidate);
52
- else if (observedRollouts < CONTEXT_GC_POLICY.largeExecProtectionRollouts) {
51
+ else if (observedRollouts < CONTEXT_GC_TEXT_PROTECTION_ROLLOUTS) {
53
52
  protectedTokens += reclaimedTokens;
54
- retryAfterRollouts = Math.min(retryAfterRollouts, CONTEXT_GC_POLICY.largeExecProtectionRollouts - observedRollouts);
53
+ retryAfterRollouts = Math.min(retryAfterRollouts, CONTEXT_GC_TEXT_PROTECTION_ROLLOUTS - observedRollouts);
55
54
  }
56
55
  }
57
56
  return { eligible, protectedTokens, retryAfterRollouts };
58
57
  }
59
- /** Generic successful tool results share output economics, without inferring resource state. */
58
+ /** Generic successful tool results share output retention rules, without inferring resource state. */
60
59
  export function collectToolOutputCandidates(input, groups, existingTargets, candidateSavings) {
61
60
  const eligible = [];
62
61
  let protectedTokens = 0;
@@ -260,9 +259,9 @@ export function collectToolOutputCandidates(input, groups, existingTargets, cand
260
259
  continue;
261
260
  if (contextGcProtectionMature(protection))
262
261
  eligible.push(candidate);
263
- else if (observedRollouts < CONTEXT_GC_POLICY.largeExecProtectionRollouts) {
262
+ else if (observedRollouts < CONTEXT_GC_TEXT_PROTECTION_ROLLOUTS) {
264
263
  protectedTokens += reclaimedTokens;
265
- retryAfterRollouts = Math.min(retryAfterRollouts, CONTEXT_GC_POLICY.largeExecProtectionRollouts - observedRollouts);
264
+ retryAfterRollouts = Math.min(retryAfterRollouts, CONTEXT_GC_TEXT_PROTECTION_ROLLOUTS - observedRollouts);
266
265
  }
267
266
  }
268
267
  return { eligible, protectedTokens, retryAfterRollouts };
@@ -1,20 +1,16 @@
1
1
  import { isDeepStrictEqual } from 'node:util';
2
- import { ContextGcCheckpointCache, contextGcMinimumUsageGrowth, contextGcReplacementKey } from '../../session/context-gc-checkpoint.js';
3
- import { CONTEXT_GC_POLICY, contextGcEligibleUsage, inspectContextGc } from './gc.js';
2
+ import { ContextGcCheckpointCache, contextGcReplacementKey } from '../../session/context-gc-checkpoint.js';
3
+ import { contextGcEligibleUsage, inspectContextGc } from './gc.js';
4
4
  import { buildContextProjection } from './projection.js';
5
5
  /** Only scheduling metadata survives a call; records and validation caches do not. */
6
6
  export class ContextGcRuntime {
7
7
  #scope = '';
8
8
  #lastUsage;
9
- #checkpoint;
10
- #rolloutsSinceCheckpoint = 0;
11
9
  #failures = 0;
12
10
  #remaining = 0;
13
11
  reset() {
14
12
  this.#scope = '';
15
13
  this.#lastUsage = undefined;
16
- this.#checkpoint = undefined;
17
- this.#rolloutsSinceCheckpoint = 0;
18
14
  this.#failures = 0;
19
15
  this.#remaining = 0;
20
16
  }
@@ -44,40 +40,18 @@ export class ContextGcRuntime {
44
40
  this.#remaining = 0;
45
41
  }
46
42
  this.#lastUsage = usage;
47
- if (this.#checkpoint)
48
- this.#rolloutsSinceCheckpoint++;
49
43
  const maximumInterval = usage / input.contextWindowTokens >= 0.8 ? 2 : 8;
50
44
  this.#remaining = Math.min(this.#remaining, maximumInterval - 1);
51
45
  if (this.#remaining > 0 && !mediaRequired) {
52
46
  this.#remaining--;
53
47
  return false;
54
48
  }
55
- if (!mediaRequired &&
56
- this.#checkpoint?.trigger.modelId === input.modelId &&
57
- this.#checkpoint.trigger.contextWindowTokens === input.contextWindowTokens &&
58
- !this.#checkpoint.protectedCandidatesPending &&
59
- this.#rolloutsSinceCheckpoint < CONTEXT_GC_POLICY.largeExecProtectionRollouts &&
60
- usage - this.#checkpoint.trigger.contextInputTokens <
61
- contextGcMinimumUsageGrowth(input.contextWindowTokens))
62
- return false;
63
49
  const attempt = await attemptContextGc(input);
64
50
  if (attempt.result) {
65
- this.#checkpoint = {
66
- trigger: { ...attempt.result.projection.checkpoint.trigger },
67
- protectedCandidatesPending: attempt.result.projection.checkpoint.metrics.protectedCandidateTokens > 0
68
- };
69
- this.#rolloutsSinceCheckpoint = 0;
70
51
  this.#failures = 0;
71
- this.#remaining = 1;
52
+ this.#remaining = 0;
72
53
  }
73
54
  else {
74
- if (attempt.checkpointTrigger)
75
- this.#checkpoint = {
76
- trigger: { ...attempt.checkpointTrigger },
77
- protectedCandidatesPending: attempt.protectedCandidatesPending === true
78
- };
79
- if (attempt.checkpointTrigger)
80
- this.#rolloutsSinceCheckpoint = 0;
81
55
  this.#failures = Math.min(3, this.#failures + 1);
82
56
  this.#remaining =
83
57
  Math.min(2 ** this.#failures, maximumInterval, attempt.retryAfterRollouts ?? Infinity) - 1;
@@ -107,30 +81,6 @@ async function attemptContextGc(input) {
107
81
  records =
108
82
  input.records ?? (await input.store.readExecutionState(input.sessionId)).contextRecords;
109
83
  const checkpointCache = new ContextGcCheckpointCache();
110
- const previous = checkpointCache.read(records);
111
- if (previous) {
112
- deferred = {
113
- ...deferred,
114
- checkpointTrigger: previous.payload.trigger,
115
- protectedCandidatesPending: previous.payload.metrics.protectedCandidateTokens > 0
116
- };
117
- }
118
- if (!mediaRequired &&
119
- previous?.payload.trigger.modelId === input.modelId &&
120
- previous.payload.trigger.contextWindowTokens === input.contextWindowTokens) {
121
- const rolloutGrowth = records.filter((record) => record.sequence > previous.record.sequence &&
122
- record.type === 'model.attempt' &&
123
- isRecord(record.payload) &&
124
- record.payload.purpose === 'EXECUTION' &&
125
- record.payload.state === 'SUCCEEDED').length;
126
- const minimumRollouts = previous.payload.metrics.protectedCandidateTokens > 0
127
- ? 2
128
- : CONTEXT_GC_POLICY.largeExecProtectionRollouts;
129
- if (rolloutGrowth < minimumRollouts &&
130
- contextUsage - previous.payload.trigger.contextInputTokens <
131
- contextGcMinimumUsageGrowth(input.contextWindowTokens))
132
- return deferred;
133
- }
134
84
  const projection = buildContextProjection(records, checkpointCache);
135
85
  const messages = projection.entries.map((entry) => entry.message);
136
86
  const inspection = inspectContextGc({
@@ -155,11 +105,8 @@ async function attemptContextGc(input) {
155
105
  if (inspection.retryAfterRollouts !== undefined)
156
106
  deferred = {
157
107
  ...deferred,
158
- protectedCandidatesPending: true,
159
108
  retryAfterRollouts: inspection.retryAfterRollouts
160
109
  };
161
- else if (!inspection.plan)
162
- deferred = { ...deferred, protectedCandidatesPending: false };
163
110
  const plan = inspection.plan;
164
111
  if (!plan)
165
112
  return deferred;
@@ -206,7 +153,6 @@ async function matchingCheckpointWasPersisted(store, sessionId, draft, records,
206
153
  if (latest?.payload.boundarySequence !== draft.boundarySequence ||
207
154
  !isDeepStrictEqual(latest.payload.base, draft.base) ||
208
155
  !isDeepStrictEqual(latest.payload.trigger, draft.trigger) ||
209
- !isDeepStrictEqual(latest.payload.economics, draft.economics) ||
210
156
  !isDeepStrictEqual(latest.payload.inlineMedia, draft.inlineMedia) ||
211
157
  !isDeepStrictEqual(latest.payload.metrics, draft.metrics))
212
158
  return false;
@@ -20,7 +20,3 @@ export declare function contextGcTokenEntries(input: TokenContext, messages: rea
20
20
  export declare function contextGcReclaimedTokens(input: TokenContext, before: readonly ModelMessage[], after: readonly ModelMessage[]): number;
21
21
  export declare function contextGcPrefixTokens(input: TokenContext): number;
22
22
  export declare function contextGcMessageTokens(input: TokenContext, messages: readonly ModelMessage[]): number;
23
- export declare function contextGcCacheImpact(input: TokenContext, before: readonly ModelMessage[], after: readonly ModelMessage[], cacheRead: number, reclaimedTokens: number): {
24
- preservedCachedPrefixTokens: number;
25
- reclaimedCachedTokens: number;
26
- };
@@ -83,24 +83,6 @@ export function contextGcPrefixTokens(input) {
83
83
  export function contextGcMessageTokens(input, messages) {
84
84
  return contextGcTokenEntries(input, messages).reduce((sum, entry) => sum + entry.tokens, 0);
85
85
  }
86
- export function contextGcCacheImpact(input, before, after, cacheRead, reclaimedTokens) {
87
- const afterByKey = new Map(contextGcTokenEntries(input, after).map((entry) => [entry.key, entry]));
88
- let offset = contextGcPrefixTokens(input);
89
- let earliest = cacheRead;
90
- let reclaimedCachedTokens = 0;
91
- for (const entry of contextGcTokenEntries(input, before)) {
92
- const replacement = afterByKey.get(entry.key);
93
- if (entry.text !== replacement?.text) {
94
- earliest = Math.min(earliest, offset);
95
- reclaimedCachedTokens += Math.min(Math.max(0, entry.tokens - (replacement?.tokens ?? 0)), Math.max(0, cacheRead - offset));
96
- }
97
- offset += entry.tokens;
98
- }
99
- return {
100
- preservedCachedPrefixTokens: Math.max(0, Math.min(cacheRead, earliest)),
101
- reclaimedCachedTokens: Math.min(reclaimedTokens, reclaimedCachedTokens)
102
- };
103
- }
104
86
  function tokensFor(input, text) {
105
87
  const cached = input.tokenCache?.get(text);
106
88
  if (cached !== undefined)
@@ -1,5 +1,5 @@
1
1
  import { type ContextGcPlan, type ContextGcInspection, type ContextGcPlannerInput } from './gc-contracts.js';
2
- export { CONTEXT_GC_POLICY, type ContextGcEconomicsResult, evaluateContextGcEconomics, contextGcMinimumReclaimedTokens, contextGcEligibleUsage } from './gc-economics.js';
2
+ export { contextGcMinimumReclaimedTokens, contextGcEligibleUsage } from './gc-admission.js';
3
3
  export { type ContextGcPlan, type ContextGcInspection, type ContextGcPlannerInput } from './gc-contracts.js';
4
4
  export declare function planContextGc(input: ContextGcPlannerInput): ContextGcPlan | undefined;
5
5
  export declare function inspectContextGc(input: ContextGcPlannerInput): ContextGcInspection;
@@ -1,14 +1,14 @@
1
- import { ContextGcCheckpointCache, contextGcBaseFromRecords, contextGcCheckpointIntervalEligible, contextGcMinimumUsageGrowth, contextGcTargetKey, normalizeContextGcReplacements } from '../../session/context-gc-checkpoint.js';
1
+ import { ContextGcCheckpointCache, contextGcBaseFromRecords, contextGcTargetKey, normalizeContextGcReplacements } from '../../session/context-gc-checkpoint.js';
2
2
  import { buildContextGcProtection } from '../../session/context-gc-protection.js';
3
- import { CONTEXT_GC_TOKEN_ESTIMATOR, contextGcCacheImpact, contextGcMessageTokens, contextGcPrefixTokens, contextGcReclaimedTokens } from './gc-tokens.js';
3
+ import { CONTEXT_GC_TOKEN_ESTIMATOR, contextGcMessageTokens, contextGcPrefixTokens, contextGcReclaimedTokens } from './gc-tokens.js';
4
4
  import { applyContextGcReplacementsToMessages, providerToolCallId } from './projection.js';
5
- import { CONTEXT_GC_POLICY, COMPACT_THRESHOLD_RATIO, evaluateContextGcEconomics, contextGcMinimumReclaimedTokens, contextGcEligibleUsage, modelContextUsage } from './gc-economics.js';
5
+ import { contextGcMinimumReclaimedTokens, contextGcEligibleUsage, modelContextUsage } from './gc-admission.js';
6
6
  import { buildToolGroups, countSuccessfulNormalRollouts, isRecord } from './gc-candidates.js';
7
7
  import { collectCodeModeCandidates } from './gc-candidates-code.js';
8
8
  import { collectDeterministicCandidates } from './gc-candidates-read-patch.js';
9
9
  import { collectExecCandidates } from './gc-candidates-exec.js';
10
10
  import { collectToolOutputCandidates, collectWebCandidates } from './gc-candidates-web.js';
11
- export { CONTEXT_GC_POLICY, evaluateContextGcEconomics, contextGcMinimumReclaimedTokens, contextGcEligibleUsage } from './gc-economics.js';
11
+ export { contextGcMinimumReclaimedTokens, contextGcEligibleUsage } from './gc-admission.js';
12
12
  export function planContextGc(input) {
13
13
  return inspectContextGc(input).plan;
14
14
  }
@@ -36,20 +36,8 @@ export function inspectContextGc(input) {
36
36
  protectedCandidateTokens: text.plan?.metrics.protectedCandidateTokens ?? 0
37
37
  };
38
38
  if (text.plan) {
39
- const combined = evaluateCandidateSet(input, [], [
40
- {
41
- replacements: text.plan.draft.replacements,
42
- observedRollouts: text.plan.metrics.minimumObservedRollouts,
43
- evidenceSequence: text.plan.draft.boundarySequence
44
- }
45
- ], state, media.project);
46
- if (combined?.economics.passes) {
47
- const plan = finalizePlan(input, combined, true);
48
- if (plan)
49
- return { plan };
50
- }
51
- if (media.reason === 'TEXT_GC')
52
- return text;
39
+ const { project: _project, ...inlineMedia } = media;
40
+ return { ...text, plan: { ...text.plan, draft: { ...text.plan.draft, inlineMedia } } };
53
41
  }
54
42
  // Byte pressure has its own admission signal; it never relaxes text GC eligibility.
55
43
  const onlyMedia = evaluateCandidateSet(input, input.checkpointCache.read(input.records)?.payload.replacements ?? [], [
@@ -60,7 +48,7 @@ export function inspectContextGc(input) {
60
48
  }
61
49
  ], { ...state, stage: 'INLINE_MEDIA', mediaOnly: true }, media.project);
62
50
  const plan = onlyMedia && finalizePlan(input, onlyMedia, true);
63
- return plan ? { plan } : text;
51
+ return plan ? { ...text, plan } : text;
64
52
  }
65
53
  function inspectTextGc(input) {
66
54
  const contextUsageTokens = contextGcEligibleUsage(input.usage, input.contextWindowTokens);
@@ -77,26 +65,6 @@ function inspectTextGc(input) {
77
65
  return {};
78
66
  const normalRolloutOrdinal = countSuccessfulNormalRollouts(input.records, input.modelId, input.contextWindowTokens);
79
67
  const previous = input.checkpointCache.read(input.records);
80
- const checkpointEligible = contextGcCheckpointIntervalEligible({
81
- usageSequence: usageRecord.sequence,
82
- contextInputTokens: contextUsageTokens,
83
- contextWindowTokens: input.contextWindowTokens,
84
- normalRolloutOrdinal,
85
- modelId: input.modelId,
86
- platform: input.platform
87
- }, previous?.payload.trigger);
88
- // Discovery and economic evaluation have their own low-growth opportunities;
89
- // neither a protected candidate nor periodic discovery makes a draft eligible.
90
- if (previous?.payload.trigger.modelId === input.modelId &&
91
- previous.payload.trigger.contextWindowTokens === input.contextWindowTokens) {
92
- const rolloutGrowth = normalRolloutOrdinal - previous.payload.trigger.normalRolloutOrdinal;
93
- if (rolloutGrowth < 2 ||
94
- (previous.payload.metrics.protectedCandidateTokens === 0 &&
95
- rolloutGrowth < CONTEXT_GC_POLICY.largeExecProtectionRollouts &&
96
- contextUsageTokens - previous.payload.trigger.contextInputTokens <
97
- contextGcMinimumUsageGrowth(input.contextWindowTokens)))
98
- return {};
99
- }
100
68
  if (input.messages.length !== input.messageSources.length)
101
69
  return {};
102
70
  const groups = buildToolGroups(input.records, input.messages, input.messageSources);
@@ -118,13 +86,14 @@ function inspectTextGc(input) {
118
86
  codeMode.protectedTokens +
119
87
  web.protectedTokens +
120
88
  tools.protectedTokens;
121
- const combined = evaluateCandidateSet(input, existing, [
89
+ const eligible = [
122
90
  ...deterministic.eligible,
123
91
  ...exec.eligible,
124
92
  ...codeMode.eligible,
125
93
  ...web.eligible,
126
94
  ...tools.eligible
127
- ], {
95
+ ];
96
+ const combined = evaluateCandidateSet(input, existing, eligible, {
128
97
  stage: 'UNIFIED',
129
98
  contextUsageTokens,
130
99
  usageRecord,
@@ -132,7 +101,7 @@ function inspectTextGc(input) {
132
101
  protectedCandidateTokens
133
102
  });
134
103
  const retryAfterRollouts = Math.min(deterministic.retryAfterRollouts, exec.retryAfterRollouts, codeMode.retryAfterRollouts, web.retryAfterRollouts, tools.retryAfterRollouts);
135
- if (combined?.economics.passes && checkpointEligible) {
104
+ if (combined) {
136
105
  const plan = finalizePlan(input, combined);
137
106
  return plan
138
107
  ? {
@@ -143,10 +112,11 @@ function inspectTextGc(input) {
143
112
  }
144
113
  : {};
145
114
  }
146
- return protectedCandidateTokens > 0 &&
147
- (combined?.economics.passes ||
148
- protectedCandidateTokens >= contextGcMinimumReclaimedTokens(input.contextWindowTokens)) &&
149
- Number.isFinite(retryAfterRollouts)
115
+ // An already mature but sub-threshold batch can combine with newly mature
116
+ // candidates on the next rollout; no-candidate backoff must not delay it.
117
+ if (eligible.length > 0)
118
+ return { retryAfterRollouts: 1 };
119
+ return protectedCandidateTokens > 0 && Number.isFinite(retryAfterRollouts)
150
120
  ? { retryAfterRollouts }
151
121
  : {};
152
122
  }
@@ -170,23 +140,15 @@ function finalizePlan(input, evaluated, includeMedia = false) {
170
140
  beforeTokens: evaluated.beforeTokens,
171
141
  afterTokens: evaluated.afterTokens,
172
142
  reclaimedTokens: evaluated.reclaimedTokens,
173
- grossReductionRate: evaluated.economics.grossReductionRate,
174
- minimumRequiredReclaimedTokens: evaluated.minimumReclaimedTokens,
175
- cacheRebuildPenaltyCostUnits: evaluated.economics.cacheRebuildPenaltyCostUnits,
176
- requiredPositiveNetRollouts: evaluated.economics.requiredPositiveNetRollouts,
177
- projectedRolloutsUntilCompactAfterGc: evaluated.projectedRolloutsUntilCompactAfterGc,
178
- projectedTokensBeforeGc: evaluated.projectedTokensBeforeGc,
179
- projectedTokensAfterGc: evaluated.projectedTokensAfterGc,
180
- contextGrowthSampleCount: evaluated.contextGrowthSamples.length,
181
- contextGrowthP75Tokens: evaluated.contextGrowthP75Tokens,
182
- expectedNetSavedCostUnits: evaluated.economics.expectedNetSavedCostUnits,
143
+ ...(evaluated.mediaOnly
144
+ ? {}
145
+ : { minimumRequiredReclaimedTokens: evaluated.minimumReclaimedTokens }),
183
146
  minimumObservedRollouts: evaluated.minimumObservedRollouts,
184
147
  protectedCandidateTokens: evaluated.protectedCandidateTokens,
185
148
  eligibleCandidateCount: evaluated.eligibleCandidateCount,
186
149
  eligibleTargetCount: replacements.reduce((count, replacement) => count +
187
150
  1 +
188
- (replacement.kind === 'TOOL_CALL_INPUT' ? replacement.providerAliasTargets.length : 0), 0),
189
- cachePenaltyKnown: input.usage.cacheReadTokens !== undefined
151
+ (replacement.kind === 'TOOL_CALL_INPUT' ? replacement.providerAliasTargets.length : 0), 0)
190
152
  };
191
153
  const draft = {
192
154
  ...(inlineMedia ? { inlineMedia } : {}),
@@ -206,7 +168,6 @@ function finalizePlan(input, evaluated, includeMedia = false) {
206
168
  modelId: input.modelId,
207
169
  platform: input.platform
208
170
  },
209
- economics: CONTEXT_GC_POLICY,
210
171
  metrics
211
172
  };
212
173
  return { stage: evaluated.stage, draft, metrics };
@@ -222,48 +183,23 @@ function evaluateCandidateSet(input, existing, candidates, state, mediaProjectio
222
183
  return undefined;
223
184
  const projected = applyContextGcReplacementsToMessages(input.messages, input.messageSources, replacements);
224
185
  const projectedMessages = mediaProjection?.(projected.messages) ?? projected.messages;
225
- const reclaimedTokens = contextGcReclaimedTokens(input, input.messages, projectedMessages);
186
+ const baseline = applyContextGcReplacementsToMessages(input.messages, input.messageSources, existing);
187
+ const baselineMessages = mediaProjection?.(baseline.messages) ?? baseline.messages;
188
+ const reclaimedTokens = contextGcReclaimedTokens(input, baselineMessages, projectedMessages);
226
189
  const minimumReclaimedTokens = contextGcMinimumReclaimedTokens(input.contextWindowTokens);
227
190
  if (!state.mediaOnly && reclaimedTokens < minimumReclaimedTokens)
228
191
  return undefined;
229
- const beforeTokens = contextGcPrefixTokens(input) + contextGcMessageTokens(input, input.messages);
192
+ const beforeTokens = contextGcPrefixTokens(input) + contextGcMessageTokens(input, baselineMessages);
230
193
  const afterTokens = contextGcPrefixTokens(input) + contextGcMessageTokens(input, projectedMessages);
231
194
  if (!state.mediaOnly && reclaimedTokens === 0)
232
195
  return undefined;
233
- const cache = contextGcCacheImpact(input, input.messages, projectedMessages, input.usage.cacheReadTokens ?? 0, reclaimedTokens);
234
- const growthSamples = collectContextGrowthSamples(input.records, input.modelId, input.contextWindowTokens, input.checkpointCache);
235
- const contextGrowthP75Tokens = percentile75(growthSamples);
236
- const compactThresholdTokens = Math.floor(input.contextWindowTokens * COMPACT_THRESHOLD_RATIO);
237
- // Usage describes the request before its generated output and tool results.
238
- const pendingMessages = input.messages.filter((_message, index) => input.messageSources[index].sequence > state.usageRecord.sequence);
239
- const pendingTokens = Math.max(0, input.usage.outputTokens ?? 0) + contextGcMessageTokens(input, pendingMessages);
240
- const projectedTokensBeforeGc = state.contextUsageTokens + pendingTokens;
241
- const projectedTokensAfterGc = Math.max(0, projectedTokensBeforeGc - reclaimedTokens);
242
- const projectedRolloutsUntilCompactAfterGc = contextGrowthP75Tokens <= 0
243
- ? 0
244
- : Math.max(0, Math.floor((compactThresholdTokens - projectedTokensAfterGc) / Math.max(1, contextGrowthP75Tokens)));
245
- const economics = evaluateContextGcEconomics({
246
- contextUsageTokens: state.contextUsageTokens,
247
- contextWindowTokens: input.contextWindowTokens,
248
- reclaimedTokens,
249
- reclaimedCachedTokens: cache.reclaimedCachedTokens,
250
- cacheReadTokens: input.usage.cacheReadTokens ?? 0,
251
- preservedCachedPrefixTokens: cache.preservedCachedPrefixTokens
252
- });
253
196
  return {
254
197
  ...state,
255
- economics,
256
- reclaimedCachedTokens: cache.reclaimedCachedTokens,
257
198
  replacements,
258
199
  beforeTokens,
259
200
  afterTokens,
260
201
  reclaimedTokens,
261
- contextGrowthSamples: growthSamples,
262
- contextGrowthP75Tokens,
263
202
  minimumReclaimedTokens,
264
- projectedRolloutsUntilCompactAfterGc,
265
- projectedTokensBeforeGc,
266
- projectedTokensAfterGc,
267
203
  boundarySequence: input.records.at(-1)?.sequence ?? state.usageRecord.sequence,
268
204
  minimumObservedRollouts: Math.min(...candidates.map((candidate) => candidate.observedRollouts)),
269
205
  eligibleCandidateCount: candidates.length
@@ -286,51 +222,6 @@ function latestUsageRecord(records, selectedModelId) {
286
222
  }
287
223
  return latest;
288
224
  }
289
- function collectContextGrowthSamples(records, modelId, contextWindowTokens, checkpointCache) {
290
- const previous = checkpointCache.read(records);
291
- const base = contextGcBaseFromRecords(records);
292
- const startSequence = previous?.record.sequence ?? (base?.type === 'COMPACT' ? base.sequence : base?.firstSequence);
293
- let activeModelId = modelId;
294
- let activeWindow = contextWindowTokens;
295
- let previousUsage;
296
- const samples = [];
297
- for (const record of records) {
298
- if (startSequence !== undefined && record.sequence <= startSequence)
299
- continue;
300
- if (record.type === 'execution.header' && isRecord(record.payload)) {
301
- const nextModel = modelIdFromRecord(record);
302
- const nextWindow = record.payload.contextWindowTokens;
303
- if (nextModel !== modelId ||
304
- (typeof nextWindow === 'number' && nextWindow !== contextWindowTokens)) {
305
- activeModelId = nextModel ?? activeModelId;
306
- activeWindow =
307
- typeof nextWindow === 'number' && Number.isSafeInteger(nextWindow)
308
- ? nextWindow
309
- : activeWindow;
310
- previousUsage = undefined;
311
- }
312
- else {
313
- activeModelId = modelId;
314
- activeWindow = contextWindowTokens;
315
- }
316
- continue;
317
- }
318
- if (activeModelId !== modelId ||
319
- activeWindow !== contextWindowTokens ||
320
- !isRecord(record.payload) ||
321
- record.payload.type !== 'usage.updated')
322
- continue;
323
- const usage = modelContextUsage(record.payload);
324
- if (usage === undefined) {
325
- previousUsage = undefined;
326
- continue;
327
- }
328
- if (previousUsage !== undefined && usage > previousUsage)
329
- samples.push(usage - previousUsage);
330
- previousUsage = usage;
331
- }
332
- return samples.slice(-CONTEXT_GC_POLICY.contextGrowthSampleWindow);
333
- }
334
225
  /** Measure only affected call families, retaining native aliases for provider lowering. */
335
226
  function createCandidateSavingsEstimator(input, existing) {
336
227
  const byCall = new Map();
@@ -377,9 +268,3 @@ function modelIdFromRecord(record) {
377
268
  return undefined;
378
269
  return typeof record.payload.modelId === 'string' ? record.payload.modelId : undefined;
379
270
  }
380
- function percentile75(values) {
381
- if (values.length === 0)
382
- return 0;
383
- const sorted = [...values].sort((left, right) => left - right);
384
- return sorted[Math.max(0, Math.ceil(sorted.length * 0.75) - 1)];
385
- }
@@ -23,6 +23,7 @@ export declare class ExecutionContextCoordinator {
23
23
  readonly requests: Pick<CompactRequests, 'drain'>;
24
24
  readonly signal: AbortSignal;
25
25
  readonly window: number;
26
+ readonly gcEnabled?: boolean;
26
27
  readonly gc: Pick<ContextGcRuntime, 'run'>;
27
28
  readonly gcBase: Omit<ContextGcRuntimeInput, 'system' | 'tools' | 'usage' | 'inlineMedia' | 'mediaView' | 'getActiveProcessIds'>;
28
29
  start(input?: {
@@ -80,10 +80,12 @@ export class ExecutionContextCoordinator {
80
80
  this.ports.signal.throwIfAborted();
81
81
  this.ports.execution.assertContextValid();
82
82
  });
83
- if (!this.ports.compact.pending)
83
+ if (this.ports.gcEnabled !== false && !this.ports.compact.pending)
84
84
  await this.gc(round, usage, await this.ports.resources.gcCandidate(), true);
85
85
  }
86
86
  async gc(round, usage, media, includeProcesses = false) {
87
+ if (this.ports.gcEnabled === false || this.ports.compact.pending)
88
+ return false;
87
89
  const appendBoundary = this.ports.execution.appendBoundary;
88
90
  const committed = await this.ports.gc.run({
89
91
  ...this.ports.gcBase,
@@ -571,6 +571,7 @@ export function createSessionExecutionFactory(ports) {
571
571
  requests: compactRequests,
572
572
  signal: controller.signal,
573
573
  window: selectedModel.contextWindowTokens,
574
+ gcEnabled: selectedModel.gcEnabled ?? true,
574
575
  gc: contextGc,
575
576
  gcBase: {
576
577
  store,
@@ -724,7 +725,9 @@ export function createSessionExecutionFactory(ports) {
724
725
  },
725
726
  prepareResources: async ({ candidateRequest, request, toolScope }) => {
726
727
  resourceInput.observe(candidateRequest.messages);
727
- const media = await resourceInput.gcCandidate(request, candidateRequest.images);
728
+ const media = selectedModel.gcEnabled === false
729
+ ? undefined
730
+ : await resourceInput.gcCandidate(request, candidateRequest.images);
728
731
  if (media?.reason === 'HARD_LIMIT') {
729
732
  const committed = await contextCoordinator.gc({ request, toolScope }, modelLifecycle.usage, media);
730
733
  if (committed)
@@ -1,5 +1,5 @@
1
1
  import type { SessionRecord } from './record-contract.js';
2
- import type { ContextGcPlatform, ContextGcBase, ContextGcTarget, ContextGcReplacement, ContextGcCheckpointDraft, ContextGcCheckpointPayload } from './context-gc-contract.js';
2
+ import type { ContextGcPlatform, ContextGcBase, ContextGcTarget, ContextGcReplacement, ContextGcCheckpointPayload } from './context-gc-contract.js';
3
3
  export type { ContextGcPlatform, ContextGcEconomicsPolicy, ContextGcBase, ContextGcTarget, ContextGcReadItemReplacement, ContextGcReplacement, ContextGcMetrics, ContextGcCheckpointDraft, ContextGcCheckpointPayload } from './context-gc-contract.js';
4
4
  export declare const CONTEXT_GC_CHECKPOINT_VERSION: 1;
5
5
  export declare const CONTEXT_GC_MAX_TARGETS = 4096;
@@ -8,9 +8,6 @@ export declare class ContextGcCheckpointCache {
8
8
  #private;
9
9
  read(records: readonly SessionRecord[], boundarySequence?: number): ReturnType<typeof latestValidContextGcCheckpoint>;
10
10
  }
11
- export declare function contextGcMinimumUsageGrowth(contextWindowTokens: number): number;
12
- /** Compare adjacent checkpoint triggers only within the same model/window scope. */
13
- export declare function contextGcCheckpointIntervalEligible(trigger: ContextGcCheckpointDraft['trigger'], previous?: ContextGcCheckpointDraft['trigger']): boolean;
14
11
  export declare function contextGcLogicalPathKey(path: string, workspace: string, platform?: ContextGcPlatform): string | undefined;
15
12
  export declare function contextGcTargetKey(target: ContextGcTarget): string;
16
13
  export declare function contextGcReplacementKey(replacement: ContextGcReplacement): string;
@@ -22,19 +22,6 @@ export class ContextGcCheckpointCache {
22
22
  return value;
23
23
  }
24
24
  }
25
- export function contextGcMinimumUsageGrowth(contextWindowTokens) {
26
- return Math.max(2_000, Math.floor(contextWindowTokens * 0.05));
27
- }
28
- /** Compare adjacent checkpoint triggers only within the same model/window scope. */
29
- export function contextGcCheckpointIntervalEligible(trigger, previous) {
30
- if (!previous ||
31
- trigger.modelId !== previous.modelId ||
32
- trigger.contextWindowTokens !== previous.contextWindowTokens)
33
- return true;
34
- return (trigger.normalRolloutOrdinal - previous.normalRolloutOrdinal >= 2 &&
35
- trigger.contextInputTokens - previous.contextInputTokens >=
36
- contextGcMinimumUsageGrowth(trigger.contextWindowTokens));
37
- }
38
25
  export function contextGcLogicalPathKey(path, workspace, platform) {
39
26
  if (!path || !workspace || path.includes('\0') || workspace.includes('\0'))
40
27
  return undefined;
@@ -126,7 +113,7 @@ export function parseContextGcCheckpoint(record) {
126
113
  !Array.isArray(payload.replacements) ||
127
114
  (payload.replacements.length === 0 && payload.inlineMedia === undefined) ||
128
115
  !isContextGcTrigger(payload.trigger) ||
129
- !isContextGcEconomicsPolicy(payload.economics) ||
116
+ (payload.economics !== undefined && !isContextGcEconomicsPolicy(payload.economics)) ||
130
117
  !isContextGcMetrics(payload.metrics) ||
131
118
  (payload.inlineMedia !== undefined && !parseInlineMediaGc(payload.inlineMedia)))
132
119
  return undefined;
@@ -141,7 +128,7 @@ export function parseContextGcCheckpoint(record) {
141
128
  boundarySequence: payload.boundarySequence,
142
129
  replacements,
143
130
  trigger: structuredClone(payload.trigger),
144
- economics: structuredClone(payload.economics),
131
+ ...(payload.economics === undefined ? {} : { economics: structuredClone(payload.economics) }),
145
132
  metrics: structuredClone(payload.metrics),
146
133
  ...(payload.inlineMedia === undefined
147
134
  ? {}
@@ -1129,17 +1116,17 @@ function isContextGcMetrics(value) {
1129
1116
  'beforeTokens',
1130
1117
  'afterTokens',
1131
1118
  'reclaimedTokens',
1132
- 'grossReductionRate',
1133
- 'cacheRebuildPenaltyCostUnits',
1134
- 'projectedRolloutsUntilCompactAfterGc',
1135
- 'contextGrowthSampleCount',
1136
- 'contextGrowthP75Tokens',
1137
1119
  'minimumObservedRollouts',
1138
1120
  'protectedCandidateTokens',
1139
1121
  'eligibleCandidateCount',
1140
1122
  'eligibleTargetCount'
1141
1123
  ];
1142
1124
  const optionalNonnegative = [
1125
+ 'grossReductionRate',
1126
+ 'cacheRebuildPenaltyCostUnits',
1127
+ 'projectedRolloutsUntilCompactAfterGc',
1128
+ 'contextGrowthSampleCount',
1129
+ 'contextGrowthP75Tokens',
1143
1130
  'effectiveMinimumGrossReductionRate',
1144
1131
  'compactDeferralCreditCostUnits',
1145
1132
  'minimumRequiredReclaimedTokens',
@@ -1150,11 +1137,12 @@ function isContextGcMetrics(value) {
1150
1137
  ];
1151
1138
  return (nonnegative.every((key) => isNonnegativeFinite(value[key])) &&
1152
1139
  [
1153
- 'requiredPositiveNetRollouts',
1140
+ ...(value.requiredPositiveNetRollouts === undefined ? [] : ['requiredPositiveNetRollouts']),
1154
1141
  ...(value.requiredTargetRoiRollouts === undefined ? [] : ['requiredTargetRoiRollouts'])
1155
1142
  ].every((key) => isNonnegativeFinite(value[key]) || (value.stage === 'INLINE_MEDIA' && value[key] === null)) &&
1156
1143
  optionalNonnegative.every((key) => value[key] === undefined || isNonnegativeFinite(value[key])) &&
1157
- Number.isFinite(value.expectedNetSavedCostUnits) &&
1144
+ (value.expectedNetSavedCostUnits === undefined ||
1145
+ Number.isFinite(value.expectedNetSavedCostUnits)) &&
1158
1146
  (value.cacheInvestmentRoi === undefined || Number.isFinite(value.cacheInvestmentRoi)) &&
1159
1147
  (value.tokenEstimator === undefined || value.tokenEstimator === 'UTF8_BYTES_V1') &&
1160
1148
  (value.cachePenaltyKnown === undefined || typeof value.cachePenaltyKnown === 'boolean'));
@@ -105,21 +105,21 @@ export interface ContextGcMetrics {
105
105
  readonly beforeTokens: number;
106
106
  readonly afterTokens: number;
107
107
  readonly reclaimedTokens: number;
108
- readonly grossReductionRate: number;
108
+ readonly grossReductionRate?: number;
109
109
  readonly effectiveMinimumGrossReductionRate?: number;
110
110
  readonly minimumRequiredReclaimedTokens?: number;
111
- readonly cacheRebuildPenaltyCostUnits: number;
111
+ readonly cacheRebuildPenaltyCostUnits?: number;
112
112
  readonly compactDeferralCreditCostUnits?: number;
113
113
  readonly imminentCompactAvoidanceCreditCostUnits?: number;
114
114
  readonly appliedCompactCreditCostUnits?: number;
115
- readonly requiredPositiveNetRollouts: number | null;
115
+ readonly requiredPositiveNetRollouts?: number | null;
116
116
  readonly requiredTargetRoiRollouts?: number | null;
117
- readonly projectedRolloutsUntilCompactAfterGc: number;
117
+ readonly projectedRolloutsUntilCompactAfterGc?: number;
118
118
  readonly projectedTokensBeforeGc?: number;
119
119
  readonly projectedTokensAfterGc?: number;
120
- readonly contextGrowthSampleCount: number;
121
- readonly contextGrowthP75Tokens: number;
122
- readonly expectedNetSavedCostUnits: number;
120
+ readonly contextGrowthSampleCount?: number;
121
+ readonly contextGrowthP75Tokens?: number;
122
+ readonly expectedNetSavedCostUnits?: number;
123
123
  readonly cacheInvestmentRoi?: number;
124
124
  readonly minimumObservedRollouts: number;
125
125
  readonly protectedCandidateTokens: number;
@@ -141,7 +141,7 @@ export interface ContextGcCheckpointDraft {
141
141
  readonly modelId: string;
142
142
  readonly platform: ContextGcPlatform;
143
143
  };
144
- readonly economics: ContextGcEconomicsPolicy;
144
+ readonly economics?: ContextGcEconomicsPolicy;
145
145
  readonly metrics: ContextGcMetrics;
146
146
  }
147
147
  export interface ContextGcCheckpointPayload extends ContextGcCheckpointDraft {
@@ -9,7 +9,7 @@ import { cloneCoreJson } from '../../extension-checkpoint.js';
9
9
  import { acquireFileLock } from '../../../storage/file-lock.js';
10
10
  import { todoItemsFromSnapshot } from '../../todo.js';
11
11
  import { applyCodeModeStateMutations, parseCodeModeStateMutations, restoreCodeModeState } from '../../code-state.js';
12
- import { contextGcBaseFromRecords, contextGcCheckpointIntervalEligible, latestValidContextGcCheckpoint, normalizeContextGcReplacements, sameContextGcBase, validateContextGcCheckpoint } from '../../context-gc-checkpoint.js';
12
+ import { contextGcBaseFromRecords, latestValidContextGcCheckpoint, normalizeContextGcReplacements, sameContextGcBase, validateContextGcCheckpoint } from '../../context-gc-checkpoint.js';
13
13
  import { recordToolResources, readToolResultResource, writeToolResultResource } from '../../tool-result-resources.js';
14
14
  import { beginTreeDeleteTransaction, finishTreeDelete, finishTreeDeletePreparation, readTreeDeleteTransaction, recoverSessionStorageTransaction, stageTreeDelete } from './transaction-recovery.js';
15
15
  import { extensionCheckpointFromPayload, indexExtensionCheckpoint, restoreExtensionCheckpoint, updateSessionCurrentState } from '../current-state.js';
@@ -1293,9 +1293,6 @@ export class JsonlSessionStore {
1293
1293
  !sameContextGcBase(contextGcBaseFromRecords(records, boundarySequence), draft.base))
1294
1294
  throw new AgentError('MAR_AGENT_SESSION_CORRUPT', 'Context GC planning boundary is invalid.');
1295
1295
  const previous = latestValidContextGcCheckpoint(records, boundarySequence);
1296
- if ((!draft.inlineMedia || draft.inlineMedia.reason === 'TEXT_GC') &&
1297
- !contextGcCheckpointIntervalEligible(draft.trigger, previous?.payload.trigger))
1298
- throw new AgentError('MAR_AGENT_SESSION_CORRUPT', 'Context GC checkpoint interval validation failed.');
1299
1296
  const plannedReplacements = normalizeContextGcReplacements(draft.replacements);
1300
1297
  if (!plannedReplacements || (plannedReplacements.length === 0 && !draft.inlineMedia))
1301
1298
  throw new AgentError('MAR_AGENT_SESSION_CORRUPT', 'Context GC checkpoint must contain a replacement.');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myagentroam/agent",
3
- "version": "0.11.62",
3
+ "version": "0.11.63",
4
4
  "description": "Embeddable MAR coding agent SDK and CLI.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -1,35 +0,0 @@
1
- import type { ModelUsage } from '../../model/contracts.js';
2
- export declare const CONTEXT_GC_POLICY: {
3
- version: 1;
4
- minimumPaybackRollouts: number;
5
- maximumPaybackRollouts: number;
6
- contextGrowthSampleWindow: number;
7
- minimumContextGrowthSamples: number;
8
- minimumObservationRollouts: number;
9
- stageAProtectionRollouts: number;
10
- largeExecProtectionRollouts: number;
11
- normalInputCostRate: number;
12
- cacheReadCostRate: number;
13
- };
14
- export declare const COMPACT_THRESHOLD_RATIO: number;
15
- interface ContextGcEconomicsInput {
16
- readonly contextUsageTokens: number;
17
- readonly contextWindowTokens: number;
18
- readonly reclaimedTokens: number;
19
- readonly reclaimedCachedTokens: number;
20
- readonly cacheReadTokens: number;
21
- readonly preservedCachedPrefixTokens: number;
22
- }
23
- export interface ContextGcEconomicsResult {
24
- readonly cacheRebuildPenaltyCostUnits: number;
25
- readonly requiredPositiveNetRollouts: number | null;
26
- readonly expectedNetSavedCostUnits: number;
27
- readonly grossReductionRate: number;
28
- readonly passes: boolean;
29
- }
30
- export declare function evaluateContextGcEconomics(input: ContextGcEconomicsInput): ContextGcEconomicsResult;
31
- export declare function contextGcMinimumReclaimedTokens(contextWindowTokens: number): number;
32
- export declare function contextGcEligibleUsage(usage: ModelUsage, contextWindowTokens: number): number | undefined;
33
- export declare function modelContextUsage(value: ModelUsage | Record<string, unknown>): number | undefined;
34
- export declare function clamp(value: number, minimum: number, maximum: number): number;
35
- export {};
@@ -1,93 +0,0 @@
1
- import { AGENT_EXECUTION_POLICY } from '../execution-policy.js';
2
- import { CONTEXT_GC_TEXT_PROTECTION_ROLLOUTS } from '../../session/context-gc-protection.js';
3
- export const CONTEXT_GC_POLICY = {
4
- version: 1,
5
- minimumPaybackRollouts: 30,
6
- maximumPaybackRollouts: 60,
7
- contextGrowthSampleWindow: 16,
8
- minimumContextGrowthSamples: 6,
9
- minimumObservationRollouts: 1,
10
- stageAProtectionRollouts: CONTEXT_GC_TEXT_PROTECTION_ROLLOUTS,
11
- largeExecProtectionRollouts: CONTEXT_GC_TEXT_PROTECTION_ROLLOUTS,
12
- normalInputCostRate: 1,
13
- cacheReadCostRate: 0.05
14
- };
15
- const CONTEXT_GC_START_RATIO = 0.5;
16
- export const COMPACT_THRESHOLD_RATIO = AGENT_EXECUTION_POLICY.backgroundCompactionPercent / 100;
17
- export function evaluateContextGcEconomics(input) {
18
- const reclaimedTokens = Math.max(0, input.reclaimedTokens);
19
- const cacheReadTokens = Math.max(0, input.cacheReadTokens);
20
- const preservedCachedPrefixTokens = clamp(input.preservedCachedPrefixTokens, 0, cacheReadTokens);
21
- const invalidatedCachedSuffixTokens = cacheReadTokens - preservedCachedPrefixTokens;
22
- const reclaimedCachedTokens = clamp(input.reclaimedCachedTokens, 0, invalidatedCachedSuffixTokens);
23
- const invalidatedRetainedCachedTokens = Math.max(0, invalidatedCachedSuffixTokens - reclaimedCachedTokens);
24
- // Calculate token-derived costs in cache-read units to keep exact zero exact.
25
- const unitsPerNormalInput = CONTEXT_GC_POLICY.normalInputCostRate / CONTEXT_GC_POLICY.cacheReadCostRate;
26
- const cacheRebuildPenalty = invalidatedRetainedCachedTokens * (unitsPerNormalInput - 1);
27
- const cacheRebuildPenaltyCostUnits = cacheRebuildPenalty * CONTEXT_GC_POLICY.cacheReadCostRate;
28
- const reclaimedUncachedTokens = Math.max(0, reclaimedTokens - reclaimedCachedTokens);
29
- const firstRolloutSavings = reclaimedCachedTokens + reclaimedUncachedTokens * unitsPerNormalInput;
30
- const steadyRolloutSavings = reclaimedTokens;
31
- const netAt = (rollouts) => rollouts <= 0
32
- ? -cacheRebuildPenalty
33
- : firstRolloutSavings +
34
- Math.max(0, rollouts - 1) * steadyRolloutSavings -
35
- cacheRebuildPenalty;
36
- let requiredPositiveNetRollouts;
37
- if (netAt(1) > 0)
38
- requiredPositiveNetRollouts = 1;
39
- else if (steadyRolloutSavings <= 0)
40
- requiredPositiveNetRollouts = null;
41
- else {
42
- requiredPositiveNetRollouts = Math.max(2, Math.floor(-netAt(1) / steadyRolloutSavings) + 2);
43
- if (netAt(requiredPositiveNetRollouts - 1) > 0)
44
- requiredPositiveNetRollouts--;
45
- else if (netAt(requiredPositiveNetRollouts) <= 0)
46
- requiredPositiveNetRollouts++;
47
- }
48
- const expectedNetSavedCostUnits = netAt(requiredPositiveNetRollouts ?? 0) * CONTEXT_GC_POLICY.cacheReadCostRate;
49
- const grossReductionRate = reclaimedTokens / Math.max(1, Math.max(0, input.contextUsageTokens));
50
- const minimumReclaimedTokens = contextGcMinimumReclaimedTokens(input.contextWindowTokens);
51
- const usageRatio = input.contextUsageTokens / input.contextWindowTokens;
52
- const progress = clamp((usageRatio - CONTEXT_GC_START_RATIO) / (COMPACT_THRESHOLD_RATIO - CONTEXT_GC_START_RATIO), 0, 1);
53
- const paybackWindow = CONTEXT_GC_POLICY.minimumPaybackRollouts +
54
- progress *
55
- (CONTEXT_GC_POLICY.maximumPaybackRollouts - CONTEXT_GC_POLICY.minimumPaybackRollouts);
56
- const withinWindow = requiredPositiveNetRollouts !== null && requiredPositiveNetRollouts <= paybackWindow;
57
- const passes = reclaimedTokens > 0 &&
58
- reclaimedTokens >= minimumReclaimedTokens &&
59
- Number.isFinite(usageRatio) &&
60
- usageRatio >= CONTEXT_GC_START_RATIO &&
61
- usageRatio < COMPACT_THRESHOLD_RATIO &&
62
- withinWindow;
63
- return {
64
- cacheRebuildPenaltyCostUnits,
65
- requiredPositiveNetRollouts,
66
- expectedNetSavedCostUnits,
67
- grossReductionRate,
68
- passes
69
- };
70
- }
71
- export function contextGcMinimumReclaimedTokens(contextWindowTokens) {
72
- return Math.ceil(contextWindowTokens / 20);
73
- }
74
- export function contextGcEligibleUsage(usage, contextWindowTokens) {
75
- const contextUsageTokens = modelContextUsage(usage);
76
- if (contextUsageTokens === undefined ||
77
- !Number.isSafeInteger(contextWindowTokens) ||
78
- contextWindowTokens <= 0)
79
- return undefined;
80
- const usageRatio = contextUsageTokens / contextWindowTokens;
81
- if (usageRatio < CONTEXT_GC_START_RATIO || usageRatio >= COMPACT_THRESHOLD_RATIO)
82
- return undefined;
83
- return contextUsageTokens;
84
- }
85
- export function modelContextUsage(value) {
86
- const context = value.contextInputTokens ?? value.inputTokens;
87
- return typeof context === 'number' && Number.isSafeInteger(context) && context >= 0
88
- ? context
89
- : undefined;
90
- }
91
- export function clamp(value, minimum, maximum) {
92
- return Math.min(maximum, Math.max(minimum, value));
93
- }