@compilr-dev/agents 0.3.25 → 0.3.27

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 (3) hide show
  1. package/dist/agent.d.ts +71 -74
  2. package/dist/agent.js +147 -118
  3. package/package.json +1 -1
package/dist/agent.d.ts CHANGED
@@ -256,6 +256,27 @@ export interface AgentConfig {
256
256
  maxIterations: number;
257
257
  toolCallCount: number;
258
258
  }) => Promise<number | false>;
259
+ /**
260
+ * Callback when tool loop is detected (same tool called N times with identical input).
261
+ *
262
+ * When provided, the agent asks the user instead of throwing ToolLoopError.
263
+ * Return `true` to continue (reset the counter), `false` to stop.
264
+ *
265
+ * When not provided, ToolLoopError is thrown (backwards compatible).
266
+ *
267
+ * @example
268
+ * ```typescript
269
+ * onToolLoopDetected: async ({ toolName, consecutiveCalls }) => {
270
+ * const answer = await askUser(`${toolName} called ${consecutiveCalls} times. Continue?`);
271
+ * return answer === 'yes';
272
+ * }
273
+ * ```
274
+ */
275
+ onToolLoopDetected?: (context: {
276
+ toolName: string;
277
+ consecutiveCalls: number;
278
+ input: Record<string, unknown>;
279
+ }) => Promise<boolean>;
259
280
  /**
260
281
  * Chat options (model, temperature, etc.)
261
282
  */
