From 7920f8d4fdeb6e000795cf53751b341cc5776fb4 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 25 Apr 2026 04:02:57 +0100 Subject: [PATCH] fix(compaction): honor manual keepRecentTokens --- CHANGELOG.md | 1 + docs/.generated/config-baseline.sha256 | 4 +- docs/concepts/compaction.md | 5 ++ docs/gateway/config-agents.md | 4 + .../session-management-compaction.md | 8 ++ src/agents/pi-embedded-runner/compact.ts | 2 + .../pi-embedded-runner/extensions.test.ts | 20 ++++- src/agents/pi-embedded-runner/extensions.ts | 2 +- .../manual-compaction-boundary.test.ts | 33 ++++++++ .../manual-compaction-boundary.ts | 14 ++++ .../pi-hooks/compaction-safeguard-quality.ts | 1 + .../pi-hooks/compaction-safeguard.test.ts | 80 +++++++++++++++++++ src/agents/pi-hooks/compaction-safeguard.ts | 36 ++++++++- src/config/schema.base.generated.ts | 8 +- src/config/schema.help.ts | 4 +- 15 files changed, 210 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ff48909b87a..be9fdd5659c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -67,6 +67,7 @@ Docs: https://docs.openclaw.ai ### Fixes +- Compaction: honor explicit `agents.defaults.compaction.keepRecentTokens` for manual `/compact`, re-distill safeguard summaries instead of snowballing previous summaries, and enable safeguard summary quality checks by default. Fixes #71357. Thanks @WhiteGiverMa. - Sessions: honor configured `session.maintenance` settings during load-time maintenance instead of falling back to default entry caps. Fixes #71356. Thanks @comolago. - Browser/sandbox: pass the resolved `browser.ssrfPolicy` into sandbox browser bridges and refresh cached bridges when the effective policy changes, so sandboxed browser navigation honors private-network opt-ins. Fixes #45153 and #57055. Thanks @jzakirov, @zuoanCo, and @kybrcore. - Browser/proxy: keep Gateway/provider proxy environment variables from proxying the OpenClaw-managed browser, so `HTTP_PROXY` and `HTTPS_PROXY` no longer block ordinary browser navigation. Fixes #71358. Thanks @Sanjays2402. diff --git a/docs/.generated/config-baseline.sha256 b/docs/.generated/config-baseline.sha256 index 661d9ede57d..b4da6f725d4 100644 --- a/docs/.generated/config-baseline.sha256 +++ b/docs/.generated/config-baseline.sha256 @@ -1,4 +1,4 @@ -8f23e853ccde6cd021b84b32fe205f456f8516667683d16c9b56d6598f608989 config-baseline.json -037bf4a873587adb8349f531c0ad79cd4f90e01712f5aa5d8b4387be73538a7f config-baseline.core.json +71ef32b7723f64d4a84ac43bb6d41ff21e0d77a099b42e026d8b0d3d5301f917 config-baseline.json +cfab1910132ed23777005e0c650a13f44626b0450963f733e9de56a13323ae2b config-baseline.core.json 22d7cd6d8279146b2d79c9531a55b80b52a2c99c81338c508104729154fdd02d config-baseline.channel.json 86f615b7d267b03888af0af7ccb3f8232a6b636f8a741d522ff425e46729ba81 config-baseline.plugin.json diff --git a/docs/concepts/compaction.md b/docs/concepts/compaction.md index 25d188ac3de..5a1f3dab24a 100644 --- a/docs/concepts/compaction.md +++ b/docs/concepts/compaction.md @@ -113,6 +113,11 @@ the summary: /compact Focus on the API design decisions ``` +When `agents.defaults.compaction.keepRecentTokens` is set, manual compaction +honors that Pi cut-point and keeps the recent tail in rebuilt context. Without +an explicit keep budget, manual compaction behaves as a hard checkpoint and +continues from the new summary alone. + ## Using a different model By default, compaction uses your agent's primary model. You can use a more diff --git a/docs/gateway/config-agents.md b/docs/gateway/config-agents.md index 8a5e86e41cb..ad7fff44e7e 100644 --- a/docs/gateway/config-agents.md +++ b/docs/gateway/config-agents.md @@ -542,8 +542,10 @@ Periodic heartbeat runs. provider: "my-provider", // id of a registered compaction provider plugin (optional) timeoutSeconds: 900, reserveTokensFloor: 24000, + keepRecentTokens: 50000, identifierPolicy: "strict", // strict | off | custom identifierInstructions: "Preserve deployment IDs, ticket IDs, and host:port pairs exactly.", // used when identifierPolicy=custom + qualityGuard: { enabled: true, maxRetries: 1 }, postCompactionSections: ["Session Startup", "Red Lines"], // [] disables reinjection model: "openrouter/anthropic/claude-sonnet-4-6", // optional compaction-only model override notifyUser: true, // send brief notices when compaction starts and completes (default: false) @@ -562,8 +564,10 @@ Periodic heartbeat runs. - `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). - `timeoutSeconds`: maximum seconds allowed for a single compaction operation before OpenClaw aborts it. Default: `900`. +- `keepRecentTokens`: Pi cut-point budget for keeping the most recent transcript tail verbatim. Manual `/compact` honors this when explicitly set; otherwise manual compaction is a hard checkpoint. - `identifierPolicy`: `strict` (default), `off`, or `custom`. `strict` prepends built-in opaque identifier retention guidance during compaction summarization. - `identifierInstructions`: optional custom identifier-preservation text used when `identifierPolicy=custom`. +- `qualityGuard`: retry-on-malformed-output checks for safeguard summaries. Enabled by default in safeguard mode; set `enabled: false` to skip the audit. - `postCompactionSections`: optional AGENTS.md H2/H3 section names to re-inject after compaction. Defaults to `["Session Startup", "Red Lines"]`; set `[]` to disable reinjection. When unset or explicitly set to that default pair, older `Every Session`/`Safety` headings are also accepted as a legacy fallback. - `model`: optional `provider/model-id` override for compaction summarization only. Use this when the main session should keep one model but compaction summaries should run on another; when unset, compaction uses the session's primary model. - `notifyUser`: when `true`, sends brief notices to the user when compaction starts and when it completes (for example, "Compacting context..." and "Compaction complete"). Disabled by default to keep compaction silent. diff --git a/docs/reference/session-management-compaction.md b/docs/reference/session-management-compaction.md index 38254eb4b0e..679b87ddba8 100644 --- a/docs/reference/session-management-compaction.md +++ b/docs/reference/session-management-compaction.md @@ -267,6 +267,10 @@ OpenClaw also enforces a safety floor for embedded runs: - Default floor is `20000` tokens. - Set `agents.defaults.compaction.reserveTokensFloor: 0` to disable the floor. - If it’s already higher, OpenClaw leaves it alone. +- Manual `/compact` honors an explicit `agents.defaults.compaction.keepRecentTokens` + and keeps Pi's recent-tail cut point. Without an explicit keep budget, + manual compaction remains a hard checkpoint and rebuilt context starts from + the new summary. Why: leave enough headroom for multi-turn “housekeeping” (like memory writes) before compaction becomes unavoidable. @@ -283,6 +287,10 @@ Plugins can register a compaction provider via `registerCompactionProvider()` on - Setting a `provider` forces `mode: "safeguard"`. - Providers receive the same compaction instructions and identifier-preservation policy as the built-in path. - The safeguard still preserves recent-turn and split-turn suffix context after provider output. +- Built-in safeguard summarization re-distills prior summaries with new messages + instead of preserving the full previous summary verbatim. +- Safeguard mode enables summary quality audits by default; set + `qualityGuard.enabled: false` to skip retry-on-malformed-output behavior. - If the provider fails or returns an empty result, OpenClaw falls back to built-in LLM summarization automatically. - Abort/timeout signals are re-thrown (not swallowed) to respect caller cancellation. diff --git a/src/agents/pi-embedded-runner/compact.ts b/src/agents/pi-embedded-runner/compact.ts index 09fa3233b39..30f042b8e4c 100644 --- a/src/agents/pi-embedded-runner/compact.ts +++ b/src/agents/pi-embedded-runner/compact.ts @@ -1059,6 +1059,8 @@ export async function compactEmbeddedPiSessionDirect( try { const hardenedBoundary = await hardenManualCompactionBoundary({ sessionFile: params.sessionFile, + preserveRecentTail: + typeof params.config?.agents?.defaults?.compaction?.keepRecentTokens === "number", }); if (hardenedBoundary.applied) { effectiveFirstKeptEntryId = diff --git a/src/agents/pi-embedded-runner/extensions.test.ts b/src/agents/pi-embedded-runner/extensions.test.ts index e2472042d6b..2c223d240b1 100644 --- a/src/agents/pi-embedded-runner/extensions.test.ts +++ b/src/agents/pi-embedded-runner/extensions.test.ts @@ -45,7 +45,7 @@ function expectSafeguardRuntime( } describe("buildEmbeddedExtensionFactories", () => { - it("does not opt safeguard mode into quality-guard retries", () => { + it("enables quality-guard retries by default in safeguard mode", () => { const cfg = { agents: { defaults: { @@ -55,6 +55,24 @@ describe("buildEmbeddedExtensionFactories", () => { }, }, } as OpenClawConfig; + expectSafeguardRuntime(cfg, { + qualityGuardEnabled: true, + }); + }); + + it("honors explicit safeguard quality-guard disablement", () => { + const cfg = { + agents: { + defaults: { + compaction: { + mode: "safeguard", + qualityGuard: { + enabled: false, + }, + }, + }, + }, + } as OpenClawConfig; expectSafeguardRuntime(cfg, { qualityGuardEnabled: false, }); diff --git a/src/agents/pi-embedded-runner/extensions.ts b/src/agents/pi-embedded-runner/extensions.ts index 483786d4414..0603e989bf5 100644 --- a/src/agents/pi-embedded-runner/extensions.ts +++ b/src/agents/pi-embedded-runner/extensions.ts @@ -162,7 +162,7 @@ export function buildEmbeddedExtensionFactories(params: { identifierPolicy: compactionCfg?.identifierPolicy, identifierInstructions: compactionCfg?.identifierInstructions, customInstructions: compactionCfg?.customInstructions, - qualityGuardEnabled: qualityGuardCfg?.enabled ?? false, + qualityGuardEnabled: qualityGuardCfg?.enabled ?? true, qualityGuardMaxRetries: qualityGuardCfg?.maxRetries, model: params.model, recentTurnsPreserve: compactionCfg?.recentTurnsPreserve, diff --git a/src/agents/pi-embedded-runner/manual-compaction-boundary.test.ts b/src/agents/pi-embedded-runner/manual-compaction-boundary.test.ts index eb977385ba4..85867b31e14 100644 --- a/src/agents/pi-embedded-runner/manual-compaction-boundary.test.ts +++ b/src/agents/pi-embedded-runner/manual-compaction-boundary.test.ts @@ -118,6 +118,39 @@ describe("hardenManualCompactionBoundary", () => { expect(afterTexts.join("\n")).not.toContain("detailed new answer"); }); + it("keeps the upstream recent tail when requested", async () => { + const dir = await makeTmpDir(); + const session = SessionManager.create(dir, dir); + + session.appendMessage({ role: "user", content: "old question", timestamp: 1 }); + session.appendMessage(createAssistantTextMessage("old answer", 2)); + const keepId = session.getBranch().at(-1)?.id; + expect(keepId).toBeTruthy(); + const latestCompactionId = session.appendCompaction("fresh summary", keepId!, 200); + const sessionFile = session.getSessionFile(); + expect(sessionFile).toBeTruthy(); + + const hardened = await hardenManualCompactionBoundary({ + sessionFile: sessionFile!, + preserveRecentTail: true, + }); + expect(hardened.applied).toBe(false); + expect(hardened.firstKeptEntryId).toBe(keepId); + + const reopened = SessionManager.open(sessionFile!); + const latest = reopened.getLeafEntry(); + expect(latest?.type).toBe("compaction"); + if (!latest || latest.type !== "compaction") { + throw new Error("expected latest leaf to be a compaction entry"); + } + expect(latest.id).toBe(latestCompactionId); + expect(latest.firstKeptEntryId).toBe(keepId); + expect(reopened.buildSessionContext().messages.map((message) => message.role)).toEqual([ + "compactionSummary", + "assistant", + ]); + }); + it("is a no-op when the latest leaf is not a compaction entry", async () => { const dir = await makeTmpDir(); const session = SessionManager.create(dir, dir); diff --git a/src/agents/pi-embedded-runner/manual-compaction-boundary.ts b/src/agents/pi-embedded-runner/manual-compaction-boundary.ts index 2fc8ef5f1eb..3c0e26c14f7 100644 --- a/src/agents/pi-embedded-runner/manual-compaction-boundary.ts +++ b/src/agents/pi-embedded-runner/manual-compaction-boundary.ts @@ -40,6 +40,7 @@ function replaceLatestCompactionBoundary(params: { export async function hardenManualCompactionBoundary(params: { sessionFile: string; + preserveRecentTail?: boolean; }): Promise { const sessionManager = SessionManager.open(params.sessionFile) as Partial; if ( @@ -68,6 +69,19 @@ export async function hardenManualCompactionBoundary(params: { }; } + if (params.preserveRecentTail) { + const sessionContext = sessionManager.buildSessionContext(); + return { + applied: false, + firstKeptEntryId: leaf.firstKeptEntryId, + leafId: + typeof sessionManager.getLeafId === "function" + ? (sessionManager.getLeafId() ?? undefined) + : undefined, + messages: sessionContext.messages, + }; + } + if (leaf.firstKeptEntryId === leaf.id) { const sessionContext = sessionManager.buildSessionContext(); return { diff --git a/src/agents/pi-hooks/compaction-safeguard-quality.ts b/src/agents/pi-hooks/compaction-safeguard-quality.ts index 9728503be10..6c6ab429214 100644 --- a/src/agents/pi-hooks/compaction-safeguard-quality.ts +++ b/src/agents/pi-hooks/compaction-safeguard-quality.ts @@ -60,6 +60,7 @@ export function buildCompactionStructureInstructions( ...REQUIRED_SUMMARY_SECTIONS, identifierSectionInstruction, "Do not omit unresolved asks from the user.", + "When prior compaction summaries are present, re-distill them with new messages and remove stale duplicate detail.", ].join("\n"); const custom = customInstructions?.trim(); if (!custom) { diff --git a/src/agents/pi-hooks/compaction-safeguard.test.ts b/src/agents/pi-hooks/compaction-safeguard.test.ts index 1f65bd3ca07..089f915d1e5 100644 --- a/src/agents/pi-hooks/compaction-safeguard.test.ts +++ b/src/agents/pi-hooks/compaction-safeguard.test.ts @@ -38,6 +38,7 @@ const { formatPreservedTurnsSection, buildCompactionStructureInstructions, buildStructuredFallbackSummary, + prependPreviousSummaryForRedistill, appendSummarySection, resolveRecentTurnsPreserve, resolveQualityGuardMaxRetries, @@ -1198,6 +1199,20 @@ describe("compaction-safeguard recent-turn preservation", () => { expect(buildStructuredFallbackSummary(structured)).toBe(structured); }); + it("converts previous summaries into redistill input instead of update-prompt state", () => { + const messages: AgentMessage[] = [{ role: "user", content: "new context", timestamp: 1 }]; + const redistillMessages = prependPreviousSummaryForRedistill({ + messages, + previousSummary: "## Goal\nold duplicate summary", + }); + + expect(redistillMessages).toHaveLength(2); + expect(redistillMessages[0]?.role).toBe("user"); + expect(JSON.stringify(redistillMessages[0])).toContain(""); + expect(JSON.stringify(redistillMessages[0])).toContain("Prune stale, duplicate"); + expect(redistillMessages[1]).toBe(messages[0]); + }); + it("restructures summaries with near-match headings instead of reusing them", () => { const nearMatch = [ "## Decisions", @@ -1685,7 +1700,72 @@ describe("compaction-safeguard recent-turn preservation", () => { expect(summary).toContain("legacy summary without headings"); }); + it("re-distills prior summaries on the LLM path instead of preserving them verbatim", async () => { + mockSummarizeInStages.mockReset(); + mockSummarizeInStages.mockResolvedValue( + [ + "## Decisions", + "Condensed prior context with latest status.", + "## Open TODOs", + "None.", + "## Constraints/Rules", + "Preserve identifiers.", + "## Pending user asks", + "latest ask status", + "## Exact identifiers", + "None.", + ].join("\n"), + ); + + const sessionManager = stubSessionManager(); + const model = createAnthropicModelFixture(); + setCompactionSafeguardRuntime(sessionManager, { + model, + recentTurnsPreserve: 0, + qualityGuardEnabled: true, + qualityGuardMaxRetries: 1, + }); + + const compactionHandler = createCompactionHandler(); + const getApiKeyMock = vi.fn().mockResolvedValue("test-key"); + const mockContext = createCompactionContext({ + sessionManager, + getApiKeyMock, + }); + const event = { + preparation: { + messagesToSummarize: [{ role: "user", content: "latest ask status", timestamp: 1 }], + turnPrefixMessages: [], + firstKeptEntryId: "entry-1", + tokensBefore: 1_500, + fileOps: { + read: [], + edited: [], + written: [], + }, + settings: { reserveTokens: 4_000 }, + previousSummary: "## Goal\nOld duplicated section that should be re-distilled.", + isSplitTurn: false, + }, + customInstructions: "", + signal: new AbortController().signal, + }; + + const result = (await compactionHandler(event, mockContext)) as { + cancel?: boolean; + compaction?: { summary?: string }; + }; + + expect(result.cancel).not.toBe(true); + expect(mockSummarizeInStages).toHaveBeenCalledTimes(1); + const call = mockSummarizeInStages.mock.calls[0]?.[0]; + expect(call?.previousSummary).toBeUndefined(); + expect(JSON.stringify(call?.messages[0])).toContain(""); + expect(JSON.stringify(call?.messages[0])).toContain("Old duplicated section"); + }); + it("passes compaction instructions to providers and preserves suffix context", async () => { + mockSummarizeInStages.mockReset(); const providerSummarize = vi.fn().mockResolvedValue("provider summary body"); registerCompactionProvider({ id: "test-provider", diff --git a/src/agents/pi-hooks/compaction-safeguard.ts b/src/agents/pi-hooks/compaction-safeguard.ts index bbeac04f8e8..996dc9a0d37 100644 --- a/src/agents/pi-hooks/compaction-safeguard.ts +++ b/src/agents/pi-hooks/compaction-safeguard.ts @@ -67,10 +67,37 @@ const DEFAULT_QUALITY_GUARD_MAX_RETRIES = 1; const MAX_RECENT_TURNS_PRESERVE = 12; const MAX_QUALITY_GUARD_MAX_RETRIES = 3; const MAX_RECENT_TURN_TEXT_CHARS = 600; +const PREVIOUS_SUMMARY_REDISTILL_PREFIX = + "Previous compaction summary to re-distill with the current conversation. " + + "Prune stale, duplicate, or superseded details instead of preserving it verbatim."; const compactionSafeguardDeps = { summarizeInStages, }; +function buildPreviousSummaryMessage(previousSummary: string): AgentMessage { + return { + role: "user", + content: [ + { + type: "text", + text: `\n${PREVIOUS_SUMMARY_REDISTILL_PREFIX}\n\n${previousSummary.trim()}\n`, + }, + ], + timestamp: 0, + } as AgentMessage; +} + +function prependPreviousSummaryForRedistill(params: { + messages: AgentMessage[]; + previousSummary?: string; +}): AgentMessage[] { + const previousSummary = params.previousSummary?.trim(); + if (!previousSummary) { + return params.messages; + } + return [buildPreviousSummaryMessage(previousSummary), ...params.messages]; +} + /** * Attempt provider-based summarization. Returns the summary string on success, * or `undefined` when the caller should fall back to built-in LLM summarization. @@ -125,8 +152,12 @@ async function summarizeViaLLM(params: { summarizationInstructions?: Parameters[0]["summarizationInstructions"]; previousSummary?: string; }): Promise { - return compactionSafeguardDeps.summarizeInStages({ + const messages = prependPreviousSummaryForRedistill({ messages: params.messages, + previousSummary: params.previousSummary, + }); + return compactionSafeguardDeps.summarizeInStages({ + messages, model: params.model, apiKey: params.apiKey, headers: params.headers, @@ -136,7 +167,7 @@ async function summarizeViaLLM(params: { contextWindow: params.contextWindow, customInstructions: params.customInstructions, summarizationInstructions: params.summarizationInstructions, - previousSummary: params.previousSummary, + previousSummary: undefined, }); } @@ -1166,6 +1197,7 @@ export const __testing = { formatSplitTurnContextSection, buildCompactionStructureInstructions, buildStructuredFallbackSummary, + prependPreviousSummaryForRedistill, appendSummarySection, resolveRecentTurnsPreserve, resolveQualityGuardMaxRetries, diff --git a/src/config/schema.base.generated.ts b/src/config/schema.base.generated.ts index d387b2597cc..6834bfb20d2 100644 --- a/src/config/schema.base.generated.ts +++ b/src/config/schema.base.generated.ts @@ -4722,7 +4722,7 @@ export const GENERATED_BASE_CONFIG_SCHEMA: BaseConfigSchemaResponse = { type: "boolean", title: "Compaction Quality Guard Enabled", description: - "Enables summary quality audits and regeneration retries for safeguard compaction. Default: false, so safeguard mode alone does not turn on retry behavior.", + "Enables summary quality audits and regeneration retries for safeguard compaction. Default: true in safeguard mode.", }, maxRetries: { type: "integer", @@ -4736,7 +4736,7 @@ export const GENERATED_BASE_CONFIG_SCHEMA: BaseConfigSchemaResponse = { additionalProperties: false, title: "Compaction Quality Guard", description: - "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.", + "Quality-audit retry settings for safeguard compaction summaries. Safeguard mode enables this by default; set enabled: false to skip summary audits and regeneration.", }, postIndexSync: { type: "string", @@ -26066,12 +26066,12 @@ export const GENERATED_BASE_CONFIG_SCHEMA: BaseConfigSchemaResponse = { }, "agents.defaults.compaction.qualityGuard": { label: "Compaction Quality Guard", - help: "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.", + help: "Quality-audit retry settings for safeguard compaction summaries. Safeguard mode enables this by default; set enabled: false to skip summary audits and regeneration.", tags: ["advanced"], }, "agents.defaults.compaction.qualityGuard.enabled": { label: "Compaction Quality Guard Enabled", - help: "Enables summary quality audits and regeneration retries for safeguard compaction. Default: false, so safeguard mode alone does not turn on retry behavior.", + help: "Enables summary quality audits and regeneration retries for safeguard compaction. Default: true in safeguard mode.", tags: ["advanced"], }, "agents.defaults.compaction.qualityGuard.maxRetries": { diff --git a/src/config/schema.help.ts b/src/config/schema.help.ts index 465f3400df4..0b4ecdd18d0 100644 --- a/src/config/schema.help.ts +++ b/src/config/schema.help.ts @@ -1236,9 +1236,9 @@ export const FIELD_HELP: Record = { "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.", + "Quality-audit retry settings for safeguard compaction summaries. Safeguard mode enables this by default; set enabled: false to skip summary audits and regeneration.", "agents.defaults.compaction.qualityGuard.enabled": - "Enables summary quality audits and regeneration retries for safeguard compaction. Default: false, so safeguard mode alone does not turn on retry behavior.", + "Enables summary quality audits and regeneration retries for safeguard compaction. Default: true in safeguard mode.", "agents.defaults.compaction.qualityGuard.maxRetries": "Maximum number of regeneration retries after a failed safeguard summary quality audit. Use small values to bound extra latency and token cost.", "agents.defaults.compaction.postIndexSync":