@cognipeer/agent-sdk 0.5.4 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -137,12 +137,8 @@ const agent = createSmartAgent({
137
137
  summarization: { summaryTriggerTokens: 8000, summaryMode: "incremental" },
138
138
  context: { policy: "hybrid", lastTurnsToKeep: 8 },
139
139
  toolResponses: {
140
- smallResponseChars: 1200,
141
- smallResponsePolicy: "keep_full",
142
- defaultPolicy: "keep_structured",
143
- largeResponsePolicy: "summarize_archive",
144
- fallbackPolicy: "keep_full",
145
- keepRecentFullCount: 2,
140
+ defaultPolicy: "summarize_archive",
141
+ toolResponseRetentionByTool: { read_skills: "keep_full" },
146
142
  maxToolResponseChars: 4000,
147
143
  maxToolResponseTokens: 1200,
148
144
  },
@@ -158,15 +154,13 @@ const result = await agent.invoke({
158
154
  console.log(result.content);
159
155
  ```
160
156
 
161
- Tool-response retention is size-aware and composable:
157
+ Tool-response retention is lazy and summarizer-driven:
162
158
 
163
- - `smallResponseChars` + `smallResponsePolicy` define how very small payloads are kept.
164
- - `defaultPolicy` handles normal informative outputs.
165
- - `largeResponsePolicy` handles verbose outputs that cross the max char/token thresholds.
166
- - `fallbackPolicy` applies to critical tools and now defaults to `keep_full` so safety-critical payloads are not collapsed accidentally.
167
- - `keepRecentFullCount` pins the last N successful tool responses at full content during context assembly and excludes them from summarization passes.
168
-
169
- Resolution order is: per-tool override, then critical fallback, then large/small classification, then the default policy. Redundant payloads like `ok`, `true`, and `null` are dropped automatically.
159
+ - Tool outputs are stored at full fidelity in `state.toolHistory` and are never reduced at tool-call time.
160
+ - When the summarizer runs (context limits reached), old tool messages are rewritten in place according to `defaultPolicy` (default: `summarize_archive`). The full payload is still recoverable via `get_tool_response` using the execution id embedded in the placeholder.
161
+ - `toolResponseRetentionByTool` lets you opt specific tools out of reduction (e.g. `read_skills: "keep_full"`).
162
+ - `criticalTools` are never reduced. The built-in list covers `response`, `manage_todo_list`, and `get_tool_response`.
163
+ - `maxToolResponseChars` / `maxToolResponseTokens` only drive an eager hard-cap truncation when a single tool output is big enough to blow up the very next model call. The truncated head always points at `get_tool_response` for recovery.
170
164
 
171
165
  The smart wrapper now supports runtime presets (`fast`, `balanced`, `deep`, `research`), custom profiles layered on top of a base preset, structured summarization, hybrid context compaction, configurable tool-response retention, in-memory fact storage, delegation limits, and an eval harness via `runSmartAgentEvalHarness(...)`.
172
166
 
package/dist/index.cjs CHANGED
@@ -2136,18 +2136,13 @@ var BASE_DEFAULTS = {
2136
2136
  requireJsonOutputContract: true
2137
2137
  },
2138
2138
  toolResponses: {
2139
- maxToolResponseChars: 3600,
2140
- maxToolResponseTokens: 1200,
2141
- smallResponseChars: 0,
2142
- smallResponsePolicy: "keep_full",
2143
- defaultPolicy: "keep_structured",
2144
- largeResponsePolicy: "summarize_archive",
2139
+ maxToolResponseChars: 12e3,
2140
+ maxToolResponseTokens: 3e3,
2141
+ defaultPolicy: "summarize_archive",
2145
2142
  toolResponseRetentionByTool: {},
2146
2143
  criticalTools: [...DEFAULT_CRITICAL_TOOLS],
2147
2144
  schemaValidation: "strict",
2148
- retryOnSchemaError: true,
2149
- fallbackPolicy: "keep_full",
2150
- keepRecentFullCount: 0
2145
+ retryOnSchemaError: true
2151
2146
  }
2152
2147
  };
2153
2148
  function buildProfile(overrides) {
@@ -2186,7 +2181,7 @@ var DEFAULT_PROFILE_CONFIGS = {
2186
2181
  },
2187
2182
  memory: { readPolicy: "recent_only" },
2188
2183
  delegation: { mode: "off", maxDelegationDepth: 1, maxChildCalls: 2, maxParallelChild: 1, childContextPolicy: "minimal" },
2189
- toolResponses: { maxToolResponseChars: 2400, maxToolResponseTokens: 800 }
2184
+ toolResponses: { maxToolResponseChars: 8e3, maxToolResponseTokens: 2e3 }
2190
2185
  }),
2191
2186
  balanced: buildProfile({}),
2192
2187
  deep: buildProfile({
@@ -2203,7 +2198,7 @@ var DEFAULT_PROFILE_CONFIGS = {
2203
2198
  },
2204
2199
  memory: { scope: "workspace" },
2205
2200
  delegation: { maxDelegationDepth: 3, maxChildCalls: 6 },
2206
- toolResponses: { maxToolResponseChars: 5400, maxToolResponseTokens: 1800 }
2201
+ toolResponses: { maxToolResponseChars: 16e3, maxToolResponseTokens: 4e3 }
2207
2202
  }),
2208
2203
  research: buildProfile({
2209
2204
  limits: { maxToolCalls: 20, maxParallelTools: 4, maxContextTokens: 56e3 },
@@ -2219,7 +2214,7 @@ var DEFAULT_PROFILE_CONFIGS = {
2219
2214
  },
2220
2215
  memory: { scope: "workspace" },
2221
2216
  delegation: { mode: "automatic", maxDelegationDepth: 4, maxChildCalls: 8, maxParallelChild: 3, childContextPolicy: "full" },
2222
- toolResponses: { maxToolResponseChars: 7200, maxToolResponseTokens: 2200 }
2217
+ toolResponses: { maxToolResponseChars: 24e3, maxToolResponseTokens: 6e3 }
2223
2218
  })
2224
2219
  };
2225
2220
  function mergeLimits(base, override) {
@@ -2426,63 +2421,65 @@ function summarizeObject(value) {
2426
2421
  return safeStringify(value);
2427
2422
  }
2428
2423
  function classifyToolResponse(toolName, output, config) {
2429
- const serialized = safeStringify(output);
2430
- const tokenCount = countApproxTokens(serialized);
2431
2424
  const criticalTools = new Set(config.toolResponses.criticalTools);
2432
2425
  if (criticalTools.has(toolName)) return "critical";
2433
- if (serialized === "ok" || serialized === "true" || serialized === "null") {
2434
- return "redundant";
2435
- }
2426
+ const serialized = safeStringify(output);
2427
+ const tokenCount = countApproxTokens(serialized);
2436
2428
  if (tokenCount >= config.toolResponses.maxToolResponseTokens || serialized.length >= config.toolResponses.maxToolResponseChars) {
2437
2429
  return "verbose";
2438
2430
  }
2439
- const smallChars = config.toolResponses.smallResponseChars;
2440
- if (smallChars > 0 && serialized.length <= smallChars) {
2441
- return "small";
2442
- }
2443
2431
  return "informative";
2444
2432
  }
2445
- function resolveToolResponsePolicy(toolName, output, config) {
2433
+ function applyToolResponseHardCap(toolName, output, executionId, config) {
2446
2434
  const serialized = safeStringify(output);
2447
2435
  const tokenCount = countApproxTokens(serialized);
2448
2436
  const classification = classifyToolResponse(toolName, output, config);
2449
- const byTool = config.toolResponses.toolResponseRetentionByTool[toolName];
2450
- let retentionPolicy = byTool || config.toolResponses.defaultPolicy;
2451
- if (byTool) ; else if (classification === "critical") {
2452
- retentionPolicy = config.toolResponses.fallbackPolicy;
2453
- } else if (classification === "verbose") {
2454
- retentionPolicy = config.toolResponses.largeResponsePolicy;
2455
- } else if (classification === "small") {
2456
- retentionPolicy = config.toolResponses.smallResponsePolicy;
2457
- } else if (classification === "redundant") {
2458
- retentionPolicy = "drop";
2459
- }
2460
- const summary = summarizeObject(output);
2461
- if (retentionPolicy === "keep_full") {
2462
- return { classification, retentionPolicy, content: serialized, rawOutput: output, summary, tokenCount };
2463
- }
2464
- if (retentionPolicy === "keep_structured") {
2465
- return { classification, retentionPolicy, content: summary, rawOutput: output, summary, tokenCount };
2466
- }
2467
- if (retentionPolicy === "summarize_archive") {
2468
- return {
2469
- classification,
2470
- retentionPolicy,
2471
- content: `ARCHIVED_TOOL_RESPONSE: ${summary}`,
2472
- rawOutput: output,
2473
- summary,
2474
- tokenCount
2475
- };
2437
+ if (classification !== "verbose") {
2438
+ return { classification, content: serialized, rawOutput: output, tokenCount, truncated: false };
2476
2439
  }
2440
+ const cap = config.toolResponses.maxToolResponseChars;
2441
+ const head = serialized.slice(0, cap);
2442
+ const note = `
2443
+ ... [TRUNCATED ${serialized.length - cap} chars by hard cap. Use get_tool_response with executionId="${executionId}" to fetch the full payload.]`;
2477
2444
  return {
2478
2445
  classification,
2479
- retentionPolicy,
2480
- content: `DROPPED_TOOL_RESPONSE: ${summary}`,
2446
+ content: head + note,
2481
2447
  rawOutput: output,
2482
- summary,
2483
- tokenCount
2448
+ tokenCount,
2449
+ truncated: true
2484
2450
  };
2485
2451
  }
2452
+ function resolveSummarizationRetention(toolName, config) {
2453
+ const criticalTools = new Set(config.toolResponses.criticalTools);
2454
+ if (criticalTools.has(toolName)) return "keep_full";
2455
+ const byTool = config.toolResponses.toolResponseRetentionByTool[toolName];
2456
+ return byTool || config.toolResponses.defaultPolicy;
2457
+ }
2458
+ function renderRetainedToolMessage(args) {
2459
+ const { policy, rawOutput, toolName, toolCallId, executionId, prebuiltSummary } = args;
2460
+ if (policy === "keep_full") {
2461
+ return typeof rawOutput === "string" ? rawOutput : safeStringify(rawOutput);
2462
+ }
2463
+ const summary = prebuiltSummary || summarizeObject(rawOutput);
2464
+ const refs = [
2465
+ `toolName=${toolName}`,
2466
+ toolCallId ? `toolCallId=${toolCallId}` : null,
2467
+ executionId ? `executionId=${executionId}` : null
2468
+ ].filter(Boolean).join("; ");
2469
+ const refId = executionId || toolCallId || "";
2470
+ if (policy === "keep_structured") {
2471
+ return `STRUCTURED_TOOL_RESPONSE [${refs}]
2472
+ Preview: ${summary}
2473
+ Use get_tool_response with executionId "${refId}" to fetch the full payload when a specific field is needed.`;
2474
+ }
2475
+ if (policy === "summarize_archive") {
2476
+ return `ARCHIVED_TOOL_RESPONSE [${refs}]
2477
+ Summary: ${summary}
2478
+ Use get_tool_response with executionId "${refId}" to fetch the full payload.`;
2479
+ }
2480
+ return `DROPPED_TOOL_RESPONSE [${refs}]
2481
+ Use get_tool_response with executionId "${refId}" only if you must recover the original payload.`;
2482
+ }
2486
2483
  function validateToolArgs(tool, args) {
2487
2484
  const schema = tool.schema;
2488
2485
  if (!schema || typeof schema.parse !== "function") {
@@ -2761,52 +2758,25 @@ function createToolsNode(initialTools, opts) {
2761
2758
  state.ctx.__structuredOutputParsed = output.data;
2762
2759
  state.ctx.__finalizedDueToStructuredOutput = true;
2763
2760
  }
2764
- const responsePolicy = resolveToolResponsePolicy(toolName, output, resolved);
2761
+ const responsePolicy = applyToolResponseHardCap(toolName, output, executionId, resolved);
2765
2762
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
2766
- const storedOutput = responsePolicy.retentionPolicy === "keep_full" ? output : responsePolicy.summary;
2767
2763
  toolHistory.push({
2768
2764
  executionId,
2769
2765
  toolName,
2770
2766
  args,
2771
- output: storedOutput,
2767
+ output,
2772
2768
  rawOutput: output,
2773
2769
  timestamp,
2774
2770
  tool_call_id: tc.id,
2775
- summarized: responsePolicy.retentionPolicy !== "keep_full",
2771
+ summarized: false,
2776
2772
  originalTokenCount: responsePolicy.tokenCount,
2777
2773
  classification: responsePolicy.classification,
2778
- retentionPolicy: responsePolicy.retentionPolicy,
2779
- summary: responsePolicy.summary,
2780
- archiveId: responsePolicy.retentionPolicy === "summarize_archive" || responsePolicy.retentionPolicy === "drop" ? executionId : void 0,
2774
+ retentionPolicy: "keep_full",
2775
+ summary: void 0,
2776
+ archiveId: void 0,
2781
2777
  status: "success"
2782
2778
  });
2783
- if (resolved.context.archiveLargeToolResponses && (responsePolicy.retentionPolicy === "summarize_archive" || responsePolicy.retentionPolicy === "drop")) {
2784
- toolHistoryArchived.push({
2785
- executionId,
2786
- toolName,
2787
- args,
2788
- output,
2789
- rawOutput: output,
2790
- timestamp,
2791
- tool_call_id: tc.id,
2792
- summarized: true,
2793
- originalTokenCount: responsePolicy.tokenCount,
2794
- classification: responsePolicy.classification,
2795
- retentionPolicy: responsePolicy.retentionPolicy,
2796
- summary: responsePolicy.summary,
2797
- archiveId: executionId,
2798
- status: "success"
2799
- });
2800
- }
2801
- let toolMessageContent = responsePolicy.content;
2802
- if (responsePolicy.retentionPolicy === "summarize_archive") {
2803
- toolMessageContent = `ARCHIVED_TOOL_RESPONSE [executionId=${executionId}]: ${responsePolicy.summary}
2804
- Only use get_tool_response with executionId "${executionId}" if this summary is missing a specific field, id, snippet, or exact payload you still need.`;
2805
- } else if (responsePolicy.retentionPolicy === "drop") {
2806
- toolMessageContent = `DROPPED_TOOL_RESPONSE [executionId=${executionId}]: ${responsePolicy.summary}
2807
- Only use get_tool_response with executionId "${executionId}" if this summary is missing a specific field, id, snippet, or exact payload you still need.`;
2808
- }
2809
- appended.push({ role: "tool", content: toolMessageContent, tool_call_id: tc.id || `${tc.name}_${appended.length}`, name: tc.name });
2779
+ appended.push({ role: "tool", content: responsePolicy.content, tool_call_id: tc.id || `${tc.name}_${appended.length}`, name: tc.name });
2810
2780
  onEvent?.({ type: "tool_call", phase: "success", name: toolName, id: tc.id, args, result: output, durationMs });
2811
2781
  onProgress?.({ stage: "tools", message: `Tool ${tc.name} completed`, detail: { durationMs } });
2812
2782
  if (needsApproval && approvalEntry) {
@@ -2850,8 +2820,9 @@ Only use get_tool_response with executionId "${executionId}" if this summary is
2850
2820
  messageList,
2851
2821
  debug: {
2852
2822
  classification: responsePolicy.classification,
2853
- retentionPolicy: responsePolicy.retentionPolicy,
2854
- originalTokenCount: responsePolicy.tokenCount
2823
+ retentionPolicy: "keep_full",
2824
+ originalTokenCount: responsePolicy.tokenCount,
2825
+ truncated: responsePolicy.truncated
2855
2826
  }
2856
2827
  });
2857
2828
  toolCount += 1;
@@ -3990,40 +3961,6 @@ function createAgentNode(opts) {
3990
3961
  }
3991
3962
 
3992
3963
  // src/smart/contextPolicy.ts
3993
- function collectRecentFullToolCallIds(state, keepRecentFullCount) {
3994
- if (!keepRecentFullCount || keepRecentFullCount <= 0) return /* @__PURE__ */ new Set();
3995
- const history = state.toolHistory || [];
3996
- const ids = [];
3997
- for (let index = history.length - 1; index >= 0 && ids.length < keepRecentFullCount; index -= 1) {
3998
- const entry = history[index];
3999
- if (!entry || !entry.tool_call_id) continue;
4000
- if (entry.status && entry.status !== "success") continue;
4001
- ids.push(entry.tool_call_id);
4002
- }
4003
- return new Set(ids);
4004
- }
4005
- function applyRecentFullOverride(messages, state, config) {
4006
- const keepN = config.toolResponses.keepRecentFullCount;
4007
- if (!keepN || keepN <= 0) return messages;
4008
- const recentIds = collectRecentFullToolCallIds(state, keepN);
4009
- if (recentIds.size === 0) return messages;
4010
- const history = state.toolHistory || [];
4011
- const byCallId = /* @__PURE__ */ new Map();
4012
- for (const entry of history) {
4013
- if (entry?.tool_call_id) byCallId.set(entry.tool_call_id, entry);
4014
- }
4015
- return messages.map((message) => {
4016
- if (message.role !== "tool" || !message.tool_call_id) return message;
4017
- if (!recentIds.has(message.tool_call_id)) return message;
4018
- const entry = byCallId.get(message.tool_call_id);
4019
- if (!entry) return message;
4020
- const raw = entry.rawOutput ?? entry.output;
4021
- if (raw === void 0) return message;
4022
- const fullContent = typeof raw === "string" ? raw : safeStringify(raw);
4023
- if (!fullContent || fullContent === message.content) return message;
4024
- return { ...message, content: fullContent };
4025
- });
4026
- }
4027
3964
  function collectRecentTurns(messages, lastTurnsToKeep) {
4028
3965
  if (lastTurnsToKeep <= 0) return [];
4029
3966
  const systemPrefix = messages.filter((message, index) => index === 0 && message.role === "system");
@@ -4124,7 +4061,7 @@ function clampToBudget(messages, maxContextTokens) {
4124
4061
  function buildModelMessages(state, config) {
4125
4062
  const rawMessages = state.messages || [];
4126
4063
  if (config.context.policy === "raw") {
4127
- return clampToBudget(applyRecentFullOverride(rawMessages, state, config), config.limits.maxContextTokens);
4064
+ return clampToBudget(rawMessages, config.limits.maxContextTokens);
4128
4065
  }
4129
4066
  const recentMessages = collectRecentTurns(rawMessages, config.context.lastTurnsToKeep);
4130
4067
  const latestSummary = state.summaryRecords?.[state.summaryRecords.length - 1];
@@ -4144,7 +4081,7 @@ function buildModelMessages(state, config) {
4144
4081
  ...syntheticContextMessages,
4145
4082
  ...config.context.policy === "summary_only" ? body.filter((message) => message.role === "user" || message.role === "assistant").slice(-2) : body
4146
4083
  ];
4147
- return clampToBudget(applyRecentFullOverride(assembled, state, config), config.limits.maxContextTokens);
4084
+ return clampToBudget(assembled, config.limits.maxContextTokens);
4148
4085
  }
4149
4086
 
4150
4087
  // src/nodes/contextSummarize.ts
@@ -4246,24 +4183,8 @@ function extractCanonicalFacts(messages) {
4246
4183
  }
4247
4184
  return [...facts.values()];
4248
4185
  }
4249
- function flattenToolMessageContent(message) {
4250
- if (typeof message.content === "string") {
4251
- return message.content;
4252
- }
4253
- if (Array.isArray(message.content)) {
4254
- return message.content.map((part) => typeof part === "string" ? part : part?.text ?? part?.content ?? String(part ?? "")).join(" ");
4255
- }
4256
- return "";
4257
- }
4258
4186
  function isSummarizedToolPlaceholder(content) {
4259
- return typeof content === "string" && (content === "SUMMARIZED" || content.startsWith("SUMMARIZED_TOOL_RESPONSE"));
4260
- }
4261
- function summarizeToolMessagePreview(message) {
4262
- const preview = flattenToolMessageContent(message).trim();
4263
- if (!preview) {
4264
- return "No summary available.";
4265
- }
4266
- return preview.length > 240 ? `${preview.slice(0, 240)}...` : preview;
4187
+ return typeof content === "string" && (content === "SUMMARIZED" || content.startsWith("SUMMARIZED_TOOL_RESPONSE") || content.startsWith("ARCHIVED_TOOL_RESPONSE") || content.startsWith("STRUCTURED_TOOL_RESPONSE") || content.startsWith("DROPPED_TOOL_RESPONSE"));
4267
4188
  }
4268
4189
  function findToolHistoryEntry(state, toolCallId, toolName) {
4269
4190
  const entries = [
@@ -4289,29 +4210,6 @@ function findToolHistoryEntry(state, toolCallId, toolName) {
4289
4210
  }
4290
4211
  return void 0;
4291
4212
  }
4292
- function buildSummarizedToolMessage(message, state) {
4293
- const toolName = message.name || "unknown_tool";
4294
- const toolCallId = message.tool_call_id;
4295
- const historyEntry = findToolHistoryEntry(state, toolCallId, toolName);
4296
- const executionId = historyEntry?.executionId;
4297
- const summary = historyEntry?.summary || summarizeToolMessagePreview(message);
4298
- const references = [
4299
- `toolName=${toolName}`,
4300
- toolCallId ? `toolCallId=${toolCallId}` : null,
4301
- executionId ? `executionId=${executionId}` : null
4302
- ].filter(Boolean).join("; ");
4303
- let retrievalHint = "Only use get_tool_response if this summary is missing a specific field, id, snippet, or exact payload you still need.";
4304
- if (executionId && toolCallId) {
4305
- retrievalHint = `Only use get_tool_response with executionId "${executionId}" or tool call id "${toolCallId}" if this summary is missing a specific field, id, snippet, or exact payload you still need.`;
4306
- } else if (executionId || toolCallId) {
4307
- retrievalHint = `Only use get_tool_response with executionId "${executionId || toolCallId}" if this summary is missing a specific field, id, snippet, or exact payload you still need.`;
4308
- }
4309
- return [
4310
- `SUMMARIZED_TOOL_RESPONSE [${references}]`,
4311
- `Summary: ${summary}`,
4312
- retrievalHint
4313
- ].join("\n");
4314
- }
4315
4213
  function mergeStableFacts(summary, additionalFacts) {
4316
4214
  if (additionalFacts.length === 0) return summary;
4317
4215
  const merged = new Map(summary.stable_facts.map((fact) => [fact.key, fact]));
@@ -4369,20 +4267,13 @@ function createContextSummarizeNode(opts) {
4369
4267
  const messages = state.messages || [];
4370
4268
  const onEvent = state.ctx?.__onEvent;
4371
4269
  const traceSession = state.ctx?.__traceSession;
4372
- const toolResponsesConfigEarly = resolved.toolResponses;
4373
- const recentFullToolCallIdsEarly = collectRecentFullToolCallIds(state, toolResponsesConfigEarly.keepRecentFullCount);
4374
4270
  const compressableMessages = messages.filter(
4375
4271
  (m) => {
4376
4272
  if (m.role !== "tool") return false;
4377
4273
  if (isSummarizedToolPlaceholder(m.content)) return false;
4378
4274
  if (isSyntheticSummaryMessage(m)) return false;
4379
- if (m.tool_call_id && recentFullToolCallIdsEarly.has(m.tool_call_id)) return false;
4380
- const histEntry = findToolHistoryEntry(state, m.tool_call_id, m.name);
4381
- if (histEntry?.retentionPolicy === "keep_full") return false;
4382
- if (!histEntry?.retentionPolicy) {
4383
- const byTool = toolResponsesConfigEarly.toolResponseRetentionByTool?.[m.name || ""];
4384
- if ((byTool || toolResponsesConfigEarly.defaultPolicy) === "keep_full") return false;
4385
- }
4275
+ const policy = resolveSummarizationRetention(m.name || "", resolved);
4276
+ if (policy === "keep_full") return false;
4386
4277
  return true;
4387
4278
  }
4388
4279
  );
@@ -4562,8 +4453,8 @@ ${canonicalFacts.length > 0 ? canonicalFacts.map((fact) => `- ${fact.key}: ${fac
4562
4453
  break;
4563
4454
  }
4564
4455
  }
4565
- const toolResponsesConfig = resolved.toolResponses;
4566
- const recentFullToolCallIds = collectRecentFullToolCallIds(state, toolResponsesConfig.keepRecentFullCount);
4456
+ const archivedExecutions = [...state.toolHistoryArchived || []];
4457
+ const archivedIds = new Set(archivedExecutions.map((entry) => entry?.executionId).filter(Boolean));
4567
4458
  const newMessages = messages.map((m) => {
4568
4459
  if (m.role === "tool") {
4569
4460
  if (isSyntheticSummaryMessage(m)) {
@@ -4575,25 +4466,39 @@ ${canonicalFacts.length > 0 ? canonicalFacts.map((fact) => `- ${fact.key}: ${fac
4575
4466
  }
4576
4467
  if (isSummarizedToolPlaceholder(m.content)) return m;
4577
4468
  if (toolCallId && protectedToolCallIds.has(toolCallId)) return m;
4578
- if (toolCallId && recentFullToolCallIds.has(toolCallId)) return m;
4579
4469
  const toolName = m.name || "";
4470
+ const policy = resolveSummarizationRetention(toolName, resolved);
4471
+ if (policy === "keep_full") return m;
4580
4472
  const historyEntry = findToolHistoryEntry(state, toolCallId, toolName);
4581
- const entryRetention = historyEntry?.retentionPolicy;
4582
- if (entryRetention === "keep_full") return m;
4583
- if (!entryRetention) {
4584
- const byTool = toolResponsesConfig.toolResponseRetentionByTool?.[toolName];
4585
- const configPolicy = byTool || toolResponsesConfig.defaultPolicy;
4586
- if (configPolicy === "keep_full") return m;
4473
+ const rawOutput = historyEntry?.rawOutput ?? historyEntry?.output ?? m.content;
4474
+ const executionId = historyEntry?.executionId;
4475
+ const prebuiltSummary = historyEntry?.summary || summarizeObject(rawOutput);
4476
+ if ((policy === "summarize_archive" || policy === "drop") && historyEntry && executionId && !archivedIds.has(executionId)) {
4477
+ archivedExecutions.push({
4478
+ ...historyEntry,
4479
+ summarized: true,
4480
+ retentionPolicy: policy,
4481
+ archiveId: executionId,
4482
+ summary: prebuiltSummary
4483
+ });
4484
+ archivedIds.add(executionId);
4587
4485
  }
4588
4486
  return {
4589
4487
  ...m,
4590
- content: buildSummarizedToolMessage(m, state)
4488
+ content: renderRetainedToolMessage({
4489
+ policy,
4490
+ rawOutput,
4491
+ toolName,
4492
+ toolCallId,
4493
+ executionId,
4494
+ prebuiltSummary
4495
+ })
4591
4496
  };
4592
4497
  }
4593
4498
  return m;
4594
4499
  }).filter((m) => m !== null);
4595
4500
  const actuallyCompressed = newMessages.filter(
4596
- (m) => m.role === "tool" && typeof m.content === "string" && m.content.startsWith("SUMMARIZED_TOOL_RESPONSE") && !messages.some((orig) => orig.tool_call_id === m.tool_call_id && isSummarizedToolPlaceholder(orig.content))
4501
+ (m) => m.role === "tool" && isSummarizedToolPlaceholder(m.content) && !messages.some((orig) => orig.tool_call_id === m.tool_call_id && isSummarizedToolPlaceholder(orig.content))
4597
4502
  );
4598
4503
  if (actuallyCompressed.length === 0) {
4599
4504
  return {};
@@ -4672,7 +4577,8 @@ ${canonicalFacts.length > 0 ? canonicalFacts.map((fact) => `- ${fact.key}: ${fac
4672
4577
  return {
4673
4578
  messages: finalMessages,
4674
4579
  summaries,
4675
- summaryRecords
4580
+ summaryRecords,
4581
+ toolHistoryArchived: archivedExecutions
4676
4582
  };
4677
4583
  };
4678
4584
  }