@@ -348,9 +369,9 @@ export interface AgentConfig {
348
369
  */
349
370
  checkpointOnAbort?: boolean;
350
371
  /**
351
- * Anchor manager options for critical information that survives context compaction.
372
+ * Pin manager options for critical information that survives context compaction.
352
373
  *
353
- * Anchors are pieces of information that:
374
+ * Pins are pieces of information that:
354
375
  * - Never get compacted - Survive all context management
355
376
  * - Always re-injected - Present in every LLM call
356
377
  * - Scoped - Session, persistent, or temporary
@@ -359,7 +380,7 @@ export interface AgentConfig {
359
380
  * ```typescript
360
381
  * const agent = new Agent({
361
382
  * provider,
362
- * anchors: {
383
+ * pins: {
363
384
  * maxAnchors: 20,
364
385
  * maxTokens: 2000,
365
386
  * persistPath: '~/.myapp/anchors.json',
@@ -368,6 +389,8 @@ export interface AgentConfig {
368
389
  * });
369
390
  * ```
370
391
  */
392
+ pins?: AnchorManagerOptions;
393
+ /** @deprecated Use `pins` instead */
371
394
  anchors?: AnchorManagerOptions;
372
395
  /**
373
396
  * Guardrail options for pattern-based safety checks.
@@ -848,6 +871,7 @@ export declare class Agent {
848
871
  private readonly autoContextManagement;
849
872
  private readonly onEvent?;
850
873
  private readonly onIterationLimitReached?;
874
+ private readonly onToolLoopDetected?;
851
875
  private readonly retryConfig;
852
876
  private readonly checkpointer?;
853
877
  private readonly _sessionId;
@@ -865,9 +889,9 @@ export declare class Agent {
865
889
  */
866
890
  private readonly subAgents;
867
891
  /**
868
- * Anchor manager for critical information that survives context compaction
892
+ * Pin manager for critical information that survives context compaction
869
893
  */
870
- private readonly anchorManager?;
894
+ private readonly pinManager?;
871
895
  /**
872
896
  * Guardrail manager for pattern-based safety checks
873
897
  */
@@ -1029,99 +1053,72 @@ export declare class Agent {
1029
1053
  */
1030
1054
  recordUsage(model: string, provider: string, tokens: TokenUsage): void;
1031
1055
  /**
1032
- * Add an anchor (critical information that survives context compaction).
1056
+ * Add a pin (critical information that survives context compaction).
1033
1057
  *
1034
- * Anchors are injected into every LLM call and never get compacted.
1058
+ * Pins are injected into every LLM call and never get compacted.
1035
1059
  * Use them for information that must not be forgotten.
1036
1060
  *
1037
- * @param input - Anchor input
1038
- * @returns The created anchor, or undefined if anchors are not enabled
1061
+ * @param input - Pin input (uses AnchorInput type)
1062
+ * @returns The created pin, or undefined if pins are not enabled
1039
1063
  *
1040
1064
  * @example
1041
1065
  * ```typescript
1042
- * // Session anchor - lives for this session only
1043
- * agent.addAnchor({
1044
- * content: 'This session we implemented: edit tool, grep tool',
1045
- * priority: 'critical',
1046
- * scope: 'session',
1047
- * });
1048
- *
1049
- * // Safety anchor - persisted across sessions
1050
- * agent.addAnchor({
1051
- * content: 'Never delete files in /important without confirmation',
1052
- * priority: 'safety',
1053
- * scope: 'persistent',
1054
- * });
1055
- *
1056
- * // Temporary anchor - expires after 1 hour
1057
- * agent.addAnchor({
1058
- * content: 'Currently working on feature X',
1066
+ * agent.addPin({
1067
+ * content: 'Team roster: $default, $arch',
1059
1068
  * priority: 'info',
1060
- * scope: 'temporary',
1061
- * expiresAt: new Date(Date.now() + 60 * 60 * 1000),
1069
+ * scope: 'session',
1062
1070
  * });
1063
1071
  * ```
1064
1072
  */
1065
- addAnchor(input: AnchorInput): Anchor | undefined;
1073
+ addPin(input: AnchorInput): Anchor | undefined;
1066
1074
  /**
1067
- * Get an anchor by ID
1075
+ * Get a pin by ID
1068
1076
  */
1069
- getAnchor(id: string): Anchor | undefined;
1077
+ getPin(id: string): Anchor | undefined;
1070
1078
  /**
1071
- * Get all anchors, optionally filtered
1072
- *
1073
- * @param options - Query options for filtering
1074
- * @returns Array of anchors
1075
- *
1076
- * @example
1077
- * ```typescript
1078
- * // Get all anchors
1079
- * const all = agent.getAnchors();
1080
- *
1081
- * // Get only safety anchors
1082
- * const safety = agent.getAnchors({ priority: 'safety' });
1083
- *
1084
- * // Get session anchors with specific tags
1085
- * const tagged = agent.getAnchors({ scope: 'session', tags: ['files'] });
1086
- * ```
1079
+ * Get all pins, optionally filtered
1087
1080
  */
1088
- getAnchors(options?: AnchorQueryOptions): Anchor[];
1081
+ getPins(options?: AnchorQueryOptions): Anchor[];
1089
1082
  /**
1090
- * Check if an anchor exists
1083
+ * Check if a pin exists
1091
1084
  */
1092
- hasAnchor(id: string): boolean;
1085
+ hasPin(id: string): boolean;
1093
1086
  /**
1094
- * Remove an anchor by ID
1087
+ * Remove a pin by ID
1095
1088
  *
1096
- * @returns true if anchor was removed, false if not found
1089
+ * @returns true if pin was removed, false if not found
1097
1090
  */
1098
- removeAnchor(id: string): boolean;
1091
+ removePin(id: string): boolean;
1099
1092
  /**
1100
- * Clear anchors based on criteria
1101
- *
1102
- * @param options - Clear options for filtering which anchors to remove
1103
- * @returns Number of anchors removed
1104
- *
1105
- * @example
1106
- * ```typescript
1107
- * // Clear all session anchors
1108
- * agent.clearAnchors({ scope: 'session' });
1109
- *
1110
- * // Clear expired temporary anchors
1111
- * agent.clearAnchors({ expiredOnly: true });
1093
+ * Clear pins based on criteria
1112
1094
  *
1113
- * // Clear anchors with specific tags
1114
- * agent.clearAnchors({ tags: ['auto'] });
1115
- * ```
1095
+ * @param options - Clear options for filtering which pins to remove
1096
+ * @returns Number of pins removed
1116
1097
  */
1117
- clearAnchors(options?: AnchorClearOptions): number;
1098
+ clearPins(options?: AnchorClearOptions): number;
1118
1099
  /**
1119
- * Get the anchor manager (if configured)
1100
+ * Get the pin manager (if configured)
1120
1101
  */
1121
- getAnchorManager(): AnchorManager | undefined;
1102
+ getPinManager(): AnchorManager | undefined;
1122
1103
  /**
1123
- * Check if anchors are enabled
1104
+ * Check if pins are enabled
1124
1105
  */
1106
+ hasPins(): boolean;
1107
+ /** @deprecated Use addPin() instead */
1108
+ addAnchor(input: AnchorInput): Anchor | undefined;
1109
+ /** @deprecated Use getPin() instead */
1110
+ getAnchor(id: string): Anchor | undefined;
1111
+ /** @deprecated Use getPins() instead */
1112
+ getAnchors(options?: AnchorQueryOptions): Anchor[];
1113
+ /** @deprecated Use hasPin() instead */
1114
+ hasAnchor(id: string): boolean;
1115
+ /** @deprecated Use removePin() instead */
1116
+ removeAnchor(id: string): boolean;
1117
+ /** @deprecated Use clearPins() instead */
1118
+ clearAnchors(options?: AnchorClearOptions): number;
1119
+ /** @deprecated Use getPinManager() instead */
1120
+ getAnchorManager(): AnchorManager | undefined;
1121
+ /** @deprecated Use hasPins() instead */
1125
1122
  hasAnchors(): boolean;
1126
1123
  /**
1127
1124
  * Add a custom guardrail
@@ -1657,8 +1654,8 @@ export declare class Agent {
1657
1654
  * Generate a summary of messages using the LLM provider.
1658
1655
  * Used for context summarization when approaching limits.
1659
1656
  *
1660
- * If anchors are configured, the summary will prioritize keeping
1661
- * information related to anchor content (anchor-weighted summarization).
1657
+ * If pins are configured, the summary will prioritize keeping
1658
+ * information related to pinned content (pin-weighted summarization).
1662
1659
  */
1663
1660
  private generateSummary;
1664
1661
  /**
package/dist/agent.js CHANGED
@@ -50,6 +50,7 @@ export class Agent {
50
50
  autoContextManagement;
51
51
  onEvent;
52
52
  onIterationLimitReached;
53
+ onToolLoopDetected;
53
54
  // Retry configuration
54
55
  retryConfig;
55
56
  // State management
@@ -69,9 +70,9 @@ export class Agent {
69
70
  */
70
71
  subAgents = new Map();
71
72
  /**
72
- * Anchor manager for critical information that survives context compaction
73
+ * Pin manager for critical information that survives context compaction
73
74
  */
74
- anchorManager;
75
+ pinManager;
75
76
  /**
76
77
  * Guardrail manager for pattern-based safety checks
77
78
  */
@@ -139,15 +140,18 @@ export class Agent {
139
140
  }
140
141
  this.onEvent = config.onEvent;
141
142
  this.onIterationLimitReached = config.onIterationLimitReached;
143
+ this.onToolLoopDetected = config.onToolLoopDetected;
142
144
  // State management
143
145
  this.checkpointer = config.checkpointer;
144
146
  this._sessionId = config.sessionId ?? generateSessionId();
145
147
  this.autoCheckpoint = config.autoCheckpoint ?? false;
146
148
  this.checkpointOnAbort = config.checkpointOnAbort ?? false;
147
149
  this._createdAt = new Date().toISOString();
148
- // Anchor management
149
- if (config.anchors !== undefined) {
150
- this.anchorManager = new AnchorManager(config.anchors);
150
+ // Pin management (backwards compat: accept `anchors` as alias for `pins`)
151
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
152
+ const pinConfig = config.pins ?? config.anchors;
153
+ if (pinConfig !== undefined) {
154
+ this.pinManager = new AnchorManager(pinConfig);
151
155
  }
152
156
  // Guardrail management
153
157
  if (config.guardrails !== undefined) {
@@ -427,128 +431,118 @@ export class Agent {
427
431
  this.usageTracker?.record({ model, provider, tokens });
428
432
  }
429
433
  // ==========================================================================
430
- // Anchor Management - Critical information that survives context compaction
434
+ // Pin Management - Critical information that survives context compaction
431
435
  // ==========================================================================
432
436
  /**
433
- * Add an anchor (critical information that survives context compaction).
437
+ * Add a pin (critical information that survives context compaction).
434
438
  *
435
- * Anchors are injected into every LLM call and never get compacted.
439
+ * Pins are injected into every LLM call and never get compacted.
436
440
  * Use them for information that must not be forgotten.
437
441
  *
438
- * @param input - Anchor input
439
- * @returns The created anchor, or undefined if anchors are not enabled
442
+ * @param input - Pin input (uses AnchorInput type)
443
+ * @returns The created pin, or undefined if pins are not enabled
440
444
  *
441
445
  * @example
442
446
  * ```typescript
443
- * // Session anchor - lives for this session only
444
- * agent.addAnchor({
445
- * content: 'This session we implemented: edit tool, grep tool',
446
- * priority: 'critical',
447
- * scope: 'session',
448
- * });
449
- *
450
- * // Safety anchor - persisted across sessions
451
- * agent.addAnchor({
452
- * content: 'Never delete files in /important without confirmation',
453
- * priority: 'safety',
454
- * scope: 'persistent',
455
- * });
456
- *
457
- * // Temporary anchor - expires after 1 hour
458
- * agent.addAnchor({
459
- * content: 'Currently working on feature X',
447
+ * agent.addPin({
448
+ * content: 'Team roster: $default, $arch',
460
449
  * priority: 'info',
461
- * scope: 'temporary',
462
- * expiresAt: new Date(Date.now() + 60 * 60 * 1000),
450
+ * scope: 'session',
463
451
  * });
464
452
  * ```
465
453
  */
466
- addAnchor(input) {
467
- if (!this.anchorManager)
454
+ addPin(input) {
455
+ if (!this.pinManager)
468
456
  return undefined;
469
- const anchor = this.anchorManager.add(input);
457
+ const anchor = this.pinManager.add(input);
470
458
  this.onEvent?.({ type: 'anchor_added', anchor });
471
459
  return anchor;
472
460
  }
473
461
  /**
474
- * Get an anchor by ID
462
+ * Get a pin by ID
475
463
  */
476
- getAnchor(id) {
477
- return this.anchorManager?.get(id);
464
+ getPin(id) {
465
+ return this.pinManager?.get(id);
478
466
  }
479
467
  /**
480
- * Get all anchors, optionally filtered
481
- *
482
- * @param options - Query options for filtering
483
- * @returns Array of anchors
484
- *
485
- * @example
486
- * ```typescript
487
- * // Get all anchors
488
- * const all = agent.getAnchors();
489
- *
490
- * // Get only safety anchors
491
- * const safety = agent.getAnchors({ priority: 'safety' });
492
- *
493
- * // Get session anchors with specific tags
494
- * const tagged = agent.getAnchors({ scope: 'session', tags: ['files'] });
495
- * ```
468
+ * Get all pins, optionally filtered
496
469
  */
497
- getAnchors(options) {
498
- return this.anchorManager?.getAll(options) ?? [];
470
+ getPins(options) {
471
+ return this.pinManager?.getAll(options) ?? [];
499
472
  }
500
473
  /**
501
- * Check if an anchor exists
474
+ * Check if a pin exists
502
475
  */
503
- hasAnchor(id) {
504
- return this.anchorManager?.has(id) ?? false;
476
+ hasPin(id) {
477
+ return this.pinManager?.has(id) ?? false;
505
478
  }
506
479
  /**
507
- * Remove an anchor by ID
480
+ * Remove a pin by ID
508
481
  *
509
- * @returns true if anchor was removed, false if not found
482
+ * @returns true if pin was removed, false if not found
510
483
  */
511
- removeAnchor(id) {
512
- if (!this.anchorManager)
484
+ removePin(id) {
485
+ if (!this.pinManager)
513
486
  return false;
514
- const removed = this.anchorManager.remove(id);
487
+ const removed = this.pinManager.remove(id);
515
488
  if (removed) {
516
489
  this.onEvent?.({ type: 'anchor_removed', anchorId: id });
517
490
  }
518
491
  return removed;
519
492
  }
520
493
  /**
521
- * Clear anchors based on criteria
522
- *
523
- * @param options - Clear options for filtering which anchors to remove
524
- * @returns Number of anchors removed
525
- *
526
- * @example
527
- * ```typescript
528
- * // Clear all session anchors
529
- * agent.clearAnchors({ scope: 'session' });
494
+ * Clear pins based on criteria
530
495
  *
531
- * // Clear expired temporary anchors
532
- * agent.clearAnchors({ expiredOnly: true });
533
- *
534
- * // Clear anchors with specific tags
535
- * agent.clearAnchors({ tags: ['auto'] });
536
- * ```
496
+ * @param options - Clear options for filtering which pins to remove
497
+ * @returns Number of pins removed
537
498
  */
538
- clearAnchors(options) {
539
- return this.anchorManager?.clear(options) ?? 0;
499
+ clearPins(options) {
500
+ return this.pinManager?.clear(options) ?? 0;
540
501
  }
541
502
  /**
542
- * Get the anchor manager (if configured)
503
+ * Get the pin manager (if configured)
543
504
  */
544
- getAnchorManager() {
545
- return this.anchorManager;
505
+ getPinManager() {
506
+ return this.pinManager;
546
507
  }
547
508
  /**
548
- * Check if anchors are enabled
509
+ * Check if pins are enabled
549
510
  */
511
+ hasPins() {
512
+ return this.pinManager !== undefined;
513
+ }
514
+ // --- Deprecated aliases (use pin methods instead) --------------------------
515
+ /** @deprecated Use addPin() instead */
516
+ addAnchor(input) {
517
+ return this.addPin(input);
518
+ }
519
+ /** @deprecated Use getPin() instead */
520
+ getAnchor(id) {
521
+ return this.getPin(id);
522
+ }
523
+ /** @deprecated Use getPins() instead */
524
+ getAnchors(options) {
525
+ return this.getPins(options);
526
+ }
527
+ /** @deprecated Use hasPin() instead */
528
+ hasAnchor(id) {
529
+ return this.hasPin(id);
530
+ }
531
+ /** @deprecated Use removePin() instead */
532
+ removeAnchor(id) {
533
+ return this.removePin(id);
534
+ }
535
+ /** @deprecated Use clearPins() instead */
536
+ clearAnchors(options) {
537
+ return this.clearPins(options);
538
+ }
539
+ /** @deprecated Use getPinManager() instead */
540
+ getAnchorManager() {
541
+ return this.getPinManager();
542
+ }
543
+ /** @deprecated Use hasPins() instead */
550
544
  hasAnchors() {
551
- return this.anchorManager !== undefined;
545
+ return this.hasPins();
552
546
  }
553
547
  // ==========================================================================
554
548
  // Guardrail Management - Pattern-based safety checks
@@ -1535,25 +1529,25 @@ export class Agent {
1535
1529
  // Build messages: system prompt + history + new user message
1536
1530
  let messages = [];
1537
1531
  // Add system message if present
1538
- // NOTE: Anchors are now appended to the system prompt (not a separate message)
1532
+ // NOTE: Pins are appended to the system prompt (not a separate message)
1539
1533
  // to avoid multiple system messages which can confuse Claude's role identity
1540
1534
  let systemContent = this.systemPrompt;
1541
- // Inject anchors into the system prompt (not as separate message)
1542
- if (this.anchorManager && this.anchorManager.size > 0) {
1543
- const anchorsContent = this.anchorManager.format();
1544
- if (anchorsContent) {
1545
- // Insert anchors BEFORE the role ending (if present) so role reinforcement stays at the end
1535
+ // Inject pins into the system prompt (not as separate message)
1536
+ if (this.pinManager && this.pinManager.size > 0) {
1537
+ const pinsContent = this.pinManager.format();
1538
+ if (pinsContent) {
1539
+ // Insert pins BEFORE the role ending (if present) so role reinforcement stays at the end
1546
1540
  const roleEndingMarker = '## YOUR ASSIGNED ROLE:';
1547
1541
  const roleEndingIndex = systemContent.indexOf(roleEndingMarker);
1548
1542
  if (roleEndingIndex > 0) {
1549
- // Insert anchors before the role ending
1543
+ // Insert pins before the role ending
1550
1544
  const beforeRole = systemContent.substring(0, roleEndingIndex);
1551
1545
  const roleEnding = systemContent.substring(roleEndingIndex);
1552
- systemContent = `${beforeRole}\n\n---\n\n## Active Anchors (Critical Information)\n\n${anchorsContent}\n\n---\n\n${roleEnding}`;
1546
+ systemContent = `${beforeRole}\n\n---\n\n## Pinned Context (Critical Information)\n\n${pinsContent}\n\n---\n\n${roleEnding}`;
1553
1547
  }
1554
1548
  else {
1555
- // No role ending, just append anchors
1556
- systemContent = `${systemContent}\n\n---\n\n## Active Anchors (Critical Information)\n\n${anchorsContent}`;
1549
+ // No role ending, just append pins
1550
+ systemContent = `${systemContent}\n\n---\n\n## Pinned Context (Critical Information)\n\n${pinsContent}`;
1557
1551
  }
1558
1552
  }
1559
1553
  }
@@ -2099,13 +2093,36 @@ export class Agent {
2099
2093
  aborted = true;
2100
2094
  break;
2101
2095
  }
2096
+ const toolCallEntry = { name: toolUse.name, input: toolUse.input, result };
2097
+ toolCalls.push(toolCallEntry);
2098
+ iterationToolCalls.push(toolCallEntry);
2099
+ // Always push the tool_result BEFORE loop detection
2100
+ // so the conversation history stays valid if we throw
2101
+ messages.push(toolResultMsg);
2102
+ newMessages.push(toolResultMsg);
2102
2103
  // Tool loop detection (still applies per-tool)
2103
2104
  if (this.maxConsecutiveToolCalls > 0) {
2104
2105
  const currentHash = hashToolCall(toolUse.name, toolUse.input);
2105
2106
  if (currentHash === lastToolCallHash) {
2106
2107
  consecutiveIdenticalCalls++;
2107
2108
  if (consecutiveIdenticalCalls >= this.maxConsecutiveToolCalls) {
2108
- throw new ToolLoopError(toolUse.name, consecutiveIdenticalCalls, toolUse.input);
2109
+ if (this.onToolLoopDetected) {
2110
+ // Ask user: continue or stop?
2111
+ const shouldContinue = await this.onToolLoopDetected({
2112
+ toolName: toolUse.name,
2113
+ consecutiveCalls: consecutiveIdenticalCalls,
2114
+ input: toolUse.input,
2115
+ });
2116
+ if (shouldContinue) {
2117
+ consecutiveIdenticalCalls = 0; // Reset counter
2118
+ }
2119
+ else {
2120
+ throw new ToolLoopError(toolUse.name, consecutiveIdenticalCalls, toolUse.input);
2121
+ }
2122
+ }
2123
+ else {
2124
+ throw new ToolLoopError(toolUse.name, consecutiveIdenticalCalls, toolUse.input);
2125
+ }
2109
2126
  }
2110
2127
  emit({
2111
2128
  type: 'tool_loop_warning',
@@ -2118,11 +2135,6 @@ export class Agent {
2118
2135
  consecutiveIdenticalCalls = 1;
2119
2136
  }
2120
2137
  }
2121
- const toolCallEntry = { name: toolUse.name, input: toolUse.input, result };
2122
- toolCalls.push(toolCallEntry);
2123
- iterationToolCalls.push(toolCallEntry);
2124
- messages.push(toolResultMsg);
2125
- newMessages.push(toolResultMsg);
2126
2138
  // Stamp for observation masking
2127
2139
  if (this.observationMasker) {
2128
2140
  const block = toolResultMsg.content[0];
@@ -2142,13 +2154,35 @@ export class Agent {
2142
2154
  aborted = true;
2143
2155
  break;
2144
2156
  }
2157
+ const toolCallEntry = { name: toolUse.name, input: toolUse.input, result };
2158
+ toolCalls.push(toolCallEntry);
2159
+ iterationToolCalls.push(toolCallEntry);
2160
+ // Always push the tool_result BEFORE loop detection
2161
+ // so the conversation history stays valid if we throw
2162
+ messages.push(toolResultMsg);
2163
+ newMessages.push(toolResultMsg);
2145
2164
  // Tool loop detection
2146
2165
  if (this.maxConsecutiveToolCalls > 0) {
2147
2166
  const currentHash = hashToolCall(toolUse.name, toolUse.input);
2148
2167
  if (currentHash === lastToolCallHash) {
2149
2168
  consecutiveIdenticalCalls++;
2150
2169
  if (consecutiveIdenticalCalls >= this.maxConsecutiveToolCalls) {
2151
- throw new ToolLoopError(toolUse.name, consecutiveIdenticalCalls, toolUse.input);
2170
+ if (this.onToolLoopDetected) {
2171
+ const shouldContinue = await this.onToolLoopDetected({
2172
+ toolName: toolUse.name,
2173
+ consecutiveCalls: consecutiveIdenticalCalls,
2174
+ input: toolUse.input,
2175
+ });
2176
+ if (shouldContinue) {
2177
+ consecutiveIdenticalCalls = 0;
2178
+ }
2179
+ else {
2180
+ throw new ToolLoopError(toolUse.name, consecutiveIdenticalCalls, toolUse.input);
2181
+ }
2182
+ }
2183
+ else {
2184
+ throw new ToolLoopError(toolUse.name, consecutiveIdenticalCalls, toolUse.input);
2185
+ }
2152
2186
  }
2153
2187
  emit({
2154
2188
  type: 'tool_loop_warning',
@@ -2161,11 +2195,6 @@ export class Agent {
2161
2195
  consecutiveIdenticalCalls = 1;
2162
2196
  }
2163
2197
  }
2164
- const toolCallEntry = { name: toolUse.name, input: toolUse.input, result };
2165
- toolCalls.push(toolCallEntry);
2166
- iterationToolCalls.push(toolCallEntry);
2167
- messages.push(toolResultMsg);
2168
- newMessages.push(toolResultMsg);
2169
2198
  // Stamp for observation masking
2170
2199
  if (this.observationMasker) {
2171
2200
  const block = toolResultMsg.content[0];
@@ -2498,25 +2527,25 @@ export class Agent {
2498
2527
  * Generate a summary of messages using the LLM provider.
2499
2528
  * Used for context summarization when approaching limits.
2500
2529
  *
2501
- * If anchors are configured, the summary will prioritize keeping
2502
- * information related to anchor content (anchor-weighted summarization).
2530
+ * If pins are configured, the summary will prioritize keeping
2531
+ * information related to pinned content (pin-weighted summarization).
2503
2532
  */
2504
2533
  async generateSummary(messages) {
2505
- // Build anchor context if available (for weighted summarization)
2534
+ // Build pin context if available (for weighted summarization)
2506
2535
  let anchorContext = '';
2507
- if (this.anchorManager && this.anchorManager.size > 0) {
2508
- const anchors = this.anchorManager.getAll();
2509
- if (anchors.length > 0) {
2510
- const anchorItems = anchors.map((a) => `- [${a.priority}] ${a.content}`).join('\n');
2536
+ if (this.pinManager && this.pinManager.size > 0) {
2537
+ const pins = this.pinManager.getAll();
2538
+ if (pins.length > 0) {
2539
+ const pinItems = pins.map((a) => `- [${a.priority}] ${a.content}`).join('\n');
2511
2540
  anchorContext = `
2512
- ## PRIORITY CONTEXT (Anchors)
2541
+ ## PRIORITY CONTEXT (Pinned)
2513
2542
 
2514
- The following are critical anchors that should be preserved in context. When summarizing,
2515
- PRIORITIZE keeping information that relates to these anchors:
2543
+ The following are pinned items that should be preserved in context. When summarizing,
2544
+ PRIORITIZE keeping information that relates to these pins:
2516
2545
 
2517
- ${anchorItems}
2546
+ ${pinItems}
2518
2547
 
2519
- Ensure the summary captures any conversation details related to these anchors.
2548
+ Ensure the summary captures any conversation details related to these pinned items.
2520
2549
  `;
2521
2550
  }
2522
2551
  }
@@ -2525,7 +2554,7 @@ Ensure the summary captures any conversation details related to these anchors.
2525
2554
  2. Important context established
2526
2555
  3. Tasks completed or in progress
2527
2556
  4. Any relevant file paths or code snippets mentioned
2528
- ${anchorContext ? '5. Information related to the priority anchors listed below' : ''}
2557
+ ${anchorContext ? '5. Information related to the pinned priority items below' : ''}
2529
2558
  ${anchorContext}
2530
2559
  Keep the summary under 500 words.
2531
2560
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@compilr-dev/agents",
3
- "version": "0.3.25",
3
+ "version": "0.3.27",
4
4
  "description": "Lightweight multi-LLM agent library for building CLI AI assistants",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",