惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

宝玉的分享
宝玉的分享
Engineering at Meta
Engineering at Meta
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
博客园 - 聂微东
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Last Week in AI
Last Week in AI
酷 壳 – CoolShell
酷 壳 – CoolShell
博客园 - 三生石上(FineUI控件)
T
Tailwind CSS Blog
Apple Machine Learning Research
Apple Machine Learning Research
Hugging Face - Blog
Hugging Face - Blog
爱范儿
爱范儿
博客园 - 司徒正美
人人都是产品经理
人人都是产品经理
Jina AI
Jina AI
博客园 - 叶小钗
雷峰网
雷峰网
罗磊的独立博客
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - Franky
WordPress大学
WordPress大学
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
阮一峰的网络日志
阮一峰的网络日志
量子位

Recent Commits to openclaw:main

test: merge chat side-result checks · openclaw/openclaw@ddd2c2a test: merge cron history checks · openclaw/openclaw@f7eb746 test: merge responsive navigation shell checks · openclaw/openclaw@c2e4b47 docs(changelog): add codex oauth fixes · openclaw/openclaw@628e6cd test: merge navigation routing cases · openclaw/openclaw@5d8cecb Tests: mock channel registry bundled fallback · openclaw/openclaw@2b08233 Secrets: avoid broad web search discovery for single plugin config · openclaw/openclaw@a464f59 test: merge config view browser checks · openclaw/openclaw@20cf511 fix(status): align oauth health with runtime · openclaw/openclaw@eed7116 feat: add macOS screen snapshots for monitor preview (#67954) thanks … · openclaw/openclaw@f377db1 fix: report shared auth scopes in hello-ok (#67810) thanks @BunsDev · openclaw/openclaw@0b6c39b Auto-reply: avoid eager bundled route fallback · openclaw/openclaw@3ea1bf4 Tests: narrow session binding contract setup · openclaw/openclaw@54e4e16 fix(macOS): enable undo/redo in webchat composer text input (#34962) · openclaw/openclaw@00951dc Tests: speed up channel setup promotion · openclaw/openclaw@82b529a Docs: refresh agent instructions · openclaw/openclaw@5775fe2 fix(auth): serialize OAuth refresh across agents to fix #26322 (#67876) · openclaw/openclaw@8e79080 test: allow ollama public surface boundary test · openclaw/openclaw@7d4f1a6 Docs: add test performance guardrails · openclaw/openclaw@89706d3 Tests: restore context-engine usage proof · openclaw/openclaw@e4c4f95 Tests: slim context engine runtime coverage · openclaw/openclaw@74c198f ci: retry failed custom checkouts · openclaw/openclaw@0ee5baf test: trim duplicate provider auth onboarding cases · openclaw/openclaw@1ffc02e matrix: fix sessions_spawn --thread subagent session spawning (#67643) · openclaw/openclaw@1ce2596 test: reduce auth choice fixture churn · openclaw/openclaw@857b9cd test: mock health status config boundaries · openclaw/openclaw@9d5ab4a test: mock onboard config io boundary · openclaw/openclaw@299694d test: mock legacy state plugin boundaries · openclaw/openclaw@2713089 test: mock channel install boundaries · openclaw/openclaw@b945248 test: mock doctor preview channel boundaries · openclaw/openclaw@b1a3ad4
fix(agents): keep live tool-result prompts cache-stable ·...
vincentkoc · 2026-06-21 · via Recent Commits to openclaw:main
Original file line numberDiff line numberDiff line change

@@ -249,9 +249,10 @@ Shared defaults for bounded runtime context surfaces.

249249

- `toolResultMaxChars`: advanced live tool-result ceiling used for persisted

250250

results and overflow recovery. Leave unset for the model-context auto cap:

251251

`16000` chars below 100K tokens, `32000` chars at 100K+ tokens, and `64000`

252-

chars at 200K+ tokens. The effective cap is still limited to about 30% of the

253-

model context window. `openclaw doctor --deep` prints the effective cap, and

254-

doctor warns only when an explicit override is stale or has no effect.

252+

chars at 200K+ tokens. Explicit values up to `1000000` are accepted for

253+

long-context models, but the effective cap is still limited to about 30% of

254+

the model context window. `openclaw doctor --deep` prints the effective cap,

255+

and doctor warns only when an explicit override is stale or has no effect.

255256

- `postCompactionMaxChars`: AGENTS.md excerpt cap used during post-compaction

256257

refresh injection.

257258
Original file line numberDiff line numberDiff line change

@@ -54,7 +54,7 @@ for bounded runtime excerpts and injected runtime-owned blocks. They are

5454

separate from bootstrap limits, startup-context limits, and skills prompt

5555

limits.

5656
57-

`toolResultMaxChars` is an advanced ceiling. When it is unset, OpenClaw chooses

57+

`toolResultMaxChars` is an advanced ceiling (up to `1000000` characters). When it is unset, OpenClaw chooses

5858

the live tool-result cap from the effective model context window: `16000` chars

5959

below 100K tokens, `32000` chars at 100K+ tokens, and `64000` chars at 200K+

6060

tokens, still bounded by the runtime context-share guard.

Original file line numberDiff line numberDiff line change

@@ -2840,7 +2840,7 @@ describe("runEmbeddedAttempt tool-result guard budget wiring", () => {

28402840

).toBe(1_000_000);

28412841

});

