@illuma-ai/agents 1.0.96 → 1.1.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.
Files changed (76) hide show
  1. package/dist/cjs/agents/AgentContext.cjs +6 -2
  2. package/dist/cjs/agents/AgentContext.cjs.map +1 -1
  3. package/dist/cjs/common/constants.cjs +78 -0
  4. package/dist/cjs/common/constants.cjs.map +1 -1
  5. package/dist/cjs/graphs/Graph.cjs +191 -165
  6. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  7. package/dist/cjs/main.cjs +22 -0
  8. package/dist/cjs/main.cjs.map +1 -1
  9. package/dist/cjs/messages/dedup.cjs +95 -0
  10. package/dist/cjs/messages/dedup.cjs.map +1 -0
  11. package/dist/cjs/tools/CodeExecutor.cjs +22 -3
  12. package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
  13. package/dist/cjs/types/graph.cjs.map +1 -1
  14. package/dist/cjs/utils/contextPressure.cjs +154 -0
  15. package/dist/cjs/utils/contextPressure.cjs.map +1 -0
  16. package/dist/cjs/utils/pruneCalibration.cjs +78 -0
  17. package/dist/cjs/utils/pruneCalibration.cjs.map +1 -0
  18. package/dist/cjs/utils/run.cjs.map +1 -1
  19. package/dist/cjs/utils/tokens.cjs.map +1 -1
  20. package/dist/cjs/utils/toolDiscoveryCache.cjs +127 -0
  21. package/dist/cjs/utils/toolDiscoveryCache.cjs.map +1 -0
  22. package/dist/esm/agents/AgentContext.mjs +6 -2
  23. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  24. package/dist/esm/common/constants.mjs +71 -1
  25. package/dist/esm/common/constants.mjs.map +1 -1
  26. package/dist/esm/graphs/Graph.mjs +192 -166
  27. package/dist/esm/graphs/Graph.mjs.map +1 -1
  28. package/dist/esm/main.mjs +5 -1
  29. package/dist/esm/main.mjs.map +1 -1
  30. package/dist/esm/messages/dedup.mjs +93 -0
  31. package/dist/esm/messages/dedup.mjs.map +1 -0
  32. package/dist/esm/tools/CodeExecutor.mjs +22 -3
  33. package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
  34. package/dist/esm/types/graph.mjs.map +1 -1
  35. package/dist/esm/utils/contextPressure.mjs +148 -0
  36. package/dist/esm/utils/contextPressure.mjs.map +1 -0
  37. package/dist/esm/utils/pruneCalibration.mjs +74 -0
  38. package/dist/esm/utils/pruneCalibration.mjs.map +1 -0
  39. package/dist/esm/utils/run.mjs.map +1 -1
  40. package/dist/esm/utils/tokens.mjs.map +1 -1
  41. package/dist/esm/utils/toolDiscoveryCache.mjs +125 -0
  42. package/dist/esm/utils/toolDiscoveryCache.mjs.map +1 -0
  43. package/dist/types/agents/AgentContext.d.ts +4 -1
  44. package/dist/types/common/constants.d.ts +49 -0
  45. package/dist/types/graphs/Graph.d.ts +25 -0
  46. package/dist/types/messages/dedup.d.ts +25 -0
  47. package/dist/types/messages/index.d.ts +1 -0
  48. package/dist/types/types/graph.d.ts +63 -0
  49. package/dist/types/utils/contextPressure.d.ts +72 -0
  50. package/dist/types/utils/index.d.ts +3 -0
  51. package/dist/types/utils/pruneCalibration.d.ts +43 -0
  52. package/dist/types/utils/toolDiscoveryCache.d.ts +77 -0
  53. package/package.json +1 -1
  54. package/src/agents/AgentContext.ts +7 -0
  55. package/src/common/constants.ts +82 -0
  56. package/src/graphs/Graph.ts +254 -208
  57. package/src/graphs/contextManagement.e2e.test.ts +28 -20
  58. package/src/graphs/gapFeatures.test.ts +520 -0
  59. package/src/graphs/nonBlockingSummarization.test.ts +307 -0
  60. package/src/messages/__tests__/dedup.test.ts +166 -0
  61. package/src/messages/dedup.ts +104 -0
  62. package/src/messages/index.ts +1 -0
  63. package/src/specs/agent-handoffs-bedrock.integration.test.ts +7 -7
  64. package/src/specs/agent-handoffs.test.ts +36 -36
  65. package/src/specs/thinking-handoff.test.ts +10 -10
  66. package/src/tools/CodeExecutor.ts +22 -3
  67. package/src/types/graph.ts +73 -0
  68. package/src/utils/__tests__/pruneCalibration.test.ts +148 -0
  69. package/src/utils/__tests__/toolDiscoveryCache.test.ts +214 -0
  70. package/src/utils/contextPressure.test.ts +262 -0
  71. package/src/utils/contextPressure.ts +188 -0
  72. package/src/utils/index.ts +3 -0
  73. package/src/utils/pruneCalibration.ts +92 -0
  74. package/src/utils/run.ts +108 -108
  75. package/src/utils/tokens.ts +118 -118
  76. package/src/utils/toolDiscoveryCache.ts +150 -0
