@n8n/agents 0.8.1 → 0.9.1

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 (103) hide show
  1. package/dist/build.tsbuildinfo +1 -1
  2. package/dist/index.d.ts +10 -2
  3. package/dist/index.js +57 -4
  4. package/dist/index.js.map +1 -1
  5. package/dist/integrations/langsmith.js +28 -2
  6. package/dist/integrations/langsmith.js.map +1 -1
  7. package/dist/runtime/agent-runtime.d.ts +12 -1
  8. package/dist/runtime/agent-runtime.js +243 -119
  9. package/dist/runtime/agent-runtime.js.map +1 -1
  10. package/dist/runtime/episodic-memory-defaults.d.ts +17 -0
  11. package/dist/runtime/episodic-memory-defaults.js +615 -0
  12. package/dist/runtime/episodic-memory-defaults.js.map +1 -0
  13. package/dist/runtime/episodic-memory.d.ts +45 -0
  14. package/dist/runtime/episodic-memory.js +479 -0
  15. package/dist/runtime/episodic-memory.js.map +1 -0
  16. package/dist/runtime/execution-counter.d.ts +9 -0
  17. package/dist/runtime/execution-counter.js +16 -0
  18. package/dist/runtime/execution-counter.js.map +1 -0
  19. package/dist/runtime/fix-tool-call.d.ts +7 -0
  20. package/dist/runtime/fix-tool-call.js +38 -0
  21. package/dist/runtime/fix-tool-call.js.map +1 -0
  22. package/dist/runtime/lazy-ai.d.ts +2 -0
  23. package/dist/runtime/lazy-ai.js +12 -0
  24. package/dist/runtime/lazy-ai.js.map +1 -0
  25. package/dist/runtime/mcp-connection.d.ts +1 -1
  26. package/dist/runtime/mcp-connection.js +71 -16
  27. package/dist/runtime/mcp-connection.js.map +1 -1
  28. package/dist/runtime/memory-lifecycle.d.ts +34 -0
  29. package/dist/runtime/memory-lifecycle.js +63 -0
  30. package/dist/runtime/memory-lifecycle.js.map +1 -0
  31. package/dist/runtime/memory-store.d.ts +22 -7
  32. package/dist/runtime/memory-store.js +240 -50
  33. package/dist/runtime/memory-store.js.map +1 -1
  34. package/dist/runtime/message-list.d.ts +1 -0
  35. package/dist/runtime/message-list.js +5 -0
  36. package/dist/runtime/message-list.js.map +1 -1
  37. package/dist/runtime/messages.js +14 -1
  38. package/dist/runtime/messages.js.map +1 -1
  39. package/dist/runtime/model-factory.d.ts +5 -1
  40. package/dist/runtime/model-factory.js +3 -2
  41. package/dist/runtime/model-factory.js.map +1 -1
  42. package/dist/runtime/observation-log-defaults.js +6 -5
  43. package/dist/runtime/observation-log-defaults.js.map +1 -1
  44. package/dist/runtime/observation-log-observer.d.ts +7 -13
  45. package/dist/runtime/observation-log-observer.js +11 -21
  46. package/dist/runtime/observation-log-observer.js.map +1 -1
  47. package/dist/runtime/observation-log-reflector.d.ts +5 -5
  48. package/dist/runtime/observation-log-reflector.js +9 -21
  49. package/dist/runtime/observation-log-reflector.js.map +1 -1
  50. package/dist/runtime/scoped-memory-task-runner.d.ts +2 -4
  51. package/dist/runtime/scoped-memory-task-runner.js +3 -7
  52. package/dist/runtime/scoped-memory-task-runner.js.map +1 -1
  53. package/dist/runtime/stream.js +11 -1
  54. package/dist/runtime/stream.js.map +1 -1
  55. package/dist/runtime/title-generation.d.ts +5 -2
  56. package/dist/runtime/title-generation.js +7 -3
  57. package/dist/runtime/title-generation.js.map +1 -1
  58. package/dist/runtime/tool-adapter.d.ts +1 -1
  59. package/dist/runtime/tool-adapter.js +5 -4
  60. package/dist/runtime/tool-adapter.js.map +1 -1
  61. package/dist/sdk/agent.d.ts +10 -0
  62. package/dist/sdk/agent.js +77 -11
  63. package/dist/sdk/agent.js.map +1 -1
  64. package/dist/sdk/memory.d.ts +6 -2
  65. package/dist/sdk/memory.js +57 -3
  66. package/dist/sdk/memory.js.map +1 -1
  67. package/dist/sdk/telemetry.d.ts +1 -0
  68. package/dist/sdk/telemetry.js +7 -0
  69. package/dist/sdk/telemetry.js.map +1 -1
  70. package/dist/skills/index.d.ts +6 -0
  71. package/dist/skills/index.js +29 -0
  72. package/dist/skills/index.js.map +1 -0
  73. package/dist/skills/prompt.d.ts +6 -0
  74. package/dist/skills/prompt.js +49 -0
  75. package/dist/skills/prompt.js.map +1 -0
  76. package/dist/skills/registry.d.ts +14 -0
  77. package/dist/skills/registry.js +380 -0
  78. package/dist/skills/registry.js.map +1 -0
  79. package/dist/skills/tools.d.ts +6 -0
  80. package/dist/skills/tools.js +312 -0
  81. package/dist/skills/tools.js.map +1 -0
  82. package/dist/skills/types.d.ts +141 -0
  83. package/dist/skills/types.js +15 -0
  84. package/dist/skills/types.js.map +1 -0
  85. package/dist/skills/validator.d.ts +15 -0
  86. package/dist/skills/validator.js +336 -0
  87. package/dist/skills/validator.js.map +1 -0
  88. package/dist/types/index.d.ts +4 -4
  89. package/dist/types/index.js +1 -3
  90. package/dist/types/index.js.map +1 -1
  91. package/dist/types/runtime/event.d.ts +1 -1
  92. package/dist/types/sdk/agent-builder.d.ts +4 -1
  93. package/dist/types/sdk/agent.d.ts +2 -1
  94. package/dist/types/sdk/memory.d.ts +153 -2
  95. package/dist/types/sdk/observation-log.d.ts +7 -10
  96. package/dist/types/sdk/observation-log.js +0 -9
  97. package/dist/types/sdk/observation-log.js.map +1 -1
  98. package/dist/types/sdk/observation.d.ts +1 -4
  99. package/dist/workspace/tools/execute-command.js +2 -0
  100. package/dist/workspace/tools/execute-command.js.map +1 -1
  101. package/dist/workspace/types.d.ts +1 -0
  102. package/dist/workspace/types.js.map +1 -1
  103. package/package.json +28 -3