28422842
2843-

it("bounds aggregate tool-result prompt history without rewriting append results", async () => {

2843+

it("preserves the cacheable prefix while bounding current prompt results", async () => {

28442844

const toolText = "process output ".repeat(70);

28452845

const sessionMessages: AgentMessage[] = [{ role: "user", content: "seed", timestamp: 1 }];

28462846

for (let index = 0; index < 8; index += 1) {

@@ -2880,7 +2880,7 @@ describe("runEmbeddedAttempt tool-result guard budget wiring", () => {

28802880

agents: {

28812881

defaults: {

28822882

contextLimits: {

2883-

toolResultMaxChars: 1_000,

2883+

toolResultMaxChars: 2_000,

28842884

},

28852885

},

28862886

list: [{ id: "main" }],

@@ -2902,6 +2902,18 @@ describe("runEmbeddedAttempt tool-result guard budget wiring", () => {

29022902

};

29032903

};

29042904

session.prompt = async (_prompt, options) => {

2905+

for (let index = 0; index < 8; index += 1) {

2906+

session.messages.push({

2907+

role: "toolResult",

2908+

toolCallId: `current_call_${index}`,

2909+

toolName: "process",

2910+

content: [

2911+

{ type: "text", text: `current ${index}: ${"current output ".repeat(300)}` },

2912+

],

2913+

isError: false,

2914+

timestamp: 100 + index,

2915+

} as AgentMessage);

2916+

}

29052917

promptHandlerMessages = session.messages.map((message) => message as AgentMessage);

29062918

options?.preflightResult?.(true);

29072919

await session.agent.streamFn?.({} as never, { messages: session.messages } as never, {});

@@ -2913,8 +2925,14 @@ describe("runEmbeddedAttempt tool-result guard budget wiring", () => {

29132925
29142926

expect(sumToolResultTextChars(sessionMessages)).toBeGreaterThan(4_000);

29152927

expect(sumToolResultTextChars(promptHandlerMessages)).toBeGreaterThan(4_000);

2916-

expect(sumToolResultTextChars(submittedMessages)).toBeLessThanOrEqual(4_000);

2917-

expect(JSON.stringify(submittedMessages)).toContain("truncated");

2928+

const submittedCurrentPromptMessages = submittedMessages.slice(sessionMessages.length);

2929+

expect(submittedMessages.slice(0, sessionMessages.length)).toEqual(sessionMessages);

2930+

expect(

2931+

submittedCurrentPromptMessages

2932+

.filter((message) => message.role === "toolResult")

2933+

.every((message) => sumToolResultTextChars([message]) <= 2_000),

2934+

).toBe(true);

2935+

expect(JSON.stringify(submittedCurrentPromptMessages)).toContain("truncated");

29182936

expect(afterTurn).toHaveBeenCalledTimes(1);

29192937

expect(sumToolResultTextChars(afterTurnMessages)).toBeGreaterThan(4_000);

29202938

expect(JSON.stringify(afterTurnMessages)).not.toContain("truncated");

Original file line numberDiff line numberDiff line change

@@ -542,8 +542,6 @@ export {

542542

};

543543
544544

const MAX_BTW_SNAPSHOT_MESSAGES = 100;

545-

const PROMPT_TOOL_RESULT_AGGREGATE_CAP_MULTIPLIER = 4;

546-
547545

function pluginMetadataSnapshotCoversProvider(

548546

snapshot: PluginMetadataSnapshot | undefined,

549547

provider: string,

@@ -4176,17 +4174,14 @@ export async function runEmbeddedAttempt(

41764174

activeSession.messages,

41774175

contextTokenBudget,

41784176

promptToolResultMaxChars,

4179-

promptToolResultMaxChars * PROMPT_TOOL_RESULT_AGGREGATE_CAP_MULTIPLIER,

4177+

null,

41804178

);

41814179

if (promptToolResultTruncation.truncatedCount > 0) {

41824180

promptHistoryMessages = promptToolResultTruncation.messages;

41834181

log.info(

41844182

`[tool-result-truncation] Truncated ${promptToolResultTruncation.truncatedCount} ` +

41854183

`tool result(s) for prompt history ` +

4186-

`(maxChars=${promptToolResultMaxChars} ` +

4187-

`aggregateBudgetChars=${

4188-

promptToolResultMaxChars * PROMPT_TOOL_RESULT_AGGREGATE_CAP_MULTIPLIER

4189-

}) ` +

4184+

`(maxChars=${promptToolResultMaxChars}) ` +

41904185

`sessionKey=${params.sessionKey ?? params.sessionId ?? "unknown"}`,

41914186

);

41924187

}

@@ -4717,7 +4712,7 @@ export async function runEmbeddedAttempt(

47174712

messages,

47184713

contextTokenBudget,

47194714

promptToolResultMaxChars,

4720-

promptToolResultMaxChars * PROMPT_TOOL_RESULT_AGGREGATE_CAP_MULTIPLIER,

4715+

null,

47214716

);

47224717

return providerPromptHistoryTruncation.truncatedCount > 0

47234718

? providerPromptHistoryTruncation.messages

Original file line numberDiff line numberDiff line change

@@ -437,6 +437,34 @@ describe("truncateOversizedToolResultsInMessages", () => {

437437

medium.length * 3,

438438

);

439439

});

440+
441+

it("keeps prompt projections byte-stable as history grows", () => {

442+

const prefix = [

443+

makeToolResult("p".repeat(15_000), "prefix_1"),

444+

makeToolResult("q".repeat(15_000), "prefix_2"),

445+

];

446+

const suffix = [

447+

makeToolResult("x".repeat(15_000), "current_1"),

448+

makeToolResult("y".repeat(15_000), "current_2"),

449+

];

450+

const messages = [...prefix, ...suffix];

451+
452+

const first = truncateOversizedToolResultsInMessages(messages, 128_000, 12_000, null);

453+

const second = truncateOversizedToolResultsInMessages(

454+

[...messages, makeToolResult("z".repeat(15_000), "current_3")],

455+

128_000,

456+

12_000,

457+

null,

458+

);

459+
460+

expect(first.truncatedCount).toBe(4);

461+

expect(second.truncatedCount).toBe(5);

462+

expect(second.messages.slice(0, messages.length)).toEqual(first.messages);

463+

expect(second.messages.every((message) => getToolResultTextLength(message) <= 12_000)).toBe(

464+

true,

465+

);

466+

expect(messages).toEqual([...prefix, ...suffix]);

467+

});

440468

});

441469
442470

describe("truncateOversizedToolResultsInSession", () => {

Original file line numberDiff line numberDiff line change

@@ -338,17 +338,22 @@ export function truncateOversizedToolResultsInMessages(

338338

messages: AgentMessage[],

339339

contextWindowTokens: number,

340340

maxCharsOverride?: number,

341-

aggregateMaxCharsOverride?: number,

341+

aggregateMaxCharsOverride?: number | null,

342342

): { messages: AgentMessage[]; truncatedCount: number } {

343343

const maxChars = Math.max(

344344

1,

345345

maxCharsOverride ?? calculateMaxToolResultChars(contextWindowTokens),

346346

);

347-

const aggregateBudgetChars = calculateRecoveryAggregateToolResultChars(

348-

contextWindowTokens,

349-

maxChars,

350-

aggregateMaxCharsOverride,

351-

);

347+

// Live prompt assembly disables aggregate rewriting so unchanged history stays byte-stable

348+

// for provider prefix caches; recovery and persisted-session callers keep the aggregate guard.

349+

const aggregateBudgetChars =

350+

aggregateMaxCharsOverride === null

351+

? Number.POSITIVE_INFINITY

352+

: calculateRecoveryAggregateToolResultChars(

353+

contextWindowTokens,

354+

maxChars,

355+

aggregateMaxCharsOverride,

356+

);

352357

const branch = messages.map((message, index) => ({

353358

id: `message-${index}`,

354359

type: "message",

Original file line numberDiff line numberDiff line change

@@ -147,6 +147,20 @@ describe("config schema regressions", () => {

147147

expect(res.ok).toBe(false);

148148

});

149149
150+

it("accepts 1M-character tool result caps for long-context agents", () => {

151+

const res = validateConfigObject({

152+

agents: {

153+

defaults: {

154+

contextLimits: {

155+

toolResultMaxChars: 1_000_000,

156+

},

157+

},

158+

},

159+

});

160+
161+

expect(res.ok).toBe(true);

162+

});

163+
150164

it("accepts agents.defaults and agents.list contextLimits overrides", () => {

151165

const res = validateConfigObject({

152166

agents: {

Original file line numberDiff line numberDiff line change

@@ -297,7 +297,7 @@ export const AgentContextLimitsSchema = z

297297

.object({

298298

memoryGetMaxChars: z.number().int().min(1).max(250_000).optional(),

299299

memoryGetDefaultLines: z.number().int().min(1).max(5_000).optional(),

300-

toolResultMaxChars: z.number().int().min(1).max(250_000).optional(),

300+

toolResultMaxChars: z.number().int().min(1).max(1_000_000).optional(),

301301

postCompactionMaxChars: z.number().int().min(1).max(50_000).optional(),

302302

})

303303

.strict()