Config: expose Pi compaction tuning values (openclaw#21568) thanks @Takhoffman
Verified: - pnpm install --frozen-lockfile - pnpm build - pnpm check - pnpm test:macmini Co-authored-by: Takhoffman <781889+Takhoffman@users.noreply.github.com>
This commit is contained in:
@@ -11,6 +11,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Fixes
|
||||
|
||||
- Config/Agents: expose Pi compaction tuning values `agents.defaults.compaction.reserveTokens` and `agents.defaults.compaction.keepRecentTokens` in config schema/types and apply them in embedded Pi runner settings overrides with floor enforcement via `reserveTokensFloor`. (#21568) Thanks @Takhoffman.
|
||||
- Auto-reply/WebChat: avoid defaulting inbound runtime channel labels to unrelated providers (for example `whatsapp`) for webchat sessions so channel-specific formatting guidance stays accurate. (#21534) Thanks @lbo728.
|
||||
- Status: include persisted `cacheRead`/`cacheWrite` in session summaries so compact `/status` output consistently shows cache hit percentages from real session data.
|
||||
- Heartbeat/Cron: restore interval heartbeat behavior so missing `HEARTBEAT.md` no longer suppresses runs (only effectively empty files skip), preserving prompt-driven and tagged-cron execution paths.
|
||||
|
||||
@@ -37,10 +37,7 @@ import {
|
||||
validateAnthropicTurns,
|
||||
validateGeminiTurns,
|
||||
} from "../pi-embedded-helpers.js";
|
||||
import {
|
||||
ensurePiCompactionReserveTokens,
|
||||
resolveCompactionReserveTokensFloor,
|
||||
} from "../pi-settings.js";
|
||||
import { applyPiCompactionSettingsFromConfig } from "../pi-settings.js";
|
||||
import { createOpenClawCodingTools } from "../pi-tools.js";
|
||||
import { resolveSandboxContext } from "../sandbox.js";
|
||||
import { repairSessionFileIfNeeded } from "../session-file-repair.js";
|
||||
@@ -532,9 +529,9 @@ export async function compactEmbeddedPiSessionDirect(
|
||||
});
|
||||
trackSessionManagerAccess(params.sessionFile);
|
||||
const settingsManager = SettingsManager.create(effectiveWorkspace, agentDir);
|
||||
ensurePiCompactionReserveTokens({
|
||||
applyPiCompactionSettingsFromConfig({
|
||||
settingsManager,
|
||||
minReserveTokens: resolveCompactionReserveTokensFloor(params.config),
|
||||
cfg: params.config,
|
||||
});
|
||||
// Call for side effects (sets compaction/pruning runtime state)
|
||||
buildEmbeddedExtensionPaths({
|
||||
|
||||
@@ -45,10 +45,7 @@ import {
|
||||
validateGeminiTurns,
|
||||
} from "../../pi-embedded-helpers.js";
|
||||
import { subscribeEmbeddedPiSession } from "../../pi-embedded-subscribe.js";
|
||||
import {
|
||||
ensurePiCompactionReserveTokens,
|
||||
resolveCompactionReserveTokensFloor,
|
||||
} from "../../pi-settings.js";
|
||||
import { applyPiCompactionSettingsFromConfig } from "../../pi-settings.js";
|
||||
import { toClientToolDefinitions } from "../../pi-tool-definition-adapter.js";
|
||||
import { createOpenClawCodingTools, resolveToolLoopDetectionConfig } from "../../pi-tools.js";
|
||||
import { resolveSandboxContext } from "../../sandbox.js";
|
||||
@@ -531,9 +528,9 @@ export async function runEmbeddedAttempt(
|
||||
});
|
||||
|
||||
const settingsManager = SettingsManager.create(effectiveWorkspace, agentDir);
|
||||
ensurePiCompactionReserveTokens({
|
||||
applyPiCompactionSettingsFromConfig({
|
||||
settingsManager,
|
||||
minReserveTokens: resolveCompactionReserveTokensFloor(params.config),
|
||||
cfg: params.config,
|
||||
});
|
||||
|
||||
// Call for side effects (sets compaction/pruning runtime state)
|
||||
|
||||
@@ -1,37 +1,123 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
applyPiCompactionSettingsFromConfig,
|
||||
DEFAULT_PI_COMPACTION_RESERVE_TOKENS_FLOOR,
|
||||
ensurePiCompactionReserveTokens,
|
||||
resolveCompactionReserveTokensFloor,
|
||||
} from "./pi-settings.js";
|
||||
|
||||
describe("ensurePiCompactionReserveTokens", () => {
|
||||
describe("applyPiCompactionSettingsFromConfig", () => {
|
||||
it("bumps reserveTokens when below floor", () => {
|
||||
const settingsManager = {
|
||||
getCompactionReserveTokens: () => 16_384,
|
||||
getCompactionKeepRecentTokens: () => 20_000,
|
||||
applyOverrides: vi.fn(),
|
||||
};
|
||||
|
||||
const result = ensurePiCompactionReserveTokens({ settingsManager });
|
||||
const result = applyPiCompactionSettingsFromConfig({ settingsManager });
|
||||
|
||||
expect(result).toEqual({
|
||||
didOverride: true,
|
||||
reserveTokens: DEFAULT_PI_COMPACTION_RESERVE_TOKENS_FLOOR,
|
||||
});
|
||||
expect(result.didOverride).toBe(true);
|
||||
expect(result.compaction.reserveTokens).toBe(DEFAULT_PI_COMPACTION_RESERVE_TOKENS_FLOOR);
|
||||
expect(settingsManager.applyOverrides).toHaveBeenCalledWith({
|
||||
compaction: { reserveTokens: DEFAULT_PI_COMPACTION_RESERVE_TOKENS_FLOOR },
|
||||
});
|
||||
});
|
||||
|
||||
it("does not override when already above floor", () => {
|
||||
it("does not override when already above floor and not in safeguard mode", () => {
|
||||
const settingsManager = {
|
||||
getCompactionReserveTokens: () => 32_000,
|
||||
getCompactionKeepRecentTokens: () => 20_000,
|
||||
applyOverrides: vi.fn(),
|
||||
};
|
||||
|
||||
const result = ensurePiCompactionReserveTokens({ settingsManager });
|
||||
const result = applyPiCompactionSettingsFromConfig({
|
||||
settingsManager,
|
||||
cfg: { agents: { defaults: { compaction: { mode: "default" } } } },
|
||||
});
|
||||
|
||||
expect(result).toEqual({ didOverride: false, reserveTokens: 32_000 });
|
||||
expect(result.didOverride).toBe(false);
|
||||
expect(result.compaction.reserveTokens).toBe(32_000);
|
||||
expect(settingsManager.applyOverrides).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("applies explicit reserveTokens but still enforces floor", () => {
|
||||
const settingsManager = {
|
||||
getCompactionReserveTokens: () => 10_000,
|
||||
getCompactionKeepRecentTokens: () => 20_000,
|
||||
applyOverrides: vi.fn(),
|
||||
};
|
||||
|
||||
const result = applyPiCompactionSettingsFromConfig({
|
||||
settingsManager,
|
||||
cfg: {
|
||||
agents: {
|
||||
defaults: {
|
||||
compaction: { reserveTokens: 12_000, reserveTokensFloor: 20_000 },
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.compaction.reserveTokens).toBe(20_000);
|
||||
expect(settingsManager.applyOverrides).toHaveBeenCalledWith({
|
||||
compaction: { reserveTokens: 20_000 },
|
||||
});
|
||||
});
|
||||
|
||||
it("applies keepRecentTokens when explicitly configured", () => {
|
||||
const settingsManager = {
|
||||
getCompactionReserveTokens: () => 20_000,
|
||||
getCompactionKeepRecentTokens: () => 20_000,
|
||||
applyOverrides: vi.fn(),
|
||||
};
|
||||
|
||||
const result = applyPiCompactionSettingsFromConfig({
|
||||
settingsManager,
|
||||
cfg: {
|
||||
agents: {
|
||||
defaults: {
|
||||
compaction: {
|
||||
keepRecentTokens: 15_000,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.compaction.keepRecentTokens).toBe(15_000);
|
||||
expect(settingsManager.applyOverrides).toHaveBeenCalledWith({
|
||||
compaction: { keepRecentTokens: 15_000 },
|
||||
});
|
||||
});
|
||||
|
||||
it("preserves current keepRecentTokens when safeguard mode leaves it unset", () => {
|
||||
const settingsManager = {
|
||||
getCompactionReserveTokens: () => 25_000,
|
||||
getCompactionKeepRecentTokens: () => 20_000,
|
||||
applyOverrides: vi.fn(),
|
||||
};
|
||||
|
||||
const result = applyPiCompactionSettingsFromConfig({
|
||||
settingsManager,
|
||||
cfg: { agents: { defaults: { compaction: { mode: "safeguard" } } } },
|
||||
});
|
||||
|
||||
expect(result.compaction.keepRecentTokens).toBe(20_000);
|
||||
expect(settingsManager.applyOverrides).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("treats keepRecentTokens=0 as invalid and keeps the current setting", () => {
|
||||
const settingsManager = {
|
||||
getCompactionReserveTokens: () => 25_000,
|
||||
getCompactionKeepRecentTokens: () => 20_000,
|
||||
applyOverrides: vi.fn(),
|
||||
};
|
||||
|
||||
const result = applyPiCompactionSettingsFromConfig({
|
||||
settingsManager,
|
||||
cfg: { agents: { defaults: { compaction: { mode: "safeguard", keepRecentTokens: 0 } } } },
|
||||
});
|
||||
|
||||
expect(result.compaction.keepRecentTokens).toBe(20_000);
|
||||
expect(settingsManager.applyOverrides).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,7 +4,13 @@ export const DEFAULT_PI_COMPACTION_RESERVE_TOKENS_FLOOR = 20_000;
|
||||
|
||||
type PiSettingsManagerLike = {
|
||||
getCompactionReserveTokens: () => number;
|
||||
applyOverrides: (overrides: { compaction: { reserveTokens: number } }) => void;
|
||||
getCompactionKeepRecentTokens: () => number;
|
||||
applyOverrides: (overrides: {
|
||||
compaction: {
|
||||
reserveTokens?: number;
|
||||
keepRecentTokens?: number;
|
||||
};
|
||||
}) => void;
|
||||
};
|
||||
|
||||
export function ensurePiCompactionReserveTokens(params: {
|
||||
@@ -32,3 +38,60 @@ export function resolveCompactionReserveTokensFloor(cfg?: OpenClawConfig): numbe
|
||||
}
|
||||
return DEFAULT_PI_COMPACTION_RESERVE_TOKENS_FLOOR;
|
||||
}
|
||||
|
||||
function toNonNegativeInt(value: unknown): number | undefined {
|
||||
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
|
||||
return undefined;
|
||||
}
|
||||
return Math.floor(value);
|
||||
}
|
||||
|
||||
function toPositiveInt(value: unknown): number | undefined {
|
||||
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
|
||||
return undefined;
|
||||
}
|
||||
return Math.floor(value);
|
||||
}
|
||||
|
||||
export function applyPiCompactionSettingsFromConfig(params: {
|
||||
settingsManager: PiSettingsManagerLike;
|
||||
cfg?: OpenClawConfig;
|
||||
}): {
|
||||
didOverride: boolean;
|
||||
compaction: { reserveTokens: number; keepRecentTokens: number };
|
||||
} {
|
||||
const currentReserveTokens = params.settingsManager.getCompactionReserveTokens();
|
||||
const currentKeepRecentTokens = params.settingsManager.getCompactionKeepRecentTokens();
|
||||
const compactionCfg = params.cfg?.agents?.defaults?.compaction;
|
||||
|
||||
const configuredReserveTokens = toNonNegativeInt(compactionCfg?.reserveTokens);
|
||||
const configuredKeepRecentTokens = toPositiveInt(compactionCfg?.keepRecentTokens);
|
||||
const reserveTokensFloor = resolveCompactionReserveTokensFloor(params.cfg);
|
||||
|
||||
const targetReserveTokens = Math.max(
|
||||
configuredReserveTokens ?? currentReserveTokens,
|
||||
reserveTokensFloor,
|
||||
);
|
||||
const targetKeepRecentTokens = configuredKeepRecentTokens ?? currentKeepRecentTokens;
|
||||
|
||||
const overrides: { reserveTokens?: number; keepRecentTokens?: number } = {};
|
||||
if (targetReserveTokens !== currentReserveTokens) {
|
||||
overrides.reserveTokens = targetReserveTokens;
|
||||
}
|
||||
if (targetKeepRecentTokens !== currentKeepRecentTokens) {
|
||||
overrides.keepRecentTokens = targetKeepRecentTokens;
|
||||
}
|
||||
|
||||
const didOverride = Object.keys(overrides).length > 0;
|
||||
if (didOverride) {
|
||||
params.settingsManager.applyOverrides({ compaction: overrides });
|
||||
}
|
||||
|
||||
return {
|
||||
didOverride,
|
||||
compaction: {
|
||||
reserveTokens: targetReserveTokens,
|
||||
keepRecentTokens: targetKeepRecentTokens,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -38,6 +38,8 @@ describe("config compaction settings", () => {
|
||||
|
||||
expect(cfg.agents?.defaults?.compaction?.reserveTokensFloor).toBe(12_345);
|
||||
expect(cfg.agents?.defaults?.compaction?.mode).toBe("safeguard");
|
||||
expect(cfg.agents?.defaults?.compaction?.reserveTokens).toBeUndefined();
|
||||
expect(cfg.agents?.defaults?.compaction?.keepRecentTokens).toBeUndefined();
|
||||
expect(cfg.agents?.defaults?.compaction?.memoryFlush?.enabled).toBe(false);
|
||||
expect(cfg.agents?.defaults?.compaction?.memoryFlush?.softThresholdTokens).toBe(1234);
|
||||
expect(cfg.agents?.defaults?.compaction?.memoryFlush?.prompt).toBe("Write notes.");
|
||||
@@ -45,6 +47,35 @@ describe("config compaction settings", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("preserves pi compaction override values", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
const configDir = path.join(home, ".openclaw");
|
||||
await fs.mkdir(configDir, { recursive: true });
|
||||
await fs.writeFile(
|
||||
path.join(configDir, "openclaw.json"),
|
||||
JSON.stringify(
|
||||
{
|
||||
agents: {
|
||||
defaults: {
|
||||
compaction: {
|
||||
reserveTokens: 15_000,
|
||||
keepRecentTokens: 12_000,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
"utf-8",
|
||||
);
|
||||
|
||||
const cfg = loadConfig();
|
||||
expect(cfg.agents?.defaults?.compaction?.reserveTokens).toBe(15_000);
|
||||
expect(cfg.agents?.defaults?.compaction?.keepRecentTokens).toBe(12_000);
|
||||
});
|
||||
});
|
||||
|
||||
it("defaults compaction mode to safeguard", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
const configDir = path.join(home, ".openclaw");
|
||||
|
||||
@@ -289,6 +289,10 @@ export type AgentCompactionMode = "default" | "safeguard";
|
||||
export type AgentCompactionConfig = {
|
||||
/** Compaction summarization mode. */
|
||||
mode?: AgentCompactionMode;
|
||||
/** Pi reserve tokens target before floor enforcement. */
|
||||
reserveTokens?: number;
|
||||
/** Pi keepRecentTokens budget used for cut-point selection. */
|
||||
keepRecentTokens?: number;
|
||||
/** Minimum reserve tokens enforced for Pi compaction (0 disables the floor). */
|
||||
reserveTokensFloor?: number;
|
||||
/** Max share of context window for history during safeguard pruning (0.1–0.9, default 0.5). */
|
||||
|
||||
@@ -91,6 +91,8 @@ export const AgentDefaultsSchema = z
|
||||
compaction: z
|
||||
.object({
|
||||
mode: z.union([z.literal("default"), z.literal("safeguard")]).optional(),
|
||||
reserveTokens: z.number().int().nonnegative().optional(),
|
||||
keepRecentTokens: z.number().int().positive().optional(),
|
||||
reserveTokensFloor: z.number().int().nonnegative().optional(),
|
||||
maxHistoryShare: z.number().min(0.1).max(0.9).optional(),
|
||||
memoryFlush: z
|
||||
|
||||
Reference in New Issue
Block a user