@@ -1,47 +1,17 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
2
  Object.defineProperty(exports, "__esModule", { value: true });
36
3
  exports.AgentRuntime = void 0;
37
- const ai_1 = require("ai");
38
4
  const zod_to_json_schema_1 = require("zod-to-json-schema");
39
5
  const catalog_1 = require("../sdk/catalog");
40
6
  const message_1 = require("../sdk/message");
41
7
  const background_task_tracker_1 = require("./background-task-tracker");
42
8
  const deferred_tool_manager_1 = require("./deferred-tool-manager");
9
+ const episodic_memory_1 = require("./episodic-memory");
43
10
  const event_bus_1 = require("./event-bus");
11
+ const execution_counter_1 = require("./execution-counter");
12
+ const fix_tool_call_1 = require("./fix-tool-call");
44
13
  const json_value_1 = require("./json-value");
14
+ const lazy_ai_1 = require("./lazy-ai");
45
15
  const logger_1 = require("./logger");
46
16
  const memory_store_1 = require("./memory-store");
47
17
  const message_list_1 = require("./message-list");
@@ -58,7 +28,7 @@ const stream_1 = require("./stream");
58
28
  const strip_orphaned_tool_messages_1 = require("./strip-orphaned-tool-messages");
59
29
  const title_generation_1 = require("./title-generation");
60
30
  const tool_adapter_1 = require("./tool-adapter");
61
- const observation_log_1 = require("../types/sdk/observation-log");
31
+ const telemetry_1 = require("../sdk/telemetry");
62
32
  const parse_1 = require("../utils/parse");
63
33
  const zod_1 = require("../utils/zod");
64
34
  function isActiveSpanTracer(value) {
@@ -119,8 +89,12 @@ function buildAgentRootInputAttributes(config) {
119
89
  ...(serialized ? { 'gen_ai.prompt': serialized } : {}),
120
90
  };
121
91
  }
122
- const MAX_LOOP_ITERATIONS = 20;
92
+ const MAX_LOOP_ITERATIONS = 30;
93
+ const DEFAULT_MEMORY_TASK_LOCK_TTL_MS = 30_000;
123
94
  const logger = (0, logger_1.createFilteredLogger)();
