fix(compaction): default maintenance reasoning to low (#126421)

* fix(compaction): default maintenance reasoning to low

* test(compaction): cover explicit inherit fallback

* test(compaction): preserve inherit literal type
This commit is contained in:
Jason (Json)
2026-08-19 16:18:06 -06:00
committed by GitHub
parent 7769cb6466
commit 45b8750d8b
15 changed files with 61 additions and 23 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
{
"core": 2333,
"channel": 3582,
"plugin": 4002
"plugin": 3978
}
+4 -4
View File
@@ -1,4 +1,4 @@
ce0ade9436d094a159eaa16faa5ed92cc8b57d7dc1c5d5baa3a6f7c5147baf4b config-baseline.json
0b84d1b0fabf0242f434f974bc7dd32ece2ac277f728d033e573e679dee042f3 config-baseline.core.json
2b756ea29fdcb1cd3e7fc22b989ce27fe4213b9e0fe86c55dd80b3e88991a2c1 config-baseline.channel.json
2eb56b8a47ab34006bd56d95f7379ac859661bed2f9335ae40b681793afaded4 config-baseline.plugin.json
0442d66ded7bed6a7a1825888835034d55eb93d24e57693f762611b8f5ef48d8 config-baseline.json
e3ec0a72fea5fbcc69b71a2687de77e8dcb58aab8ba2631d1de6fa5c0f43e9bf config-baseline.core.json
c89feef2a5109dc979f5f2b6b32fbafdc93174d6eb1b6a7e525ab04beb93891c config-baseline.channel.json
ed7c7e8dfe9d676ebbf60b5ee55f72d1f9e0286ffb37743f40f6c337816b166d config-baseline.plugin.json
+2 -2
View File
@@ -600,7 +600,7 @@ An explicit request `agentId` always wins. Delegated consults with a requesting
enabled: false, // disable embedded proactive auto-compaction (default: true)
mode: "safeguard", // default | safeguard
provider: "my-provider", // id of a registered compaction provider plugin (optional)
thinkingLevel: "low", // optional compaction-only thinking override
thinkingLevel: "low", // default; use "inherit" to reuse the session level
timeoutSeconds: 180,
keepRecentTokens: 50000,
recentTurnsPreserve: 3,
@@ -627,7 +627,7 @@ An explicit request `agentId` always wins. Delegated consults with a requesting
- `enabled`: when `false`, disables threshold-driven auto-compaction inside the embedded agent runtime. OpenClaw's preflight and overflow-recovery compaction paths and manual `/compact` remain available. Default: `true`.
- `mode`: `default` or `safeguard` (chunked summarization for long histories). See [Compaction](/concepts/compaction).
- `provider`: id of a registered compaction provider plugin. When set, the provider's `summarize()` is called instead of built-in LLM summarization. Falls back to built-in on failure. Setting a provider forces `mode: "safeguard"`. See [Compaction](/concepts/compaction).
- `thinkingLevel`: optional thinking level used only for embedded OpenClaw compaction summaries (`off`, `minimal`, `low`, `medium`, `high`, `xhigh`, `adaptive`, `max`, or `ultra`). It overrides the session's current thinking level and is clamped to the selected compaction model/runtime. Leave unset to inherit the session level. Native Codex app-server compaction ignores this setting because the native compact request has no per-operation thinking override; OpenClaw logs a warning when configured.
- `thinkingLevel`: thinking level used only for embedded OpenClaw compaction summaries (`off`, `minimal`, `low`, `medium`, `high`, `xhigh`, `adaptive`, `max`, `ultra`, or `inherit`). It defaults to `low`; set `inherit` to reuse the session's current thinking level. The selected level is clamped to the compaction model/runtime. Native Codex app-server compaction ignores this setting because the native compact request has no per-operation thinking override; OpenClaw logs a warning when configured.
- `timeoutSeconds`: maximum seconds allowed for a single compaction operation before OpenClaw aborts it. Default: `180`.
- `keepRecentTokens`: agent cut-point budget for keeping the most recent transcript tail verbatim. Default: `20000`.
- `recentTurnsPreserve`: number of most recent user/assistant turns kept verbatim outside safeguard summarization. Default: `3`.
@@ -197,7 +197,7 @@ More on limits: [/reference/token-use](/reference/token-use).
Compaction summarizes older conversation into a persisted `compaction` entry in the transcript and keeps recent messages intact. After compaction, future turns see the compaction summary plus messages after `firstKeptEntryId`. Compaction is **persistent**, unlike session pruning - see [/concepts/session-pruning](/concepts/session-pruning).
Embedded OpenClaw compaction inherits the session thinking level by default. Set `agents.defaults.compaction.thinkingLevel` to use a separate level for summary calls; the runtime clamps it to each concrete compaction model or fallback. Native Codex app-server compaction owns its compact request and cannot accept a per-compaction thinking override, so OpenClaw warns and leaves that setting to Codex.
Embedded OpenClaw compaction uses `low` thinking by default. Set `agents.defaults.compaction.thinkingLevel: "inherit"` to reuse the session level, or choose another explicit level for summary calls; the runtime clamps it to each concrete compaction model or fallback. Native Codex app-server compaction owns its compact request and cannot accept a per-compaction thinking override, so OpenClaw warns and leaves that setting to Codex.
AGENTS.md section reinjection after compaction remains opt-in via `agents.defaults.compaction.postCompactionSections`. Plugins can add other prompt context through `before_prompt_build`.
@@ -1606,6 +1606,7 @@ describe("compactEmbeddedAgentSessionDirect hooks", () => {
config: {
agents: {
defaults: {
compaction: { thinkingLevel: "inherit" as const },
models: {
"openai/gpt-5.6-sol": { agentRuntime: { id: "openclaw" } },
},
@@ -67,25 +67,41 @@ describe("resolveEmbeddedCompactionThinkingLevel", () => {
).toBe("high");
});
it("inherits the session level and otherwise defaults to off", () => {
it("defaults compaction to low without inheriting the session level", () => {
expect(
resolveEmbeddedCompactionThinkingLevel({
provider: "demo",
modelId: "demo-model",
inheritedLevel: "medium",
}),
).toBe("medium");
).toBe("low");
expect(
resolveEmbeddedCompactionThinkingLevel({
provider: "demo",
modelId: "demo-model",
}),
).toBe("off");
).toBe("low");
});
it("inherits the session level only when explicitly configured", () => {
expect(
resolveEmbeddedCompactionThinkingLevel({
config: {
agents: { defaults: { compaction: { thinkingLevel: "inherit" } } },
} as unknown as OpenClawConfig,
provider: "demo",
modelId: "demo-model",
inheritedLevel: "medium",
}),
).toBe("medium");
});
it("preserves thinking when the resolved Ollama model reports reasoning support", () => {
expect(
resolveEmbeddedCompactionThinkingLevel({
config: {
agents: { defaults: { compaction: { thinkingLevel: "inherit" } } },
},
provider: "ollama",
modelId: "qwen3.5:4b",
inheritedLevel: "high",
@@ -71,8 +71,9 @@ export function resolveEmbeddedCompactionThinkingLevel(params: {
sessionKey?: string;
agentRuntime?: string | null;
}): ThinkLevel {
const configuredLevel = params.config?.agents?.defaults?.compaction?.thinkingLevel;
const requestedLevel =
params.config?.agents?.defaults?.compaction?.thinkingLevel ?? params.inheritedLevel;
configuredLevel === "inherit" ? params.inheritedLevel : (configuredLevel ?? "low");
if (!requestedLevel) {
return "off";
}
@@ -1,6 +1,9 @@
import { describe, expect, it } from "vitest";
import { SILENT_REPLY_TOKEN } from "../tokens.js";
import { buildEmptyInteractiveReplyPayload } from "./agent-runner-failure-reply.js";
import {
buildEmptyInteractiveReplyPayload,
buildPreflightCompactionFailureText,
} from "./agent-runner-failure-reply.js";
const EMPTY_INTERACTIVE_REPLY_TEXT =
"I finished the turn, but it did not produce a visible reply. Please try again, or start a new session if this keeps happening.";
@@ -34,3 +37,16 @@ describe("buildEmptyInteractiveReplyPayload", () => {
).toMatchObject({ text: EMPTY_INTERACTIVE_REPLY_TEXT, isError: true });
});
});
describe("buildPreflightCompactionFailureText", () => {
it("identifies timeout failures without requiring verbose error details", () => {
expect(
buildPreflightCompactionFailureText(
"Preflight compaction required but failed: Compaction timed out",
),
).toBe(
"⚠️ Context is too large and auto-compaction timed out before it could finish. " +
"Try again, use /compact, or use /new to start a fresh session.",
);
});
});
@@ -8,6 +8,7 @@ import {
} from "../../agents/auth-profiles/oauth-refresh-failure.js";
import { sanitizeUserFacingText } from "../../agents/embedded-agent-helpers/sanitize-user-facing-text.js";
import { renderUserFacingText } from "../../agents/embedded-agent-helpers/user-facing-text.js";
import { classifyCompactionReason } from "../../agents/embedded-agent-runner/compact-reasons.js";
import {
describeFailoverError,
findCliMaxTurnsError,
@@ -158,11 +159,12 @@ export function buildPreflightCompactionFailureText(
)
.trim()
.replace(/\s+/gu, " ");
const reasonSuffix = options?.includeDetails && reason ? ` Reason: ${reason}.` : "";
return (
"⚠️ Context is too large and auto-compaction could not recover this turn." +
`${reasonSuffix} Try again, use /compact, or use /new to start a fresh session.`
);
const isTimeout = classifyCompactionReason(reason) === "timeout";
const reasonSuffix = options?.includeDetails && reason && !isTimeout ? ` Reason: ${reason}.` : "";
const summary = isTimeout
? "⚠️ Context is too large and auto-compaction timed out before it could finish."
: "⚠️ Context is too large and auto-compaction could not recover this turn.";
return `${summary}${reasonSuffix} Try again, use /compact, or use /new to start a fresh session.`;
}
export function buildAuthProfileFailoverFailureText(error: unknown): string | null {
@@ -73,7 +73,7 @@ describe("config compaction settings", () => {
expect(compaction?.qualityGuard?.maxRetries).toBe(99);
});
it.each(["off", "low", "adaptive", "max", "ultra"] as const)(
it.each(["off", "low", "adaptive", "max", "ultra", "inherit"] as const)(
"preserves compaction thinkingLevel=%s during materialization",
(thinkingLevel) => {
expect(materializeCompactionConfig({ thinkingLevel })?.thinkingLevel).toBe(thinkingLevel);
+1 -1
View File
@@ -129,7 +129,7 @@ export const AGENT_FIELD_HELP: Record<string, string> = {
"agents.defaults.compaction.provider":
"Id of a registered compaction provider plugin used for summarization. When set and the provider is registered, its summarize() method is called instead of the built-in summarizeInStages pipeline. Falls back to built-in on provider failure. Leave unset to use the default built-in summarization.",
"agents.defaults.compaction.thinkingLevel":
'Optional thinking level used only for embedded OpenClaw compaction summaries: "off", "minimal", "low", "medium", "high", "xhigh", "adaptive", "max", or "ultra". It overrides the session level and is clamped to the actual compaction model/runtime; leave unset to inherit the session level. Native Codex app-server compaction ignores this setting because its compact request has no per-operation thinking override, and OpenClaw logs a warning.',
'Thinking level used only for embedded OpenClaw compaction summaries: "off", "minimal", "low", "medium", "high", "xhigh", "adaptive", "max", "ultra", or "inherit". The default is "low"; set "inherit" to reuse the session level. The selected level is clamped to the actual compaction model/runtime. Native Codex app-server compaction ignores this setting because its compact request has no per-operation thinking override, and OpenClaw logs a warning.',
"agents.defaults.compaction.keepRecentTokens":
"Minimum token budget preserved from the most recent conversation window during compaction. Use higher values to protect immediate context continuity and lower values to keep more long-tail history.",
"agents.defaults.compaction.identifierPolicy":
@@ -385,6 +385,7 @@ export const ENUM_EXPECTATIONS: Record<string, string[]> = {
'"adaptive"',
'"max"',
'"ultra"',
'"inherit"',
],
"agents.defaults.compaction.identifierPolicy": ['"strict"', '"off"'],
};
+2 -2
View File
@@ -384,8 +384,8 @@ export type AgentCompactionConfig = {
enabled?: boolean;
/** Compaction summarization mode. */
mode?: AgentCompactionMode;
/** Override the session thinking level for embedded OpenClaw compaction summaries. */
thinkingLevel?: AgentThinkingLevel;
/** Thinking level for embedded OpenClaw compaction summaries. Default: low. */
thinkingLevel?: AgentThinkingLevel | "inherit";
/** Embedded OpenClaw keepRecentTokens budget used for cut-point selection. */
keepRecentTokens?: number;
/** Preserve this many most-recent user/assistant turns verbatim in compaction summary context. */
@@ -360,6 +360,7 @@ describe("agent defaults schema", () => {
"adaptive",
"max",
"ultra",
"inherit",
] as const)("accepts compaction.thinkingLevel=%s", (thinkingLevel) => {
const result = AgentDefaultsSchema.parse({ compaction: { thinkingLevel } })!;
expect(result.compaction?.thinkingLevel).toBe(thinkingLevel);
+1 -1
View File
@@ -141,7 +141,7 @@ export const AgentDefaultsSchema = z
enabled: z.boolean().optional(),
mode: z.union([z.literal("default"), z.literal("safeguard")]).optional(),
provider: z.string().optional(),
thinkingLevel: AgentThinkingLevelSchema.optional(),
thinkingLevel: z.union([AgentThinkingLevelSchema, z.literal("inherit")]).optional(),
keepRecentTokens: z.number().int().positive().optional(),
identifierPolicy: z.union([z.literal("strict"), z.literal("off")]).optional(),
recentTurnsPreserve: z.number().int().min(0).max(12).optional(),