From 4c0b873a4dd215072dfe44d75030f512f79904a1 Mon Sep 17 00:00:00 2001 From: Rodrigo Uroz Date: Sat, 7 Mar 2026 12:13:13 -0300 Subject: [PATCH] Config/Compaction: expose safeguard preserve and quality settings (#25557) Merged via squash. Prepared head SHA: ea9904039a35d8d9ced55cd6d1c459a46666954d Co-authored-by: rodrigouroz <384037+rodrigouroz@users.noreply.github.com> Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com> Reviewed-by: @jalehman --- CHANGELOG.md | 2 + src/agents/pi-embedded-runner/extensions.ts | 1 + .../compaction-safeguard.test.ts | 35 +++++++++++++++++ src/config/config.compaction-settings.test.ts | 39 +++++++++++++++++++ src/config/schema.help.quality.test.ts | 5 +++ src/config/schema.help.ts | 2 + src/config/schema.labels.ts | 1 + src/config/types.agent-defaults.ts | 2 + src/config/zod-schema.agent-defaults.ts | 1 + 9 files changed, 88 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa75cd58cae..1a42a34c583 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ Docs: https://docs.openclaw.ai - Tools/Diffs guidance: restore a short system-prompt hint for enabled diffs while keeping the detailed instructions in the companion skill, so diffs usage guidance stays out of user-prompt space. (#36904) thanks @gumadeiras. - Tools/Diffs guidance loading: move diffs usage guidance from unconditional prompt-hook injection to the plugin companion skill path, reducing unrelated-turn prompt noise while keeping diffs tool behavior unchanged. (#32630) thanks @sircrumpet. - Docs/Web search: remove outdated Brave free-tier wording and replace prescriptive AI ToS guidance with neutral compliance language in Brave setup docs. (#26860) Thanks @HenryLoenwind. +- Config/Compaction safeguard tuning: expose `agents.defaults.compaction.recentTurnsPreserve` and quality-guard retry knobs through the validated config surface and embedded-runner wiring, with regression coverage for real config loading and schema metadata. (#25557) thanks @rodrigouroz. ### Breaking @@ -232,6 +233,7 @@ Docs: https://docs.openclaw.ai - Gateway/Windows restart supervision: relaunch task-managed gateways through Scheduled Task with quoted helper-script command paths, distinguish restart-capable supervisors per platform, and stop orphaned Windows gateway children during self-restart. (#38825) Thanks @obviyus. - Telegram/native topic command routing: resolve forum-topic native commands through the same conversation route as inbound messages so topic `agentId` overrides and bound topic sessions target the active session instead of the default topic-parent session. (#38871) Thanks @obviyus. - Markdown/assistant image hardening: flatten remote markdown images to plain text across the Control UI, exported HTML, and shared Swift chat while keeping inline `data:image/...` markdown renderable, so model output no longer triggers automatic remote image fetches. (#38895) Thanks @obviyus. +- Config/compaction safeguard settings: regression-test `agents.defaults.compaction.recentTurnsPreserve` through `loadConfig()` and cover the new help metadata entry so the exposed preserve knob stays wired through schema validation and config UX. (#25557) thanks @rodrigouroz. ## 2026.3.2 diff --git a/src/agents/pi-embedded-runner/extensions.ts b/src/agents/pi-embedded-runner/extensions.ts index 8833e175461..251063c6f19 100644 --- a/src/agents/pi-embedded-runner/extensions.ts +++ b/src/agents/pi-embedded-runner/extensions.ts @@ -87,6 +87,7 @@ export function buildEmbeddedExtensionFactories(params: { qualityGuardEnabled: qualityGuardCfg?.enabled ?? false, qualityGuardMaxRetries: qualityGuardCfg?.maxRetries, model: params.model, + recentTurnsPreserve: compactionCfg?.recentTurnsPreserve, }); factories.push(compactionSafeguardExtension); } diff --git a/src/agents/pi-extensions/compaction-safeguard.test.ts b/src/agents/pi-extensions/compaction-safeguard.test.ts index e694b6137eb..c88ce044262 100644 --- a/src/agents/pi-extensions/compaction-safeguard.test.ts +++ b/src/agents/pi-extensions/compaction-safeguard.test.ts @@ -5,7 +5,9 @@ import type { AgentMessage } from "@mariozechner/pi-agent-core"; import type { Api, Model } from "@mariozechner/pi-ai"; import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent"; import { describe, expect, it, vi } from "vitest"; +import type { OpenClawConfig } from "../../config/config.js"; import * as compactionModule from "../compaction.js"; +import { buildEmbeddedExtensionFactories } from "../pi-embedded-runner/extensions.js"; import { castAgentMessage } from "../test-helpers/agent-message-fixtures.js"; import { getCompactionSafeguardRuntime, @@ -403,6 +405,39 @@ describe("compaction-safeguard runtime registry", () => { model, }); }); + + it("wires oversized safeguard runtime values when config validation is bypassed", () => { + const sessionManager = {} as unknown as Parameters< + typeof buildEmbeddedExtensionFactories + >[0]["sessionManager"]; + const cfg = { + agents: { + defaults: { + compaction: { + mode: "safeguard", + recentTurnsPreserve: 99, + qualityGuard: { maxRetries: 99 }, + }, + }, + }, + } as OpenClawConfig; + + buildEmbeddedExtensionFactories({ + cfg, + sessionManager, + provider: "anthropic", + modelId: "claude-3-opus", + model: { + contextWindow: 200_000, + } as Parameters[0]["model"], + }); + + const runtime = getCompactionSafeguardRuntime(sessionManager); + expect(runtime?.qualityGuardMaxRetries).toBe(99); + expect(runtime?.recentTurnsPreserve).toBe(99); + expect(resolveQualityGuardMaxRetries(runtime?.qualityGuardMaxRetries)).toBe(3); + expect(resolveRecentTurnsPreserve(runtime?.recentTurnsPreserve)).toBe(12); + }); }); describe("compaction-safeguard recent-turn preservation", () => { diff --git a/src/config/config.compaction-settings.test.ts b/src/config/config.compaction-settings.test.ts index 04674a7a7ac..0943a47949f 100644 --- a/src/config/config.compaction-settings.test.ts +++ b/src/config/config.compaction-settings.test.ts @@ -89,4 +89,43 @@ describe("config compaction settings", () => { }, ); }); + + it("preserves recent turn safeguard values through loadConfig()", async () => { + await withTempHomeConfig( + { + agents: { + defaults: { + compaction: { + mode: "safeguard", + recentTurnsPreserve: 4, + }, + }, + }, + }, + async () => { + const cfg = loadConfig(); + expect(cfg.agents?.defaults?.compaction?.recentTurnsPreserve).toBe(4); + }, + ); + }); + + it("preserves oversized quality guard retry values for runtime clamping", async () => { + await withTempHomeConfig( + { + agents: { + defaults: { + compaction: { + qualityGuard: { + maxRetries: 99, + }, + }, + }, + }, + }, + async () => { + const cfg = loadConfig(); + expect(cfg.agents?.defaults?.compaction?.qualityGuard?.maxRetries).toBe(99); + }, + ); + }); }); diff --git a/src/config/schema.help.quality.test.ts b/src/config/schema.help.quality.test.ts index 2ef7d8aae3a..ab685448fdf 100644 --- a/src/config/schema.help.quality.test.ts +++ b/src/config/schema.help.quality.test.ts @@ -372,6 +372,7 @@ const TARGET_KEYS = [ "agents.defaults.compaction.maxHistoryShare", "agents.defaults.compaction.identifierPolicy", "agents.defaults.compaction.identifierInstructions", + "agents.defaults.compaction.recentTurnsPreserve", "agents.defaults.compaction.qualityGuard", "agents.defaults.compaction.qualityGuard.enabled", "agents.defaults.compaction.qualityGuard.maxRetries", @@ -796,6 +797,10 @@ describe("config help copy quality", () => { expect(identifierPolicy.includes('"off"')).toBe(true); expect(identifierPolicy.includes('"custom"')).toBe(true); + const recentTurnsPreserve = FIELD_HELP["agents.defaults.compaction.recentTurnsPreserve"]; + expect(/recent.*turn|verbatim/i.test(recentTurnsPreserve)).toBe(true); + expect(/default:\s*3/i.test(recentTurnsPreserve)).toBe(true); + const postCompactionSections = FIELD_HELP["agents.defaults.compaction.postCompactionSections"]; expect(/Session Startup|Red Lines/i.test(postCompactionSections)).toBe(true); expect(/Every Session|Safety/i.test(postCompactionSections)).toBe(true); diff --git a/src/config/schema.help.ts b/src/config/schema.help.ts index c97aa0408a4..f13944cb127 100644 --- a/src/config/schema.help.ts +++ b/src/config/schema.help.ts @@ -999,6 +999,8 @@ export const FIELD_HELP: Record = { 'Identifier-preservation policy for compaction summaries: "strict" prepends built-in opaque-identifier retention guidance (default), "off" disables this prefix, and "custom" uses identifierInstructions. Keep "strict" unless you have a specific compatibility need.', "agents.defaults.compaction.identifierInstructions": 'Custom identifier-preservation instruction text used when identifierPolicy="custom". Keep this explicit and safety-focused so compaction summaries do not rewrite opaque IDs, URLs, hosts, or ports.', + "agents.defaults.compaction.recentTurnsPreserve": + "Number of most recent user/assistant turns kept verbatim outside safeguard summarization (default: 3). Raise this to preserve exact recent dialogue context, or lower it to maximize compaction savings.", "agents.defaults.compaction.qualityGuard": "Optional quality-audit retry settings for safeguard compaction summaries. Leave this disabled unless you explicitly want summary audits and one-shot regeneration on failed checks.", "agents.defaults.compaction.qualityGuard.enabled": diff --git a/src/config/schema.labels.ts b/src/config/schema.labels.ts index e14e66cb266..9266516b957 100644 --- a/src/config/schema.labels.ts +++ b/src/config/schema.labels.ts @@ -452,6 +452,7 @@ export const FIELD_LABELS: Record = { "agents.defaults.compaction.maxHistoryShare": "Compaction Max History Share", "agents.defaults.compaction.identifierPolicy": "Compaction Identifier Policy", "agents.defaults.compaction.identifierInstructions": "Compaction Identifier Instructions", + "agents.defaults.compaction.recentTurnsPreserve": "Compaction Preserve Recent Turns", "agents.defaults.compaction.qualityGuard": "Compaction Quality Guard", "agents.defaults.compaction.qualityGuard.enabled": "Compaction Quality Guard Enabled", "agents.defaults.compaction.qualityGuard.maxRetries": "Compaction Quality Guard Max Retries", diff --git a/src/config/types.agent-defaults.ts b/src/config/types.agent-defaults.ts index a7c40a5016b..a242d0bbcc1 100644 --- a/src/config/types.agent-defaults.ts +++ b/src/config/types.agent-defaults.ts @@ -306,6 +306,8 @@ export type AgentCompactionConfig = { reserveTokensFloor?: number; /** Max share of context window for history during safeguard pruning (0.1–0.9, default 0.5). */ maxHistoryShare?: number; + /** Preserve this many most-recent user/assistant turns verbatim in compaction summary context. */ + recentTurnsPreserve?: number; /** Identifier-preservation instruction policy for compaction summaries. */ identifierPolicy?: AgentCompactionIdentifierPolicy; /** Custom identifier-preservation instructions used when identifierPolicy is "custom". */ diff --git a/src/config/zod-schema.agent-defaults.ts b/src/config/zod-schema.agent-defaults.ts index 7c43a5a382d..1e83a92f54c 100644 --- a/src/config/zod-schema.agent-defaults.ts +++ b/src/config/zod-schema.agent-defaults.ts @@ -95,6 +95,7 @@ export const AgentDefaultsSchema = z .union([z.literal("strict"), z.literal("off"), z.literal("custom")]) .optional(), identifierInstructions: z.string().optional(), + recentTurnsPreserve: z.number().int().min(0).max(12).optional(), qualityGuard: z .object({ enabled: z.boolean().optional(),