95
+ function getAiSdk() {
96
+ return (0, lazy_ai_1.loadAi)();
97
+ }
124
98
  const EMPTY_MESSAGE_LIST = {
125
99
  messages: [],
126
100
  historyIds: [],
@@ -132,13 +106,14 @@ function hasFunctionProperty(value, property) {
132
106
  }
133
107
  function hasObservationLogObserverMemory(memory) {
134
108
  return ((0, observation_log_store_1.hasObservationLogStore)(memory) &&
135
- hasFunctionProperty(memory, 'getMessagesForScope') &&
109
+ hasFunctionProperty(memory, 'getMessagesForObservationScope') &&
136
110
  hasFunctionProperty(memory, 'getCursor') &&
137
111
  hasFunctionProperty(memory, 'setCursor'));
138
112
  }
139
113
  class AgentRuntime {
140
114
  constructor(config) {
141
115
  this.backgroundTasks = new background_task_tracker_1.BackgroundTaskTracker();
116
+ this.episodicMemoryTasksByResource = new Map();
142
117
  this.config = config;
143
118
  if (config.deferredTools && config.deferredTools.length > 0) {
144
119
  this.deferredToolManager = new deferred_tool_manager_1.DeferredToolManager(config.deferredTools, config.toolSearch);
@@ -152,6 +127,9 @@ class AgentRuntime {
152
127
  pendingToolCalls: {},
153
128
  };
154
129
  }
130
+ setTelemetry(telemetry) {
131
+ this.config.telemetry = telemetry;
132
+ }
155
133
  async dispose() {
156
134
  await this.backgroundTasks.flush();
157
135
  }
@@ -205,12 +183,12 @@ class AgentRuntime {
205
183
  throw new Error(`No tool call found for toolCallId: ${options.toolCallId}`);
206
184
  const list = message_list_1.AgentMessageList.deserialize(state.messageList);
207
185
  this.hydrateDeferredToolsFromList(list);
208
- const tool = this.getCurrentTools().find((t) => t.name === toolCall.toolName);
209
- if (!tool)
186
+ const toolForValidation = this.getCurrentTools(state.persistence).find((t) => t.name === toolCall.toolName);
187
+ if (!toolForValidation)
210
188
  throw new Error(`Tool ${toolCall.toolName} not found`);
211
189
  let resumeData = data;
212
- if (tool.resumeSchema) {
213
- const parseResult = await (0, parse_1.parseWithSchema)(tool.resumeSchema, data);
190
+ if (toolForValidation.resumeSchema) {
191
+ const parseResult = await (0, parse_1.parseWithSchema)(toolForValidation.resumeSchema, data);
214
192
  if (!parseResult.success) {
215
193
  throw new Error(`Invalid resume payload: ${parseResult.error}`);
216
194
  }
@@ -219,10 +197,22 @@ class AgentRuntime {
219
197
  try {
220
198
  const { runId: _rid, toolCallId: _tcid, ...callerExecOptions } = options;
221
199
  const persisted = state.executionOptions ?? {};
200
+ const persistedMaxIterations = persisted.maxIterations;
201
+ const callerMaxIterations = callerExecOptions.maxIterations;
202
+ if (callerMaxIterations !== undefined &&
203
+ persistedMaxIterations !== undefined &&
204
+ callerMaxIterations < persistedMaxIterations) {
205
+ throw new Error(`Cannot decrease maxIterations when resuming a run. Expected >= ${persistedMaxIterations}, received ${callerMaxIterations}.`);
206
+ }
207
+ const mergedMaxIterations = callerMaxIterations ?? persistedMaxIterations;
222
208
  const mergedExecOptions = {
223
- ...persisted,
224
209
  ...callerExecOptions,
210
+ ...(mergedMaxIterations !== undefined ? { maxIterations: mergedMaxIterations } : {}),
211
+ ...(state.iterationCount !== undefined ? { iterationCount: state.iterationCount } : {}),
225
212
  };
213
+ const tool = this.getCurrentTools(state.persistence, mergedExecOptions.executionCounter).find((t) => t.name === toolCall.toolName);
214
+ if (!tool)
215
+ throw new Error(`Tool ${toolCall.toolName} not found`);
226
216
  const resumeOptions = {
227
217
  persistence: state.persistence,
228
218
  ...mergedExecOptions,
@@ -278,22 +268,40 @@ class AgentRuntime {
278
268
  async buildMessageList(input, options) {
279
269
  const list = new message_list_1.AgentMessageList();
280
270
  if (this.config.memory && options?.persistence?.threadId) {
281
- const memMessages = await this.config.memory.getMessages(options.persistence.threadId, {
282
- limit: this.config.lastMessages ?? 10,
283
- resourceId: options.persistence.resourceId,
284
- });
271
+ const memMessages = await this.loadHistoryMessages(options.persistence);
285
272
  if (memMessages.length > 0) {
286
273
  list.addHistory((0, strip_orphaned_tool_messages_1.stripOrphanedToolMessages)(memMessages));
287
274
  }
288
275
  }
289
276
  if (this.config.semanticRecall && options?.persistence?.threadId) {
290
- await this.performSemanticRecall(list, input, options.persistence.threadId, options.persistence.resourceId);
277
+ await this.performSemanticRecall(list, input, options.persistence, options.executionCounter);
291
278
  }
292
279
  await this.setListObservationLogMemory(list, options?.persistence);
293
280
  list.addInput(input);
294
281
  return list;
295
282
  }
296
- async performSemanticRecall(list, input, threadId, resourceId) {
283
+ async loadHistoryMessages(persistence) {
284
+ const memory = this.config.memory;
285
+ if (!memory)
286
+ return [];
287
+ const { threadId, resourceId } = persistence;
288
+ if (this.config.observationalMemory && hasObservationLogObserverMemory(memory)) {
289
+ const cursor = await memory.getCursor(threadId);
290
+ if (cursor) {
291
+ return await memory.getMessagesForObservationScope(threadId, {
292
+ since: {
293
+ sinceCreatedAt: cursor.lastObservedAt,
294
+ sinceMessageId: cursor.lastObservedMessageId,
295
+ },
296
+ });
297
+ }
298
+ }
299
+ return await memory.getMessages(threadId, {
300
+ limit: this.config.lastMessages ?? 10,
301
+ resourceId,
302
+ });
303
+ }
304
+ async performSemanticRecall(list, input, persistence, executionCounter) {
297
305
  if (!this.config.semanticRecall || !this.config.memory)
298
306
  return;
299
307
  const userText = input
@@ -306,19 +314,20 @@ class AgentRuntime {
306
314
  return;
307
315
  let recalled = [];
308
316
  if (this.config.memory.queryEmbeddings && this.config.semanticRecall.embedder) {
309
- const { embed } = await Promise.resolve().then(() => __importStar(require('ai')));
317
+ const { embed } = getAiSdk();
310
318
  const embeddingModel = (0, model_factory_1.createEmbeddingModel)(this.config.semanticRecall.embedder, this.config.semanticRecall.apiKey);
311
- const { embedding } = await embed({ model: embeddingModel, value: userText });
319
+ const { embedding, usage } = await embed({ model: embeddingModel, value: userText });
320
+ (0, execution_counter_1.incrementTokenCountFromUsage)(executionCounter, usage);
312
321
  const hits = await this.config.memory.queryEmbeddings({
313
322
  scope: this.config.semanticRecall.scope ?? 'resource',
314
- threadId,
315
- resourceId,
323
+ threadId: persistence.threadId,
324
+ resourceId: persistence.resourceId,
316
325
  vector: embedding,
317
326
  topK: this.config.semanticRecall.topK,
318
327
  });
319
328
  if (hits.length > 0) {
320
329
  const hitIds = new Set(hits.map((h) => h.id));
321
- const allMsgs = await this.config.memory.getMessages(threadId);
330
+ const allMsgs = await this.config.memory.getMessages(persistence.threadId);
322
331
  if (this.config.semanticRecall.messageRange) {
323
332
  recalled = this.expandMessageRange(allMsgs, hitIds, this.config.semanticRecall.messageRange);
324
333
  }
@@ -332,8 +341,8 @@ class AgentRuntime {
332
341
  }
333
342
  else if (this.config.memory.search) {
334
343
  recalled = await this.config.memory.search(userText, {
335
- threadId,
336
- resourceId,
344
+ threadId: persistence.threadId,
345
+ resourceId: persistence.resourceId,
337
346
  topK: this.config.semanticRecall.topK,
338
347
  messageRange: this.config.semanticRecall.messageRange,
339
348
  });
@@ -401,14 +410,18 @@ class AgentRuntime {
401
410
  return { ...inherited, functionId: this.config.name };
402
411
  }
403
412
  async flushTelemetry(options) {
404
- try {
405
- const resolved = this.resolveTelemetry(options);
406
- if (resolved?.provider) {
407
- await resolved.provider.forceFlush();
408
- }
409
- }
410
- catch {
411
- }
413
+ await telemetry_1.Telemetry.forceFlush(this.resolveTelemetry(options));
414
+ }
415
+ buildAiSdkOptions(toolMap, options) {
416
+ return {
417
+ ...this.buildTelemetryOptions(options),
418
+ experimental_repairToolCall: async (options) => {
419
+ return await (0, fix_tool_call_1.fixToolCall)({
420
+ toolCall: options.toolCall,
421
+ error: options.error,
422
+ }, toolMap);
423
+ },
424
+ };
412
425
  }
413
426
  buildTelemetryOptions(options) {
414
427
  const t = this.resolveTelemetry(options);
@@ -469,12 +482,7 @@ class AgentRuntime {
469
482
  this.recordExecutionCounter(() => counter.incrementToolCallCount());
470
483
  }
471
484
  incrementTokenCount(counter, usage) {
472
- if (!counter || !usage)
473
- return;
474
- const tokenCount = usage.totalTokens ?? (usage.inputTokens ?? 0) + (usage.outputTokens ?? 0);
475
- if (tokenCount <= 0)
476
- return;
477
- this.recordExecutionCounter(() => counter.incrementTokenCount(tokenCount));
485
+ (0, execution_counter_1.incrementTokenCountFromUsage)(counter, usage);
478
486
  }
479
487
  async withTelemetryRootSpan(operation, options, runId, fn) {
480
488
  const t = this.resolveTelemetry(options);
@@ -542,7 +550,7 @@ class AgentRuntime {
542
550
  ...options,
543
551
  persistence: options?.persistence,
544
552
  });
545
- const pendingLoopContext = this.buildToolLoopContext(staticLoopContext.aiProviderTools);
553
+ const pendingLoopContext = this.buildToolLoopContext(staticLoopContext.aiProviderTools, options?.persistence, options?.executionCounter);
546
554
  const pendingToolCtx = {
547
555
  toolMap: pendingLoopContext.toolMap,
548
556
  list,
@@ -550,6 +558,9 @@ class AgentRuntime {
550
558
  telemetry: runTelemetry,
551
559
  executionCounter: options?.executionCounter,
552
560
  };
561
+ const maxIterations = options?.maxIterations ?? MAX_LOOP_ITERATIONS;
562
+ let iterationCount = options?.iterationCount ?? 0;
563
+ let reachedStopCondition = false;
553
564
  if (pendingResume) {
554
565
  const batch = await this.iteratePendingToolCallsConcurrent({
555
566
  ...pendingToolCtx,
@@ -561,7 +572,7 @@ class AgentRuntime {
561
572
  collectedSubAgentUsage.push(...r.subAgentUsage);
562
573
  }
563
574
  if (Object.keys(batch.pending).length > 0) {
564
- const suspendRunId = await this.persistSuspension(batch.pending, options, list, totalUsage, runId);
575
+ const suspendRunId = await this.persistSuspension(batch.pending, options, list, totalUsage, runId, maxIterations, iterationCount);
565
576
  return {
566
577
  runId: suspendRunId,
567
578
  messages: list.responseDelta(),
@@ -578,15 +589,15 @@ class AgentRuntime {
578
589
  };
579
590
  }
580
591
  }
581
- const maxIterations = options?.maxIterations ?? MAX_LOOP_ITERATIONS;
582
- for (let i = 0; i < maxIterations; i++) {
592
+ const { generateText } = (0, lazy_ai_1.loadAi)();
593
+ for (; iterationCount < maxIterations; iterationCount++) {
583
594
  if (this.eventBus.isAborted) {
584
595
  this.updateState({ status: 'cancelled' });
585
596
  throw new Error('Agent run was aborted');
586
597
  }
587
598
  this.eventBus.emit({ type: "turn_start" });
588
- const { toolMap, aiTools, hasTools, effectiveInstructions } = this.buildToolLoopContext(staticLoopContext.aiProviderTools);
589
- const result = await (0, ai_1.generateText)({
599
+ const { toolMap, aiTools, hasTools, effectiveInstructions } = this.buildToolLoopContext(staticLoopContext.aiProviderTools, options?.persistence, options?.executionCounter);
600
+ const result = await generateText({
590
601
  model: staticLoopContext.model,
591
602
  messages: list.forLlm(effectiveInstructions, this.config.instructionProviderOptions),
592
603
  abortSignal: this.eventBus.signal,
@@ -595,7 +606,7 @@ class AgentRuntime {
595
606
  ? { providerOptions: staticLoopContext.providerOptions }
596
607
  : {}),
597
608
  ...(staticLoopContext.outputSpec ? { output: staticLoopContext.outputSpec } : {}),
598
- ...this.buildTelemetryOptions(options),
609
+ ...this.buildAiSdkOptions(toolMap, options),
599
610
  });
600
611
  const aiFinishReason = result.finishReason;
601
612
  lastFinishReason = (0, messages_1.fromAiFinishReason)(aiFinishReason);
@@ -609,6 +620,7 @@ class AgentRuntime {
609
620
  structuredOutput = result.output;
610
621
  }
611
622
  this.emitTurnEnd(newMessages, (0, runtime_helpers_1.extractSettledToolCalls)(newMessages));
623
+ reachedStopCondition = true;
612
624
  break;
613
625
  }
614
626
  const batch = await this.iterateToolCallsConcurrent({
@@ -625,7 +637,7 @@ class AgentRuntime {
625
637
  collectedSubAgentUsage.push(...r.subAgentUsage);
626
638
  }
627
639
  if (Object.keys(batch.pending).length > 0) {
628
- const suspendRunId = await this.persistSuspension(batch.pending, options, list, totalUsage, runId);
640
+ const suspendRunId = await this.persistSuspension(batch.pending, options, list, totalUsage, runId, maxIterations, iterationCount + 1);
629
641
  return {
630
642
  runId: suspendRunId,
631
643
  messages: list.responseDelta(),
@@ -643,8 +655,8 @@ class AgentRuntime {
643
655
  }
644
656
  this.emitTurnEnd(newMessages, (0, runtime_helpers_1.extractSettledToolCalls)(list.responseDelta()));
645
657
  }
646
- if (lastFinishReason === 'tool-calls') {
647
- throw new Error(`Agent loop exceeded ${maxIterations} iterations without reaching a stop condition`);
658
+ if (!reachedStopCondition && iterationCount >= maxIterations) {
659
+ lastFinishReason = 'max-iterations';
648
660
  }
649
661
  await this.saveToMemory(list, options);
650
662
  await this.flushTelemetry(options);
@@ -656,6 +668,7 @@ class AgentRuntime {
656
668
  titleConfig: this.config.titleGeneration,
657
669
  agentModel: this.config.model,
658
670
  turnDelta: list.turnDelta(),
671
+ executionCounter: options.executionCounter,
659
672
  });
660
673
  this.backgroundTasks.track(titlePromise);
661
674
  if (this.config.titleGeneration.sync) {
@@ -717,6 +730,9 @@ class AgentRuntime {
717
730
  let structuredOutput;
718
731
  const collectedSubAgentUsage = [];
719
732
  const maxIterations = options?.maxIterations ?? MAX_LOOP_ITERATIONS;
733
+ let iterationCount = options?.iterationCount ?? 0;
734
+ let reachedStopCondition = false;
735
+ const { streamText } = (0, lazy_ai_1.loadAi)();
720
736
  const closeStreamWithError = async (error, status) => {
721
737
  await this.cleanupRun(runId);
722
738
  this.updateState({ status });
@@ -735,7 +751,7 @@ class AgentRuntime {
735
751
  ...options,
736
752
  persistence: options?.persistence,
737
753
  });
738
- const pendingLoopContext = this.buildToolLoopContext(staticLoopContext.aiProviderTools);
754
+ const pendingLoopContext = this.buildToolLoopContext(staticLoopContext.aiProviderTools, options?.persistence, options?.executionCounter);
739
755
  const pendingToolCtx = {
740
756
  toolMap: pendingLoopContext.toolMap,
741
757
  list,
@@ -772,7 +788,7 @@ class AgentRuntime {
772
788
  });
773
789
  }
774
790
  if (Object.keys(batch.pending).length > 0) {
775
- const suspendRunId = await this.persistSuspension(batch.pending, options, list, totalUsage, runId);
791
+ const suspendRunId = await this.persistSuspension(batch.pending, options, list, totalUsage, runId, maxIterations, iterationCount);
776
792
  for (const s of batch.suspensions) {
777
793
  await writer.write({
778
794
  type: 'tool-call-suspended',
@@ -795,13 +811,13 @@ class AgentRuntime {
795
811
  return;
796
812
  }
797
813
  }
798
- for (let i = 0; i < maxIterations; i++) {
814
+ for (; iterationCount < maxIterations; iterationCount++) {
799
815
  if (await handleAbort())
800
816
  return;
801
817
  this.eventBus.emit({ type: "turn_start" });
802
- const { toolMap, aiTools, hasTools, effectiveInstructions } = this.buildToolLoopContext(staticLoopContext.aiProviderTools);
818
+ const { toolMap, aiTools, hasTools, effectiveInstructions } = this.buildToolLoopContext(staticLoopContext.aiProviderTools, options?.persistence, options?.executionCounter);
803
819
  const messages = list.forLlm(effectiveInstructions, this.config.instructionProviderOptions);
804
- const result = (0, ai_1.streamText)({
820
+ const result = streamText({
805
821
  model: staticLoopContext.model,
806
822
  messages,
807
823
  abortSignal: this.eventBus.signal,
@@ -810,7 +826,7 @@ class AgentRuntime {
810
826
  ? { providerOptions: staticLoopContext.providerOptions }
811
827
  : {}),
812
828
  ...(staticLoopContext.outputSpec ? { output: staticLoopContext.outputSpec } : {}),
813
- ...this.buildTelemetryOptions(options),
829
+ ...this.buildAiSdkOptions(toolMap, options),
814
830
  });
815
831
  try {
816
832
  for await (const chunk of result.fullStream) {
@@ -848,6 +864,7 @@ class AgentRuntime {
848
864
  structuredOutput = await result.output;
849
865
  }
850
866
  this.emitTurnEnd(newMessages, (0, runtime_helpers_1.extractSettledToolCalls)(newMessages));
867
+ reachedStopCondition = true;
851
868
  break;
852
869
  }
853
870
  const toolCalls = await result.toolCalls;
@@ -885,7 +902,7 @@ class AgentRuntime {
885
902
  });
886
903
  }
887
904
  if (Object.keys(batch.pending).length > 0) {
888
- const suspendRunId = await this.persistSuspension(batch.pending, options, list, totalUsage, runId);
905
+ const suspendRunId = await this.persistSuspension(batch.pending, options, list, totalUsage, runId, maxIterations, iterationCount + 1);
889
906
  for (const s of batch.suspensions) {
890
907
  await writer.write({
891
908
  type: 'tool-call-suspended',
@@ -909,6 +926,9 @@ class AgentRuntime {
909
926
  }
910
927
  this.emitTurnEnd(newMessages, (0, runtime_helpers_1.extractSettledToolCalls)(list.responseDelta()));
911
928
  }
929
+ if (!reachedStopCondition && iterationCount >= maxIterations) {
930
+ lastFinishReason = 'max-iterations';
931
+ }
912
932
  const costUsage = this.applyCost(totalUsage);
913
933
  const parentCost = costUsage?.cost ?? 0;
914
934
  const subCost = collectedSubAgentUsage.reduce((sum, s) => sum + (s.usage.cost ?? 0), 0);
@@ -933,6 +953,7 @@ class AgentRuntime {
933
953
  titleConfig: this.config.titleGeneration,
934
954
  agentModel: this.config.model,
935
955
  turnDelta: list.turnDelta(),
956
+ executionCounter: options.executionCounter,
936
957
  });
937
958
  this.backgroundTasks.track(titlePromise);
938
959
  if (this.config.titleGeneration.sync) {
@@ -956,49 +977,126 @@ class AgentRuntime {
956
977
  return;
957
978
  await (0, memory_store_1.saveMessagesToThread)(this.config.memory, options.persistence.threadId, options.persistence.resourceId, delta);
958
979
  if (this.config.semanticRecall?.embedder && this.config.memory.saveEmbeddings) {
959
- await this.saveEmbeddingsForMessages(options.persistence.threadId, options.persistence.resourceId, delta);
980
+ await this.saveEmbeddingsForMessages(options.persistence.threadId, options.persistence.resourceId, delta, options.executionCounter);
960
981
  }
961
- this.scheduleObservationLogJobs(options.persistence);
982
+ const observationTasks = this.scheduleObservationLogJobs(options.persistence, options.executionCounter);
983
+ this.scheduleEpisodicMemoryJob(options.persistence, observationTasks, options.executionCounter);
962
984
  }
963
- scheduleObservationLogJobs(persistence) {
985
+ scheduleObservationLogJobs(persistence, executionCounter) {
964
986
  const { memory, observationalMemory } = this.config;
965
987
  if (!memory || !observationalMemory || !(0, observation_log_store_1.hasObservationLogStore)(memory))
966
- return;
988
+ return [];
967
989
  const scope = this.getObservationLogScope(persistence);
968
- const runner = this.getMemoryTaskRunner(memory, observationalMemory);
990
+ const runner = this.getMemoryTaskRunner(memory, observationalMemory.lockTtlMs);
969
991
  const observe = observationalMemory.observe;
970
992
  const observerThresholdTokens = observationalMemory.observerThresholdTokens;
993
+ const tasks = [];
971
994
  if (observe &&
972
995
  observerThresholdTokens !== undefined &&
973
996
  hasObservationLogObserverMemory(memory)) {
974
- runner.schedule({ ...scope, taskKind: 'observer' }, async () => await (0, observation_log_observer_1.runObservationLogObserver)({
997
+ tasks.push(this.scheduleMemoryTask(runner, scope, 'observer', async () => await (0, observation_log_observer_1.runObservationLogObserver)({
975
998
  memory,
976
999
  ...scope,
977
1000
  observerThresholdTokens,
978
1001
  observationLogTailLimit: observationalMemory.observationLogTailLimit ?? 0,
979
1002
  observe,
980
- messageSource: {
981
- threadId: persistence.threadId,
982
- resourceId: persistence.resourceId,
983
- },
984
- }));
1003
+ executionCounter,
1004
+ })));
985
1005
  }
986
1006
  const reflect = observationalMemory.reflect;
987
1007
  const reflectorThresholdTokens = observationalMemory.reflectorThresholdTokens;
988
1008
  if (reflect && reflectorThresholdTokens !== undefined) {
989
- runner.schedule({ ...scope, taskKind: 'reflector' }, async () => await (0, observation_log_reflector_1.runObservationLogReflector)({
1009
+ tasks.push(this.scheduleMemoryTask(runner, scope, 'reflector', async () => await (0, observation_log_reflector_1.runObservationLogReflector)({
990
1010
  memory,
991
1011
  ...scope,
992
1012
  reflectorThresholdTokens,
993
1013
  reflect,
994
- }));
1014
+ executionCounter,
1015
+ })));
1016
+ }
1017
+ return tasks;
1018
+ }
1019
+ scheduleEpisodicMemoryJob(persistence, observationTasks, executionCounter) {
1020
+ const { memory, episodicMemory } = this.config;
1021
+ if (!memory ||
1022
+ !episodicMemory ||
1023
+ !(0, episodic_memory_1.isEpisodicMemoryEnabled)(episodicMemory) ||
1024
+ !(0, episodic_memory_1.hasEpisodicMemoryStore)(memory) ||
1025
+ !(0, observation_log_store_1.hasObservationLogStore)(memory) ||
1026
+ !episodicMemory.extract) {
1027
+ return;
1028
+ }
1029
+ const scope = (0, episodic_memory_1.getEpisodicMemoryScope)(persistence);
1030
+ if (!scope)
1031
+ return;
1032
+ const observationScope = this.getObservationLogScope(persistence);
1033
+ this.scheduleEpisodicMemoryTask(memory, scope.resourceId, async () => {
1034
+ await Promise.allSettled(observationTasks);
1035
+ await (0, episodic_memory_1.runEpisodicMemoryIndexer)({
1036
+ memory,
1037
+ config: episodicMemory,
1038
+ scope,
1039
+ observationScope,
1040
+ threadId: persistence.threadId,
1041
+ executionCounter,
1042
+ });
1043
+ });
1044
+ }
1045
+ scheduleEpisodicMemoryTask(memory, resourceId, task) {
1046
+ const id = crypto.randomUUID();
1047
+ const previous = this.episodicMemoryTasksByResource.get(resourceId) ?? Promise.resolve();
1048
+ const done = previous
1049
+ .catch(() => undefined)
1050
+ .then(async () => await this.runEpisodicMemoryTask(memory, resourceId, id, task));
1051
+ const queued = done.finally(() => {
1052
+ if (this.episodicMemoryTasksByResource.get(resourceId) === queued) {
1053
+ this.episodicMemoryTasksByResource.delete(resourceId);
1054
+ }
1055
+ });
1056
+ this.episodicMemoryTasksByResource.set(resourceId, queued);
1057
+ this.backgroundTasks.track(queued);
1058
+ }
1059
+ async runEpisodicMemoryTask(memory, resourceId, holderId, task) {
1060
+ const taskLock = memory.episodic?.taskLock;
1061
+ let lock = null;
1062
+ try {
1063
+ if (taskLock) {
1064
+ lock = await taskLock.acquire(resourceId, {
1065
+ holderId,
1066
+ ttlMs: this.config.observationalMemory?.lockTtlMs ?? DEFAULT_MEMORY_TASK_LOCK_TTL_MS,
1067
+ });
1068
+ if (!lock)
1069
+ return;
1070
+ }
1071
+ await task();
1072
+ }
1073
+ catch (error) {
1074
+ const message = 'Episodic memory indexing task failed';
1075
+ logger.warn(message, { error, resourceId });
1076
+ this.eventBus.emit({ type: "error", message, error, source: 'episodic-memory' });
1077
+ }
1078
+ finally {
1079
+ if (lock) {
1080
+ await this.releaseEpisodicMemoryTaskLock(taskLock, lock, resourceId);
1081
+ }
1082
+ }
1083
+ }
1084
+ async releaseEpisodicMemoryTaskLock(taskLock, lock, resourceId) {
1085
+ try {
1086
+ await taskLock?.release(lock);
1087
+ }
1088
+ catch (error) {
1089
+ logger.warn('Episodic memory indexing lock release failed', { error, resourceId });
995
1090
  }
996
1091
  }
997
- getMemoryTaskRunner(memory, observationalMemory) {
1092
+ async scheduleMemoryTask(runner, scope, taskKind, task) {
1093
+ return await runner.schedule({ ...scope, taskKind }, task).done;
1094
+ }
1095
+ getMemoryTaskRunner(memory, lockTtlMs) {
998
1096
  this.memoryTasks ??= new scoped_memory_task_runner_1.ScopedMemoryTaskRunner({
999
1097
  tracker: this.backgroundTasks,
1000
1098
  lockStore: (0, observation_log_store_1.hasObservationLogTaskLockStore)(memory) ? memory : undefined,
1001
- lockTtlMs: observationalMemory.lockTtlMs,
1099
+ lockTtlMs,
1002
1100
  onEvent: (event) => {
1003
1101
  if (event.type !== 'failed')
1004
1102
  return;
@@ -1006,8 +1104,7 @@ class AgentRuntime {
1006
1104
  const message = `Observation log ${source} task failed`;
1007
1105
  logger.warn(message, {
1008
1106
  error: event.error,
1009
- scopeKind: event.task.scopeKind,
1010
- scopeId: event.task.scopeId,
1107
+ observationScopeId: event.task.observationScopeId,
1011
1108
  });
1012
1109
  this.eventBus.emit({ type: "error", message, error: event.error, source });
1013
1110
  },
@@ -1016,11 +1113,10 @@ class AgentRuntime {
1016
1113
  }
1017
1114
  getObservationLogScope(persistence) {
1018
1115
  return {
1019
- scopeKind: 'thread',
1020
- scopeId: (0, observation_log_1.createObservationLogThreadScopeId)(persistence.threadId, persistence.resourceId),
1116
+ observationScopeId: persistence.threadId,
1021
1117
  };
1022
1118
  }
1023
- async saveEmbeddingsForMessages(threadId, resourceId, messages) {
1119
+ async saveEmbeddingsForMessages(threadId, resourceId, messages, executionCounter) {
1024
1120
  const embeddable = [];
1025
1121
  for (const msg of messages) {
1026
1122
  if (!(0, message_1.isLlmMessage)(msg) || (msg.role !== 'user' && msg.role !== 'assistant'))
@@ -1038,12 +1134,13 @@ class AgentRuntime {
1038
1134
  const embedder = this.config.semanticRecall?.embedder;
1039
1135
  if (!embedder)
1040
1136
  return;
1041
- const { embedMany } = await Promise.resolve().then(() => __importStar(require('ai')));
1137
+ const { embedMany } = getAiSdk();
1042
1138
  const embeddingModel = (0, model_factory_1.createEmbeddingModel)(embedder, this.config.semanticRecall?.apiKey);
1043
- const { embeddings } = await embedMany({
1139
+ const { embeddings, usage } = await embedMany({
1044
1140
  model: embeddingModel,
1045
1141
  values: embeddable.map((e) => e.text),
1046
1142
  });
1143
+ (0, execution_counter_1.incrementTokenCountFromUsage)(executionCounter, usage);
1047
1144
  await this.config.memory.saveEmbeddings({
1048
1145
  scope: this.config.semanticRecall?.scope ?? 'resource',
1049
1146
  threadId,
@@ -1417,6 +1514,7 @@ class AgentRuntime {
1417
1514
  };
1418
1515
  }
1419
1516
  buildStaticLoopContext(execOptions) {
1517
+ const { Output } = getAiSdk();
1420
1518
  const aiProviderTools = (0, tool_adapter_1.toAiSdkProviderTools)(this.config.providerTools);
1421
1519
  const model = (0, model_factory_1.createModel)(this.config.model);
1422
1520
  return {
@@ -1424,12 +1522,12 @@ class AgentRuntime {
1424
1522
  aiProviderTools,
1425
1523
  providerOptions: this.buildCallProviderOptions(execOptions?.providerOptions),
1426
1524
  outputSpec: this.config.structuredOutput
1427
- ? ai_1.Output.object({ schema: this.config.structuredOutput })
1525
+ ? Output.object({ schema: this.config.structuredOutput })
1428
1526
  : undefined,
1429
1527
  };
1430
1528
  }
1431
- buildToolLoopContext(aiProviderTools) {
1432
- const allUserTools = this.getCurrentTools();
1529
+ buildToolLoopContext(aiProviderTools, persistence, executionCounter) {
1530
+ const allUserTools = this.getCurrentTools(persistence, executionCounter);
1433
1531
  const aiTools = (0, tool_adapter_1.toAiSdkTools)(allUserTools);
1434
1532
  const allTools = { ...aiTools, ...aiProviderTools };
1435
1533
  const aiToolCount = Object.keys(allTools).length;
@@ -1442,15 +1540,35 @@ class AgentRuntime {
1442
1540
  effectiveInstructions,
1443
1541
  };
1444
1542
  }
1445
- getCurrentTools() {
1543
+ getCurrentTools(persistence, executionCounter) {
1446
1544
  const baseTools = this.config.tools ?? [];
1447
- if (!this.deferredToolManager?.hasTools)
1448
- return baseTools;
1449
- return [
1545
+ const tools = [
1450
1546
  ...baseTools,
1451
- ...this.deferredToolManager.getControllerTools(),
1452
- ...this.deferredToolManager.getLoadedTools(),
1547
+ ...(this.deferredToolManager?.hasTools
1548
+ ? [
1549
+ ...this.deferredToolManager.getControllerTools(),
1550
+ ...this.deferredToolManager.getLoadedTools(),
1551
+ ]
1552
+ : []),
1453
1553
  ];
1554
+ const recallTool = this.createRecallMemoryToolForRun(persistence, tools, executionCounter);
1555
+ return recallTool ? [...tools, recallTool] : tools;
1556
+ }
1557
+ createRecallMemoryToolForRun(persistence, existingTools, executionCounter) {
1558
+ const { memory, episodicMemory } = this.config;
1559
+ if (!memory ||
1560
+ !episodicMemory ||
1561
+ !(0, episodic_memory_1.isEpisodicMemoryEnabled)(episodicMemory) ||
1562
+ !(0, episodic_memory_1.hasEpisodicMemoryStore)(memory)) {
1563
+ return undefined;
1564
+ }
1565
+ const scope = (0, episodic_memory_1.getEpisodicMemoryScope)(persistence);
1566
+ if (!scope)
1567
+ return undefined;
1568
+ if (existingTools.some((tool) => tool.name === episodic_memory_1.RECALL_MEMORY_TOOL_NAME)) {
1569
+ throw new Error(`Tool name "${episodic_memory_1.RECALL_MEMORY_TOOL_NAME}" is reserved while episodic memory is enabled.`);
1570
+ }
1571
+ return (0, episodic_memory_1.createRecallMemoryTool)({ memory, config: episodicMemory, scope, executionCounter });
1454
1572
  }
1455
1573
  hydrateDeferredToolsFromList(list) {
1456
1574
  if (!this.deferredToolManager?.hasTools)
@@ -1467,9 +1585,11 @@ class AgentRuntime {
1467
1585
  const block = `<built_in_rules>\n${fragments.map((f) => `- ${f}`).join('\n')}\n</built_in_rules>`;
1468
1586
  return userInstructions ? `${block}\n\n${userInstructions}` : block;
1469
1587
  }
1470
- async persistSuspension(pendingToolCalls, options, list, totalUsage, existingRunId) {
1588
+ async persistSuspension(pendingToolCalls, options, list, totalUsage, existingRunId, maxIterations, iterationCount) {
1471
1589
  const runId = existingRunId ?? (0, run_state_1.generateRunId)();
1472
- const executionOptions = options?.maxIterations !== undefined ? { maxIterations: options.maxIterations } : undefined;
1590
+ const resolvedMaxIterations = maxIterations ?? options?.maxIterations;
1591
+ const resolvedIterationCount = iterationCount ?? options?.iterationCount;
1592
+ const executionOptions = resolvedMaxIterations !== undefined ? { maxIterations: resolvedMaxIterations } : undefined;
1473
1593
  const state = {
1474
1594
  persistence: options?.persistence,
1475
1595
  status: 'suspended',
@@ -1477,6 +1597,7 @@ class AgentRuntime {
1477
1597
  pendingToolCalls,
1478
1598
  usage: totalUsage,
1479
1599
  executionOptions,
1600
+ ...(resolvedIterationCount !== undefined ? { iterationCount: resolvedIterationCount } : {}),
1480
1601
  };
1481
1602
  await this.runState.suspend(runId, state);
1482
1603
  this.updateState({ status: 'suspended', pendingToolCalls, messageList: list.serialize() });
@@ -1539,6 +1660,9 @@ class AgentRuntime {
1539
1660
  (0, observation_log_renderer_1.renderObservationLog)(observations, {
1540
1661
  renderTokenBudget: this.config.observationLog?.renderTokenBudget,
1541
1662
  }) ?? undefined;
1663
+ if (observations.length > 0) {
1664
+ list.seedLastCreatedAt(observations[observations.length - 1].createdAt.getTime());
1665
+ }
1542
1666
  }
1543
1667
  getConfiguredTelemetry() {
1544
1668
  return this.config.telemetry;