From 45b8750d8bb40bddcc5bd9a8652197f2b516fe16 Mon Sep 17 00:00:00 2001 From: "Jason (Json)" <263060202+fuller-stack-dev@users.noreply.github.com> Date: Wed, 19 Aug 2026 16:18:06 -0600 Subject: [PATCH] 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 --- docs/.generated/config-baseline.counts.json | 2 +- docs/.generated/config-baseline.sha256 | 8 +++---- docs/gateway/config-agents.md | 4 ++-- .../session-management-compaction.md | 2 +- .../compact.hooks.test.ts | 1 + .../compaction-runtime-context.test.ts | 22 ++++++++++++++++--- .../compaction-runtime-context.ts | 3 ++- .../reply/agent-runner-failure-reply.test.ts | 18 ++++++++++++++- .../reply/agent-runner-failure-reply.ts | 12 +++++----- src/config/config.compaction-settings.test.ts | 2 +- src/config/schema.help.agents.ts | 2 +- .../schema.help.quality.test-fixtures.ts | 1 + src/config/types.agent-defaults.ts | 4 ++-- src/config/zod-schema.agent-defaults.test.ts | 1 + src/config/zod-schema.agent-defaults.ts | 2 +- 15 files changed, 61 insertions(+), 23 deletions(-) diff --git a/docs/.generated/config-baseline.counts.json b/docs/.generated/config-baseline.counts.json index 850d556ee90e..5b9dc74cbc32 100644 --- a/docs/.generated/config-baseline.counts.json +++ b/docs/.generated/config-baseline.counts.json @@ -1,5 +1,5 @@ { "core": 2333, "channel": 3582, - "plugin": 4002 + "plugin": 3978 } diff --git a/docs/.generated/config-baseline.sha256 b/docs/.generated/config-baseline.sha256 index 355f1523fa82..98e08b303a11 100644 --- a/docs/.generated/config-baseline.sha256 +++ b/docs/.generated/config-baseline.sha256 @@ -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 diff --git a/docs/gateway/config-agents.md b/docs/gateway/config-agents.md index 094acb5086a2..50830636a67d 100644 --- a/docs/gateway/config-agents.md +++ b/docs/gateway/config-agents.md @@ -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`. diff --git a/docs/reference/session-management-compaction.md b/docs/reference/session-management-compaction.md index 2c4e081903b8..070183abe099 100644 --- a/docs/reference/session-management-compaction.md +++ b/docs/reference/session-management-compaction.md @@ -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`. diff --git a/src/agents/embedded-agent-runner/compact.hooks.test.ts b/src/agents/embedded-agent-runner/compact.hooks.test.ts index cdebfa79ce23..1d04068bd350 100644 --- a/src/agents/embedded-agent-runner/compact.hooks.test.ts +++ b/src/agents/embedded-agent-runner/compact.hooks.test.ts @@ -1606,6 +1606,7 @@ describe("compactEmbeddedAgentSessionDirect hooks", () => { config: { agents: { defaults: { + compaction: { thinkingLevel: "inherit" as const }, models: { "openai/gpt-5.6-sol": { agentRuntime: { id: "openclaw" } }, }, diff --git a/src/agents/embedded-agent-runner/compaction-runtime-context.test.ts b/src/agents/embedded-agent-runner/compaction-runtime-context.test.ts index 6faadb2d4d70..23ba31c287ea 100644 --- a/src/agents/embedded-agent-runner/compaction-runtime-context.test.ts +++ b/src/agents/embedded-agent-runner/compaction-runtime-context.test.ts @@ -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", diff --git a/src/agents/embedded-agent-runner/compaction-runtime-context.ts b/src/agents/embedded-agent-runner/compaction-runtime-context.ts index 442d9198b970..6185c2cfe698 100644 --- a/src/agents/embedded-agent-runner/compaction-runtime-context.ts +++ b/src/agents/embedded-agent-runner/compaction-runtime-context.ts @@ -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"; } diff --git a/src/auto-reply/reply/agent-runner-failure-reply.test.ts b/src/auto-reply/reply/agent-runner-failure-reply.test.ts index 922dca9fd2be..377b70778796 100644 --- a/src/auto-reply/reply/agent-runner-failure-reply.test.ts +++ b/src/auto-reply/reply/agent-runner-failure-reply.test.ts @@ -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.", + ); + }); +}); diff --git a/src/auto-reply/reply/agent-runner-failure-reply.ts b/src/auto-reply/reply/agent-runner-failure-reply.ts index 1a180443ce8b..9af0515e6ee9 100644 --- a/src/auto-reply/reply/agent-runner-failure-reply.ts +++ b/src/auto-reply/reply/agent-runner-failure-reply.ts @@ -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 { diff --git a/src/config/config.compaction-settings.test.ts b/src/config/config.compaction-settings.test.ts index efb071b5e1b8..e0c064559aac 100644 --- a/src/config/config.compaction-settings.test.ts +++ b/src/config/config.compaction-settings.test.ts @@ -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); diff --git a/src/config/schema.help.agents.ts b/src/config/schema.help.agents.ts index 0170f32f9cb2..e69918cd8d1b 100644 --- a/src/config/schema.help.agents.ts +++ b/src/config/schema.help.agents.ts @@ -129,7 +129,7 @@ export const AGENT_FIELD_HELP: Record = { "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": diff --git a/src/config/schema.help.quality.test-fixtures.ts b/src/config/schema.help.quality.test-fixtures.ts index dab49f56e7f6..1aadf292006f 100644 --- a/src/config/schema.help.quality.test-fixtures.ts +++ b/src/config/schema.help.quality.test-fixtures.ts @@ -385,6 +385,7 @@ export const ENUM_EXPECTATIONS: Record = { '"adaptive"', '"max"', '"ultra"', + '"inherit"', ], "agents.defaults.compaction.identifierPolicy": ['"strict"', '"off"'], }; diff --git a/src/config/types.agent-defaults.ts b/src/config/types.agent-defaults.ts index ec02cfbf8696..32c1d074c377 100644 --- a/src/config/types.agent-defaults.ts +++ b/src/config/types.agent-defaults.ts @@ -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. */ diff --git a/src/config/zod-schema.agent-defaults.test.ts b/src/config/zod-schema.agent-defaults.test.ts index 4e31202814d6..4115f1ab2567 100644 --- a/src/config/zod-schema.agent-defaults.test.ts +++ b/src/config/zod-schema.agent-defaults.test.ts @@ -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); diff --git a/src/config/zod-schema.agent-defaults.ts b/src/config/zod-schema.agent-defaults.ts index d3055b4461d9..39ba69d76b27 100644 --- a/src/config/zod-schema.agent-defaults.ts +++ b/src/config/zod-schema.agent-defaults.ts @@ -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(),