compaction: the service — settle kick, turn-start gate, registry mutex (round 14 slice 2)
- src/server/compaction.ts: ensureCompacted (the turn-start gate — awaits
in-flight compaction, compacts synchronously when still over threshold),
kickCompaction (the settle kick — summary starts inside the learner's
reading/answering pause; question/problem poses end runs so the pause is
always a between-runs window), registry mutex on globalThis, anti-churn
guard (empty-span skip), summary via the zero-tool one-shot sub-agent
seam on the session's OWN engine (owner ruling), usage to the turns log
keyed by the marker, record-changed + compaction-changed events
- chat-handler: the gate rides with the BYOK/key gates (before appends,
beginTurn; record re-read after); the kick at the onFinish tail
(re-resolves engine fresh; silent no-op when engineless)
- events: compaction-changed {chatId, running} — running=true is the
client send-gate signal
- settings: compaction.thresholdTokens (factory 150k, floor 32k,
clamp-never-reject) resolved concrete in EffectiveSettings
- effective threshold = min(setting, contextWindow − 16k) over the live
catalog index
- overflow recovery needs no special path: the next turn's gate compacts
- 8 service pins (mutex, events order, chained spans, failure re-arm)
This commit is contained in:
@@ -328,6 +328,24 @@ export interface GraphSettings {
|
||||
extractionChunkTokens?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* COMPACTION (owner round 14, 2026-10-05): the context-fitting mechanic's
|
||||
* user-facing knob. One number — the trigger threshold in context tokens.
|
||||
* The effective trigger is min(this, model contextWindow − 16k reserve)
|
||||
* (owner ruling: on big models the setting rules; on small ones the
|
||||
* window rules before the provider overflows).
|
||||
*
|
||||
* FACTORY DEFAULT 150_000 (owner ruling).
|
||||
*
|
||||
* FLOOR 32_000 (implementer's call, the chunk-budget discipline): below
|
||||
* the kept tail (20k) + a summary the chat would re-compact every turn —
|
||||
* a burn, not a preference. NO CEILING: a huge threshold just means "fit
|
||||
* everything" until the window rule binds.
|
||||
*/
|
||||
export interface CompactionSettings {
|
||||
thresholdTokens?: number;
|
||||
}
|
||||
|
||||
/** THE record. Every section optional (delta); unknown top-level keys are
|
||||
* preserved verbatim by the sanitizer (forward-compat with newer records). */
|
||||
export interface UserSettings {
|
||||
@@ -338,6 +356,7 @@ export interface UserSettings {
|
||||
models?: ModelsSettings;
|
||||
appearance?: AppearanceSettings;
|
||||
graph?: GraphSettings;
|
||||
compaction?: CompactionSettings;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
@@ -362,6 +381,7 @@ export interface FactorySettings {
|
||||
};
|
||||
};
|
||||
graph: Required<GraphSettings>;
|
||||
compaction: Required<CompactionSettings>;
|
||||
}
|
||||
|
||||
export const FACTORY_SETTINGS: FactorySettings = {
|
||||
@@ -376,6 +396,7 @@ export const FACTORY_SETTINGS: FactorySettings = {
|
||||
mdRendering: { headingWeight: 900, boldColor: null, italicColor: null },
|
||||
},
|
||||
graph: { enabled: true, extractionChunkTokens: 8192 },
|
||||
compaction: { thresholdTokens: 150_000 },
|
||||
};
|
||||
|
||||
/** The chunk budget's validated floor (GraphSettings.extractionChunkTokens
|
||||
@@ -552,6 +573,20 @@ function sanitizeGraph(raw: Record<string, unknown>): GraphSettings {
|
||||
return out;
|
||||
}
|
||||
|
||||
/** clamp-never-reject (the sanitizer's discipline): a finite positive
|
||||
* number floors to COMPACTION_THRESHOLD_FLOOR, anything else drops (the
|
||||
* factory default resolves at read time). */
|
||||
const COMPACTION_THRESHOLD_FLOOR = 32_000;
|
||||
|
||||
function sanitizeCompaction(raw: Record<string, unknown>): CompactionSettings {
|
||||
const out: CompactionSettings = {};
|
||||
const rawTokens = raw.thresholdTokens;
|
||||
if (typeof rawTokens === "number" && Number.isFinite(rawTokens) && rawTokens > 0) {
|
||||
out.thresholdTokens = Math.max(COMPACTION_THRESHOLD_FLOOR, Math.floor(rawTokens));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Coerce arbitrary JSON (disk file, network body) into a valid delta record:
|
||||
* known fields validated strictly (invalid ones drop/clamp per module doc),
|
||||
@@ -579,6 +614,9 @@ export function sanitizeUserSettings(raw: unknown): UserSettings | null {
|
||||
}
|
||||
if (r.graph !== null && typeof r.graph === "object" && !Array.isArray(r.graph)) {
|
||||
out.graph = sanitizeGraph(r.graph as Record<string, unknown>);
|
||||
if (r.compaction !== undefined && r.compaction !== null && typeof r.compaction === "object") {
|
||||
out.compaction = sanitizeCompaction(r.compaction as Record<string, unknown>);
|
||||
}
|
||||
}
|
||||
// Unknown top-level keys ride along untouched (forward-compat). One
|
||||
// known-DEAD key is stripped instead of riding: `chat` (the old
|
||||
|
||||
Reference in New Issue
Block a user