diff --git a/src/agents/pi-embedded-runner/extensions.ts b/src/agents/pi-embedded-runner/extensions.ts index fc0e76acdc9..0958d248973 100644 --- a/src/agents/pi-embedded-runner/extensions.ts +++ b/src/agents/pi-embedded-runner/extensions.ts @@ -82,6 +82,9 @@ export function buildEmbeddedExtensionFactories(params: { maxHistoryShare: compactionCfg?.maxHistoryShare, contextWindowTokens: contextWindowInfo.tokens, model: params.model, + recentTurnsPreserve: compactionCfg?.recentTurnsPreserve, + qualityGuardEnabled: compactionCfg?.qualityGuard?.enabled, + qualityGuardMaxRetries: compactionCfg?.qualityGuard?.maxRetries, }); factories.push(compactionSafeguardExtension); } diff --git a/src/agents/pi-extensions/compaction-safeguard.test.ts b/src/agents/pi-extensions/compaction-safeguard.test.ts index 673d543f61c..acdb4fa466d 100644 --- a/src/agents/pi-extensions/compaction-safeguard.test.ts +++ b/src/agents/pi-extensions/compaction-safeguard.test.ts @@ -2,6 +2,8 @@ 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 { buildEmbeddedExtensionFactories } from "../pi-embedded-runner/extensions.js"; import { getCompactionSafeguardRuntime, setCompactionSafeguardRuntime, @@ -368,6 +370,39 @@ describe("compaction-safeguard runtime registry", () => { model, }); }); + + it("wires quality guard retries from config and clamps safeguard runtime usage", () => { + 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/schema.help.ts b/src/config/schema.help.ts index c534cc4097f..529911d6e9e 100644 --- a/src/config/schema.help.ts +++ b/src/config/schema.help.ts @@ -842,6 +842,14 @@ export const FIELD_HELP: Record = { "Requires at least this many newly appended bytes before session transcript changes trigger reindex (default: 100000). Increase to reduce frequent small reindexes, or lower for faster transcript freshness.", "agents.defaults.memorySearch.sync.sessions.deltaMessages": "Requires at least this many appended transcript messages before reindex is triggered (default: 50). Lower this for near-real-time transcript recall, or raise it to reduce indexing churn.", + "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.postIndexSync": + 'Controls post-compaction session index sync mode: "off", "async", or "await" (default: "async"). Use "await" for strongest consistency, "async" for lower latency, and "off" only when downstream indexing is externally managed.', + "agents.defaults.compaction.qualityGuard.enabled": + "Enables safeguard summary quality validation and retry logic (default: true). Keep enabled to reduce malformed summaries; disable only when minimizing compaction latency is more important than summary structure checks.", + "agents.defaults.compaction.qualityGuard.maxRetries": + "Maximum additional safeguard summarization attempts after a failed quality audit (default: 1). Increase cautiously for better summary compliance at the cost of extra token usage and latency.", ui: "UI presentation settings for accenting and assistant identity shown in control surfaces. Use this for branding and readability customization without changing runtime behavior.", "ui.seamColor": "Primary accent/seam color used by UI surfaces for emphasis, badges, and visual identity cues. Use high-contrast values that remain readable across light/dark themes.", diff --git a/src/config/schema.labels.ts b/src/config/schema.labels.ts index f7b364590fa..05a4f3401a5 100644 --- a/src/config/schema.labels.ts +++ b/src/config/schema.labels.ts @@ -311,6 +311,10 @@ export const FIELD_LABELS: Record = { "agents.defaults.memorySearch.sync.watchDebounceMs": "Memory Watch Debounce (ms)", "agents.defaults.memorySearch.sync.sessions.deltaBytes": "Session Delta Bytes", "agents.defaults.memorySearch.sync.sessions.deltaMessages": "Session Delta Messages", + "agents.defaults.compaction.recentTurnsPreserve": "Compaction Preserve Recent Turns", + "agents.defaults.compaction.postIndexSync": "Compaction Post-Index Sync", + "agents.defaults.compaction.qualityGuard.enabled": "Compaction Quality Guard", + "agents.defaults.compaction.qualityGuard.maxRetries": "Compaction Quality Guard Retries", "agents.defaults.memorySearch.query.maxResults": "Memory Search Max Results", "agents.defaults.memorySearch.query.minScore": "Memory Search Min Score", "agents.defaults.memorySearch.query.hybrid.enabled": "Memory Search Hybrid", diff --git a/src/config/types.agent-defaults.ts b/src/config/types.agent-defaults.ts index afc65e3daec..738b463496a 100644 --- a/src/config/types.agent-defaults.ts +++ b/src/config/types.agent-defaults.ts @@ -271,6 +271,12 @@ 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; + /** Post-compaction memory session-index sync behavior. */ + postIndexSync?: "off" | "async" | "await"; + /** Post-compaction quality guard settings (safeguard mode). */ + qualityGuard?: AgentCompactionQualityGuardConfig; /** Pre-compaction memory flush (agentic turn). Default: enabled. */ memoryFlush?: AgentCompactionMemoryFlushConfig; }; @@ -285,3 +291,10 @@ export type AgentCompactionMemoryFlushConfig = { /** System prompt appended for the memory flush turn. */ systemPrompt?: string; }; + +export type AgentCompactionQualityGuardConfig = { + /** Enable summary quality checks and automatic retry (default: true). */ + enabled?: boolean; + /** Maximum retries when summary quality checks fail (default: 1). */ + maxRetries?: number; +}; diff --git a/src/config/zod-schema.agent-defaults.ts b/src/config/zod-schema.agent-defaults.ts index aa39a70978b..01662f8a40c 100644 --- a/src/config/zod-schema.agent-defaults.ts +++ b/src/config/zod-schema.agent-defaults.ts @@ -84,6 +84,17 @@ export const AgentDefaultsSchema = z keepRecentTokens: z.number().int().positive().optional(), reserveTokensFloor: z.number().int().nonnegative().optional(), maxHistoryShare: z.number().min(0.1).max(0.9).optional(), + recentTurnsPreserve: z.number().int().min(0).max(12).optional(), + postIndexSync: z + .union([z.literal("off"), z.literal("async"), z.literal("await")]) + .optional(), + qualityGuard: z + .object({ + enabled: z.boolean().optional(), + maxRetries: z.number().int().min(0).max(3).optional(), + }) + .strict() + .optional(), memoryFlush: z .object({ enabled: z.boolean().optional(),