@@ -4,7 +4,7 @@ import { HumanMessage, ToolMessage } from '@langchain/core/messages';
4
4
  import type { ToolCall } from '@langchain/core/messages/tool';
5
5
  import type { RunnableConfig } from '@langchain/core/runnables';
6
6
  import type * as t from '@/types';
7
- import { Providers, Constants } from '@/common';
7
+ import { Providers, Constants, EdgeType } from '@/common';
8
8
  import { StandardGraph } from '@/graphs/Graph';
9
9
  import { Run } from '@/run';
10
10
 
@@ -94,7 +94,7 @@ describe('Agent Handoffs Tests', () => {
94
94
  {
95
95
  from: 'agent_a',
96
96
  to: 'agent_b',
97
- edgeType: 'handoff',
97
+ edgeType: EdgeType.HANDOFF,
98
98
  description: 'Transfer to agent B',
99
99
  },
100
100
  ];
@@ -128,7 +128,7 @@ describe('Agent Handoffs Tests', () => {
128
128
  {
129
129
  from: 'agent_a',
130
130
  to: 'agent_b',
131
- edgeType: 'handoff',
131
+ edgeType: EdgeType.HANDOFF,
132
132
  description: 'Transfer to agent B when needed',
133
133
  },
134
134
  ];
@@ -192,7 +192,7 @@ describe('Agent Handoffs Tests', () => {
192
192
  {
193
193
  from: 'agent_a',
194
194
  to: 'agent_b',
195
- edgeType: 'handoff',
195
+ edgeType: EdgeType.HANDOFF,
196
196
  },
197
197
  ];
198
198
 
@@ -223,13 +223,13 @@ describe('Agent Handoffs Tests', () => {
223
223
  {
224
224
  from: 'agent_a',
225
225
  to: 'agent_b',
226
- edgeType: 'handoff',
226
+ edgeType: EdgeType.HANDOFF,
227
227
  description: 'Transfer to agent B',
228
228
  },
229
229
  {
230
230
  from: 'agent_b',
231
231
  to: 'agent_a',
232
- edgeType: 'handoff',
232
+ edgeType: EdgeType.HANDOFF,
233
233
  description: 'Transfer to agent A',
234
234
  },
235
235
  ];
@@ -268,12 +268,12 @@ describe('Agent Handoffs Tests', () => {
268
268
  {
269
269
  from: 'agent_a',
270
270
  to: 'agent_b',
271
- edgeType: 'handoff',
271
+ edgeType: EdgeType.HANDOFF,
272
272
  },
273
273
  {
274
274
  from: 'agent_b',
275
275
  to: 'agent_a',
276
- edgeType: 'handoff',
276
+ edgeType: EdgeType.HANDOFF,
277
277
  },
278
278
  ];
279
279
 
@@ -334,13 +334,13 @@ describe('Agent Handoffs Tests', () => {
334
334
  {
335
335
  from: 'agent_a',
336
336
  to: 'agent_b',
337
- edgeType: 'handoff',
337
+ edgeType: EdgeType.HANDOFF,
338
338
  description: 'Transfer to agent B',
339
339
  },
340
340
  {
341
341
  from: 'agent_b',
342
342
  to: 'agent_c',
343
- edgeType: 'handoff',
343
+ edgeType: EdgeType.HANDOFF,
344
344
  description: 'Transfer to agent C',
345
345
  },
346
346
  ];
@@ -395,19 +395,19 @@ describe('Agent Handoffs Tests', () => {
395
395
  {
396
396
  from: 'router',
397
397
  to: 'agent_a',
398
- edgeType: 'handoff',
398
+ edgeType: EdgeType.HANDOFF,
399
399
  description: 'Transfer to agent A for task A',
400
400
  },
401
401
  {
402
402
  from: 'router',
403
403
  to: 'agent_b',
404
- edgeType: 'handoff',
404
+ edgeType: EdgeType.HANDOFF,
405
405
  description: 'Transfer to agent B for task B',
406
406
  },
407
407
  {
408
408
  from: 'router',
409
409
  to: 'agent_c',
410
- edgeType: 'handoff',
410
+ edgeType: EdgeType.HANDOFF,
411
411
  description: 'Transfer to agent C for task C',
412
412
  },
413
413
  ];
@@ -449,13 +449,13 @@ describe('Agent Handoffs Tests', () => {
449
449
  {
450
450
  from: 'router',
451
451
  to: 'agent_a',
452
- edgeType: 'handoff',
452
+ edgeType: EdgeType.HANDOFF,
453
453
  description: 'Transfer to agent A',
454
454
  },
455
455
  {
456
456
  from: 'router',
457
457
  to: 'agent_b',
458
- edgeType: 'handoff',
458
+ edgeType: EdgeType.HANDOFF,
459
459
  description: 'Transfer to agent B',
460
460
  },
461
461
  ];
@@ -519,7 +519,7 @@ describe('Agent Handoffs Tests', () => {
519
519
  {
520
520
  from: 'agent_a',
521
521
  to: 'agent_b',
522
- edgeType: 'handoff',
522
+ edgeType: EdgeType.HANDOFF,
523
523
  description: 'Transfer to agent B with instructions',
524
524
  prompt: 'Provide specific instructions for agent B',
525
525
  promptKey: 'instructions',
@@ -551,7 +551,7 @@ describe('Agent Handoffs Tests', () => {
551
551
  {
552
552
  from: 'agent_a',
553
553
  to: 'agent_b',
554
- edgeType: 'handoff',
554
+ edgeType: EdgeType.HANDOFF,
555
555
  prompt: 'Instructions for handoff',
556
556
  // promptKey not specified, should default to 'instructions'
557
557
  },
@@ -581,7 +581,7 @@ describe('Agent Handoffs Tests', () => {
581
581
  {
582
582
  from: 'agent_a',
583
583
  to: 'agent_b',
584
- edgeType: 'handoff',
584
+ edgeType: EdgeType.HANDOFF,
585
585
  description: 'Transfer to agent B',
586
586
  prompt: 'Additional context for agent B',
587
587
  promptKey: 'context',
@@ -638,7 +638,7 @@ describe('Agent Handoffs Tests', () => {
638
638
  {
639
639
  from: 'agent_a',
640
640
  to: 'agent_a',
641
- edgeType: 'handoff',
641
+ edgeType: EdgeType.HANDOFF,
642
642
  description: 'Self-handoff (should be allowed but unusual)',
643
643
  },
644
644
  ];
@@ -724,7 +724,7 @@ describe('Agent Handoffs Tests', () => {
724
724
  {
725
725
  from: 'agent_a',
726
726
  to: 'agent_b',
727
- edgeType: 'handoff',
727
+ edgeType: EdgeType.HANDOFF,
728
728
  description: 'Transfer to agent B',
729
729
  },
730
730
  ];
@@ -759,12 +759,12 @@ describe('Agent Handoffs Tests', () => {
759
759
  {
760
760
  from: 'agent_a',
761
761
  to: 'agent_b',
762
- edgeType: 'handoff',
762
+ edgeType: EdgeType.HANDOFF,
763
763
  },
764
764
  {
765
765
  from: 'agent_b',
766
766
  to: 'agent_c',
767
- edgeType: 'handoff',
767
+ edgeType: EdgeType.HANDOFF,
768
768
  },
769
769
  ];
770
770
 
@@ -806,12 +806,12 @@ describe('Agent Handoffs Tests', () => {
806
806
  {
807
807
  from: 'agent_a',
808
808
  to: 'agent_c',
809
- edgeType: 'handoff',
809
+ edgeType: EdgeType.HANDOFF,
810
810
  },
811
811
  {
812
812
  from: 'agent_b',
813
813
  to: 'agent_c',
814
- edgeType: 'handoff',
814
+ edgeType: EdgeType.HANDOFF,
815
815
  },
816
816
  ];
817
817
 
@@ -853,7 +853,7 @@ describe('Agent Handoffs Tests', () => {
853
853
  {
854
854
  from: 'router',
855
855
  to: 'data_analyst',
856
- edgeType: 'handoff',
856
+ edgeType: EdgeType.HANDOFF,
857
857
  description: 'Transfer to data analyst',
858
858
  prompt: 'Instructions for the analyst about what to analyze',
859
859
  promptKey: 'instructions',
@@ -939,7 +939,7 @@ describe('Agent Handoffs Tests', () => {
939
939
  {
940
940
  from: 'flight_assistant',
941
941
  to: 'hotel_assistant',
942
- edgeType: 'handoff',
942
+ edgeType: EdgeType.HANDOFF,
943
943
  description: 'Transfer to hotel booking',
944
944
  },
945
945
  ];
@@ -970,7 +970,7 @@ describe('Agent Handoffs Tests', () => {
970
970
  {
971
971
  from: 'agent_with_underscores',
972
972
  to: 'AgentWithCamelCase',
973
- edgeType: 'handoff',
973
+ edgeType: EdgeType.HANDOFF,
974
974
  },
975
975
  ];
976
976
 
@@ -1007,7 +1007,7 @@ describe('Agent Handoffs Tests', () => {
1007
1007
  {
1008
1008
  from: 'supervisor',
1009
1009
  to: 'data_analyst',
1010
- edgeType: 'handoff',
1010
+ edgeType: EdgeType.HANDOFF,
1011
1011
  // No description provided - should auto-generate from agent name + description
1012
1012
  },
1013
1013
  ];
@@ -1042,7 +1042,7 @@ describe('Agent Handoffs Tests', () => {
1042
1042
  {
1043
1043
  from: 'supervisor',
1044
1044
  to: 'writer',
1045
- edgeType: 'handoff',
1045
+ edgeType: EdgeType.HANDOFF,
1046
1046
  },
1047
1047
  ];
1048
1048
 
@@ -1075,7 +1075,7 @@ describe('Agent Handoffs Tests', () => {
1075
1075
  {
1076
1076
  from: 'supervisor',
1077
1077
  to: 'agent_b',
1078
- edgeType: 'handoff',
1078
+ edgeType: EdgeType.HANDOFF,
1079
1079
  description: 'Custom handoff description that takes priority',
1080
1080
  },
1081
1081
  ];
@@ -1118,9 +1118,9 @@ describe('Agent Handoffs Tests', () => {
1118
1118
  ];
1119
1119
 
1120
1120
  const edges: t.GraphEdge[] = [
1121
- { from: 'router', to: 'sales', edgeType: 'handoff' },
1122
- { from: 'router', to: 'support', edgeType: 'handoff' },
1123
- { from: 'router', to: 'billing', edgeType: 'handoff' },
1121
+ { from: 'router', to: 'sales', edgeType: EdgeType.HANDOFF },
1122
+ { from: 'router', to: 'support', edgeType: EdgeType.HANDOFF },
1123
+ { from: 'router', to: 'billing', edgeType: EdgeType.HANDOFF },
1124
1124
  ];
1125
1125
 
1126
1126
  const run = await Run.create(createTestConfig(agents, edges));
@@ -1176,7 +1176,7 @@ describe('Agent Handoffs Tests', () => {
1176
1176
  ];
1177
1177
 
1178
1178
  const edges: t.GraphEdge[] = [
1179
- { from: 'agent_a', to: 'agent_b', edgeType: 'handoff' },
1179
+ { from: 'agent_a', to: 'agent_b', edgeType: EdgeType.HANDOFF },
1180
1180
  ];
1181
1181
 
1182
1182
  const run = await Run.create(createTestConfig(agents, edges));
@@ -1215,7 +1215,7 @@ describe('Agent Handoffs Tests', () => {
1215
1215
  {
1216
1216
  from: 'agent_a',
1217
1217
  to: 'agent_b',
1218
- edgeType: 'handoff',
1218
+ edgeType: EdgeType.HANDOFF,
1219
1219
  description: 'Transfer to B',
1220
1220
  },
1221
1221
  ];
@@ -1255,7 +1255,7 @@ describe('Agent Handoffs Tests', () => {
1255
1255
  ];
1256
1256
 
1257
1257
  const edges: t.GraphEdge[] = [
1258
- { from: 'agent_a', to: 'agent_b', edgeType: 'handoff' },
1258
+ { from: 'agent_a', to: 'agent_b', edgeType: EdgeType.HANDOFF },
1259
1259
  ];
1260
1260
 
1261
1261
  const run = await Run.create(createTestConfig(agents, edges));
@@ -3,7 +3,7 @@ import { HumanMessage, ToolMessage } from '@langchain/core/messages';
3
3
  import type { ToolCall } from '@langchain/core/messages/tool';
4
4
  import type { RunnableConfig } from '@langchain/core/runnables';
5
5
  import type * as t from '@/types';
6
- import { Providers, Constants } from '@/common';
6
+ import { Providers, Constants, EdgeType } from '@/common';
7
7
  import { StandardGraph } from '@/graphs/Graph';
8
8
  import { Run } from '@/run';
9
9
 
@@ -77,7 +77,7 @@ describe('Thinking-Enabled Agent Handoff Tests', () => {
77
77
  {
78
78
  from: 'supervisor',
79
79
  to: 'specialist',
80
- edgeType: 'handoff',
80
+ edgeType: EdgeType.HANDOFF,
81
81
  description: 'Transfer to specialist for detailed analysis',
82
82
  },
83
83
  ];
@@ -166,7 +166,7 @@ describe('Thinking-Enabled Agent Handoff Tests', () => {
166
166
  {
167
167
  from: 'agent_a',
168
168
  to: 'agent_b',
169
- edgeType: 'handoff',
169
+ edgeType: EdgeType.HANDOFF,
170
170
  },
171
171
  ];
172
172
 
@@ -223,7 +223,7 @@ describe('Thinking-Enabled Agent Handoff Tests', () => {
223
223
  {
224
224
  from: 'coordinator',
225
225
  to: 'analyst',
226
- edgeType: 'handoff',
226
+ edgeType: EdgeType.HANDOFF,
227
227
  description: 'Transfer to analyst for deep analysis',
228
228
  },
229
229
  ];
@@ -297,7 +297,7 @@ describe('Thinking-Enabled Agent Handoff Tests', () => {
297
297
  {
298
298
  from: 'agent_a',
299
299
  to: 'agent_b',
300
- edgeType: 'handoff',
300
+ edgeType: EdgeType.HANDOFF,
301
301
  },
302
302
  ];
303
303
 
@@ -360,7 +360,7 @@ describe('Thinking-Enabled Agent Handoff Tests', () => {
360
360
  {
361
361
  from: 'supervisor',
362
362
  to: 'bedrock_specialist',
363
- edgeType: 'handoff',
363
+ edgeType: EdgeType.HANDOFF,
364
364
  description: 'Transfer to Bedrock specialist',
365
365
  },
366
366
  ];
@@ -450,12 +450,12 @@ describe('Thinking-Enabled Agent Handoff Tests', () => {
450
450
  {
451
451
  from: 'router',
452
452
  to: 'processor',
453
- edgeType: 'handoff',
453
+ edgeType: EdgeType.HANDOFF,
454
454
  },
455
455
  {
456
456
  from: 'processor',
457
457
  to: 'reviewer',
458
- edgeType: 'handoff',
458
+ edgeType: EdgeType.HANDOFF,
459
459
  },
460
460
  ];
461
461
 
@@ -538,7 +538,7 @@ describe('Thinking-Enabled Agent Handoff Tests', () => {
538
538
  {
539
539
  from: 'agent_a',
540
540
  to: 'agent_b',
541
- edgeType: 'handoff',
541
+ edgeType: EdgeType.HANDOFF,
542
542
  },
543
543
  ];
544
544
 
@@ -603,7 +603,7 @@ describe('Thinking-Enabled Agent Handoff Tests', () => {
603
603
  {
604
604
  from: 'agent_a',
605
605
  to: 'agent_b',
606
- edgeType: 'handoff',
606
+ edgeType: EdgeType.HANDOFF,
607
607
  },
608
608
  ];
609
609
 
@@ -94,10 +94,27 @@ export const CodeExecutionToolDescription = `
94
94
  Runs code and returns stdout/stderr output from a stateless execution environment, similar to running scripts in a command-line interface. Each execution is isolated and independent.
95
95
 
96
96
  Usage:
97
- - No network access available.
97
+ - No network access available. Do NOT use pip install, npm install, or any package manager.
98
98
  - Generated files are automatically delivered; **DO NOT** provide download links.
99
99
  - NEVER use this tool to execute malicious code.
100
100
  - When a code_id is returned in output, you can edit that code using code_id + old_str + new_str instead of rewriting the entire code block.
101
+
102
+ Pre-installed Python packages (use directly, no installation needed):
103
+ - Data Science: numpy, pandas
104
+ - Visualization: matplotlib, seaborn, plotly
105
+ - Documents: python-docx, python-pptx, reportlab, fpdf2, PyMuPDF, pdfplumber
106
+ - Spreadsheets: openpyxl, xlsxwriter
107
+ - Image: pillow
108
+ - Data: orjson, lxml, beautifulsoup4, faker
109
+
110
+ Pre-installed JavaScript packages:
111
+ - pptxgenjs, react, react-dom, react-icons, sharp
112
+
113
+ Pre-installed Go packages:
114
+ - excelize (Excel), gofpdf (PDF)
115
+
116
+ Pre-installed R packages:
117
+ - ggplot2, dplyr, tidyr, readxl, writexl, jsonlite, Cairo
101
118
  `.trim();
102
119
 
103
120
  export const CodeExecutionToolName = Constants.EXECUTE_CODE;
@@ -135,11 +152,13 @@ Runs code in a stateless execution environment. Each execution is isolated.
135
152
  ✅ ONLY USE FOR:
136
153
  - File generation: PowerPoint (.pptx), Word (.docx), PDF (.pdf), Excel (.xlsx)
137
154
  - Processing uploaded files (CSV, Excel analysis)
138
- - Heavy computation requiring Python
155
+ - Heavy computation requiring Python (numpy, pandas for data analytics)
139
156
 
140
157
  Rules:
141
- - No network access available
158
+ - No network access — do NOT use pip install, npm install, or any package manager
159
+ - All packages are pre-installed: numpy, pandas, matplotlib, seaborn, plotly, python-docx, python-pptx, reportlab, openpyxl, xlsxwriter, pillow, faker, orjson, lxml, beautifulsoup4
142
160
  - Generated files auto-delivered (no download links needed)
161
+ - **Error recovery**: When execution fails, use \`code_id\` + \`old_str\` + \`new_str\` to fix only the broken part — do NOT rewrite the entire code block. This is faster and saves tokens.
143
162
  `.trim();
144
163
 
145
164
  return tool(
@@ -488,6 +488,73 @@ export interface StructuredOutputInput {
488
488
  strict?: boolean;
489
489
  }
490
490
 
491
+ /**
492
+ * Trigger strategy for when summarization should activate.
493
+ * - 'contextPercentage': Trigger when context utilization exceeds a threshold percentage
494
+ * - 'messageCount': Trigger when pruned message count exceeds a threshold
495
+ * - 'tokenThreshold': Trigger when total token count exceeds a raw threshold
496
+ */
497
+ export type SummarizationTriggerType =
498
+ | 'contextPercentage'
499
+ | 'messageCount'
500
+ | 'tokenThreshold';
501
+
502
+ /**
503
+ * Configuration for summarization behavior within the agent pipeline.
504
+ * All fields are optional — sensible defaults are provided via constants.
505
+ *
506
+ * @see SUMMARIZATION_CONTEXT_THRESHOLD, SUMMARIZATION_RESERVE_RATIO, PRUNING_EMA_ALPHA
507
+ */
508
+ export interface SummarizationConfig {
509
+ /**
510
+ * Strategy for when summarization triggers.
511
+ * @default 'contextPercentage'
512
+ */
513
+ triggerType?: SummarizationTriggerType;
514
+
515
+ /**
516
+ * Threshold value interpreted based on triggerType:
517
+ * - contextPercentage: 0-100 (percentage of context window)
518
+ * - messageCount: absolute count of messages pruned
519
+ * - tokenThreshold: absolute token count
520
+ * @default 80 (for contextPercentage)
521
+ */
522
+ triggerThreshold?: number;
523
+
524
+ /**
525
+ * Fraction of context window (0-1) reserved for recent messages.
526
+ * Prevents over-pruning by ensuring at least this fraction of the
527
+ * context budget is preserved as recent conversation history.
528
+ * @default 0.3
529
+ */
530
+ reserveRatio?: number;
531
+
532
+ /**
533
+ * Whether context pruning is enabled (can be disabled for debugging).
534
+ * @default true
535
+ */
536
+ contextPruning?: boolean;
537
+
538
+ /**
539
+ * Initial summary text to seed across runs.
540
+ * Different from persistedSummary: this is provided by the caller as a
541
+ * cross-conversation seed (e.g., agent personality or recurring context),
542
+ * while persistedSummary is loaded from the conversation's own history.
543
+ */
544
+ initialSummary?: string;
545
+ }
546
+
547
+ /**
548
+ * Runtime state for EMA-based pruning calibration.
549
+ * Maintained across iterations within a single run to smooth pruning decisions.
550
+ */
551
+ export interface PruneCalibrationState {
552
+ /** Current EMA calibration ratio */
553
+ ratio: number;
554
+ /** Number of calibration updates applied */
555
+ iterations: number;
556
+ }
557
+
491
558
  export interface AgentInputs {
492
559
  agentId: string;
493
560
  /** Human-readable name for the agent (used in handoff context). Defaults to agentId if not provided. */
@@ -559,4 +626,10 @@ export interface AgentInputs {
559
626
  * Set by Ranger's SummaryStore when resuming a conversation.
560
627
  */
561
628
  persistedSummary?: string;
629
+ /**
630
+ * Summarization configuration controlling trigger strategy, reserve ratio,
631
+ * and EMA calibration for pruning. When omitted, sensible defaults apply.
632
+ * @see SummarizationConfig
633
+ */
634
+ summarizationConfig?: SummarizationConfig;
562
635
  }
@@ -0,0 +1,148 @@
1
+ // src/utils/__tests__/pruneCalibration.test.ts
2
+ import {
3
+ createPruneCalibration,
4
+ updatePruneCalibration,
5
+ applyCalibration,
6
+ } from '../pruneCalibration';
7
+ import {
8
+ PRUNING_INITIAL_CALIBRATION,
9
+ PRUNING_EMA_ALPHA,
10
+ } from '@/common/constants';
11
+
12
+ describe('pruneCalibration', () => {
13
+ describe('createPruneCalibration', () => {
14
+ it('creates initial state with default ratio', () => {
15
+ const state = createPruneCalibration();
16
+ expect(state.ratio).toBe(PRUNING_INITIAL_CALIBRATION);
17
+ expect(state.iterations).toBe(0);
18
+ });
19
+
20
+ it('accepts custom initial ratio', () => {
21
+ const state = createPruneCalibration(0.85);
22
+ expect(state.ratio).toBe(0.85);
23
+ expect(state.iterations).toBe(0);
24
+ });
25
+ });
26
+
27
+ describe('updatePruneCalibration', () => {
28
+ it('adjusts ratio when actual > estimated (over-counting)', () => {
29
+ const state = createPruneCalibration();
30
+ // Actual: 1000 tokens, estimated: 1500 tokens (our counter over-estimates)
31
+ // observedRatio = 1500/1000 = 1.5
32
+ // newRatio = 0.3 * 1.5 + 0.7 * 1.0 = 0.45 + 0.7 = 1.15
33
+ const updated = updatePruneCalibration(state, 1000, 1500);
34
+ expect(updated.ratio).toBeCloseTo(1.15, 2);
35
+ expect(updated.iterations).toBe(1);
36
+ });
37
+
38
+ it('adjusts ratio when actual < estimated (under-counting)', () => {
39
+ const state = createPruneCalibration();
40
+ // Actual: 2000 tokens, estimated: 1000 tokens (our counter under-estimates)
41
+ // observedRatio = 1000/2000 = 0.5
42
+ // newRatio = 0.3 * 0.5 + 0.7 * 1.0 = 0.15 + 0.7 = 0.85
43
+ const updated = updatePruneCalibration(state, 2000, 1000);
44
+ expect(updated.ratio).toBeCloseTo(0.85, 2);
45
+ expect(updated.iterations).toBe(1);
46
+ });
47
+
48
+ it('converges with consistent readings', () => {
49
+ let state = createPruneCalibration();
50
+
51
+ // Simulate 10 iterations where actual is consistently 1.5x estimated
52
+ for (let i = 0; i < 10; i++) {
53
+ state = updatePruneCalibration(state, 1500, 1000);
54
+ }
55
+
56
+ // Should converge toward ~0.667 (estimated/actual = 1000/1500)
57
+ expect(state.ratio).toBeCloseTo(0.667, 1);
58
+ expect(state.iterations).toBe(10);
59
+ });
60
+
61
+ it('clamps extreme ratios to prevent wild adjustments', () => {
62
+ const state = createPruneCalibration();
63
+
64
+ // Extreme case: estimated 10x actual (should be clamped to 2.0)
65
+ const updated = updatePruneCalibration(state, 100, 10000);
66
+ // Clamped observedRatio = 2.0
67
+ // newRatio = 0.3 * 2.0 + 0.7 * 1.0 = 0.6 + 0.7 = 1.3
68
+ expect(updated.ratio).toBeCloseTo(1.3, 2);
69
+ });
70
+
71
+ it('does not update with invalid inputs', () => {
72
+ const state = createPruneCalibration();
73
+
74
+ expect(updatePruneCalibration(state, 0, 1000)).toBe(state);
75
+ expect(updatePruneCalibration(state, 1000, 0)).toBe(state);
76
+ expect(updatePruneCalibration(state, -1, 1000)).toBe(state);
77
+ });
78
+
79
+ it('does not mutate input state', () => {
80
+ const state = createPruneCalibration();
81
+ const original = { ...state };
82
+
83
+ updatePruneCalibration(state, 1000, 1500);
84
+ expect(state).toEqual(original);
85
+ });
86
+
87
+ it('accepts custom alpha', () => {
88
+ const state = createPruneCalibration();
89
+ // With alpha=1.0, fully adapts to new reading
90
+ const updated = updatePruneCalibration(state, 1000, 1500, 1.0);
91
+ // observedRatio = 1.5, clamped to 1.5
92
+ // newRatio = 1.0 * 1.5 + 0.0 * 1.0 = 1.5
93
+ expect(updated.ratio).toBeCloseTo(1.5, 2);
94
+ });
95
+ });
96
+
97
+ describe('applyCalibration', () => {
98
+ it('returns raw budget when no iterations have occurred', () => {
99
+ const state = createPruneCalibration();
100
+ expect(applyCalibration(10000, state)).toBe(10000);
101
+ });
102
+
103
+ it('adjusts budget after calibration', () => {
104
+ let state = createPruneCalibration();
105
+ state = updatePruneCalibration(state, 1000, 1500);
106
+ // ratio ≈ 1.15, so budget is increased (our counter over-estimates)
107
+ const adjusted = applyCalibration(10000, state);
108
+ expect(adjusted).toBeCloseTo(11500, -2);
109
+ });
110
+
111
+ it('decreases budget when under-counting', () => {
112
+ let state = createPruneCalibration();
113
+ state = updatePruneCalibration(state, 2000, 1000);
114
+ // ratio ≈ 0.85, so budget is decreased (our counter under-estimates)
115
+ const adjusted = applyCalibration(10000, state);
116
+ expect(adjusted).toBeLessThan(10000);
117
+ });
118
+
119
+ it('returns floor of the adjusted value', () => {
120
+ let state = createPruneCalibration();
121
+ state = updatePruneCalibration(state, 1000, 1500);
122
+ const adjusted = applyCalibration(10001, state);
123
+ expect(Number.isInteger(adjusted)).toBe(true);
124
+ });
125
+ });
126
+
127
+ describe('multi-iteration convergence', () => {
128
+ it('smoothly transitions when accuracy changes', () => {
129
+ let state = createPruneCalibration();
130
+
131
+ // First 5 iterations: estimated is 1.5x actual
132
+ for (let i = 0; i < 5; i++) {
133
+ state = updatePruneCalibration(state, 1000, 1500);
134
+ }
135
+ const ratio5 = state.ratio;
136
+
137
+ // Next 5 iterations: estimated matches actual
138
+ for (let i = 0; i < 5; i++) {
139
+ state = updatePruneCalibration(state, 1000, 1000);
140
+ }
141
+ const ratio10 = state.ratio;
142
+
143
+ // Ratio should move toward 1.0 but still carry some history
144
+ expect(ratio10).toBeLessThan(ratio5);
145
+ expect(ratio10).toBeGreaterThan(0.9);
146
+ });
147
+ });
148
+ });