@falai/agent 0.9.0-alpha-1 → 0.9.0-alpha-2

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 (166) hide show
  1. package/README.md +34 -22
  2. package/dist/cjs/src/core/Agent.d.ts +52 -24
  3. package/dist/cjs/src/core/Agent.d.ts.map +1 -1
  4. package/dist/cjs/src/core/Agent.js +266 -39
  5. package/dist/cjs/src/core/Agent.js.map +1 -1
  6. package/dist/cjs/src/core/PersistenceManager.d.ts.map +1 -1
  7. package/dist/cjs/src/core/PersistenceManager.js +48 -25
  8. package/dist/cjs/src/core/PersistenceManager.js.map +1 -1
  9. package/dist/cjs/src/core/PromptComposer.d.ts +1 -1
  10. package/dist/cjs/src/core/PromptComposer.d.ts.map +1 -1
  11. package/dist/cjs/src/core/PromptComposer.js.map +1 -1
  12. package/dist/cjs/src/core/ResponseEngine.d.ts +13 -12
  13. package/dist/cjs/src/core/ResponseEngine.d.ts.map +1 -1
  14. package/dist/cjs/src/core/ResponseEngine.js +4 -4
  15. package/dist/cjs/src/core/ResponseEngine.js.map +1 -1
  16. package/dist/cjs/src/core/ResponsePipeline.d.ts +66 -38
  17. package/dist/cjs/src/core/ResponsePipeline.d.ts.map +1 -1
  18. package/dist/cjs/src/core/ResponsePipeline.js +71 -3
  19. package/dist/cjs/src/core/ResponsePipeline.js.map +1 -1
  20. package/dist/cjs/src/core/Route.d.ts +24 -5
  21. package/dist/cjs/src/core/Route.d.ts.map +1 -1
  22. package/dist/cjs/src/core/Route.js +45 -1
  23. package/dist/cjs/src/core/Route.js.map +1 -1
  24. package/dist/cjs/src/core/RoutingEngine.d.ts +31 -6
  25. package/dist/cjs/src/core/RoutingEngine.d.ts.map +1 -1
  26. package/dist/cjs/src/core/RoutingEngine.js +113 -9
  27. package/dist/cjs/src/core/RoutingEngine.js.map +1 -1
  28. package/dist/cjs/src/core/SessionManager.d.ts +14 -4
  29. package/dist/cjs/src/core/SessionManager.d.ts.map +1 -1
  30. package/dist/cjs/src/core/SessionManager.js +25 -5
  31. package/dist/cjs/src/core/SessionManager.js.map +1 -1
  32. package/dist/cjs/src/core/Step.d.ts +10 -10
  33. package/dist/cjs/src/core/Step.d.ts.map +1 -1
  34. package/dist/cjs/src/core/Step.js.map +1 -1
  35. package/dist/cjs/src/core/ToolExecutor.d.ts +4 -2
  36. package/dist/cjs/src/core/ToolExecutor.d.ts.map +1 -1
  37. package/dist/cjs/src/core/ToolExecutor.js +13 -3
  38. package/dist/cjs/src/core/ToolExecutor.js.map +1 -1
  39. package/dist/cjs/src/types/agent.d.ts +41 -21
  40. package/dist/cjs/src/types/agent.d.ts.map +1 -1
  41. package/dist/cjs/src/types/agent.js.map +1 -1
  42. package/dist/cjs/src/types/index.d.ts +1 -1
  43. package/dist/cjs/src/types/index.d.ts.map +1 -1
  44. package/dist/cjs/src/types/index.js.map +1 -1
  45. package/dist/cjs/src/types/persistence.d.ts +0 -1
  46. package/dist/cjs/src/types/persistence.d.ts.map +1 -1
  47. package/dist/cjs/src/types/route.d.ts +22 -16
  48. package/dist/cjs/src/types/route.d.ts.map +1 -1
  49. package/dist/cjs/src/types/session.d.ts +6 -11
  50. package/dist/cjs/src/types/session.d.ts.map +1 -1
  51. package/dist/cjs/src/types/tool.d.ts +12 -6
  52. package/dist/cjs/src/types/tool.d.ts.map +1 -1
  53. package/dist/cjs/src/utils/session.d.ts +2 -2
  54. package/dist/cjs/src/utils/session.d.ts.map +1 -1
  55. package/dist/cjs/src/utils/session.js +6 -26
  56. package/dist/cjs/src/utils/session.js.map +1 -1
  57. package/dist/src/core/Agent.d.ts +52 -24
  58. package/dist/src/core/Agent.d.ts.map +1 -1
  59. package/dist/src/core/Agent.js +266 -39
  60. package/dist/src/core/Agent.js.map +1 -1
  61. package/dist/src/core/PersistenceManager.d.ts.map +1 -1
  62. package/dist/src/core/PersistenceManager.js +48 -25
  63. package/dist/src/core/PersistenceManager.js.map +1 -1
  64. package/dist/src/core/PromptComposer.d.ts +1 -1
  65. package/dist/src/core/PromptComposer.d.ts.map +1 -1
  66. package/dist/src/core/PromptComposer.js.map +1 -1
  67. package/dist/src/core/ResponseEngine.d.ts +13 -12
  68. package/dist/src/core/ResponseEngine.d.ts.map +1 -1
  69. package/dist/src/core/ResponseEngine.js +4 -4
  70. package/dist/src/core/ResponseEngine.js.map +1 -1
  71. package/dist/src/core/ResponsePipeline.d.ts +66 -38
  72. package/dist/src/core/ResponsePipeline.d.ts.map +1 -1
  73. package/dist/src/core/ResponsePipeline.js +71 -3
  74. package/dist/src/core/ResponsePipeline.js.map +1 -1
  75. package/dist/src/core/Route.d.ts +24 -5
  76. package/dist/src/core/Route.d.ts.map +1 -1
  77. package/dist/src/core/Route.js +45 -1
  78. package/dist/src/core/Route.js.map +1 -1
  79. package/dist/src/core/RoutingEngine.d.ts +31 -6
  80. package/dist/src/core/RoutingEngine.d.ts.map +1 -1
  81. package/dist/src/core/RoutingEngine.js +113 -9
  82. package/dist/src/core/RoutingEngine.js.map +1 -1
  83. package/dist/src/core/SessionManager.d.ts +14 -4
  84. package/dist/src/core/SessionManager.d.ts.map +1 -1
  85. package/dist/src/core/SessionManager.js +25 -5
  86. package/dist/src/core/SessionManager.js.map +1 -1
  87. package/dist/src/core/Step.d.ts +10 -10
  88. package/dist/src/core/Step.d.ts.map +1 -1
  89. package/dist/src/core/Step.js.map +1 -1
  90. package/dist/src/core/ToolExecutor.d.ts +4 -2
  91. package/dist/src/core/ToolExecutor.d.ts.map +1 -1
  92. package/dist/src/core/ToolExecutor.js +13 -3
  93. package/dist/src/core/ToolExecutor.js.map +1 -1
  94. package/dist/src/types/agent.d.ts +41 -21
  95. package/dist/src/types/agent.d.ts.map +1 -1
  96. package/dist/src/types/agent.js.map +1 -1
  97. package/dist/src/types/index.d.ts +1 -1
  98. package/dist/src/types/index.d.ts.map +1 -1
  99. package/dist/src/types/index.js.map +1 -1
  100. package/dist/src/types/persistence.d.ts +0 -1
  101. package/dist/src/types/persistence.d.ts.map +1 -1
  102. package/dist/src/types/route.d.ts +22 -16
  103. package/dist/src/types/route.d.ts.map +1 -1
  104. package/dist/src/types/session.d.ts +6 -11
  105. package/dist/src/types/session.d.ts.map +1 -1
  106. package/dist/src/types/tool.d.ts +12 -6
  107. package/dist/src/types/tool.d.ts.map +1 -1
  108. package/dist/src/utils/session.d.ts +2 -2
  109. package/dist/src/utils/session.d.ts.map +1 -1
  110. package/dist/src/utils/session.js +6 -26
  111. package/dist/src/utils/session.js.map +1 -1
  112. package/docs/README.md +3 -3
  113. package/docs/api/README.md +35 -4
  114. package/docs/api/overview.md +166 -12
  115. package/docs/core/agent/README.md +162 -17
  116. package/docs/core/agent/context-management.md +39 -15
  117. package/docs/core/agent/session-management.md +49 -16
  118. package/docs/core/ai-integration/prompt-composition.md +38 -14
  119. package/docs/core/ai-integration/response-processing.md +28 -17
  120. package/docs/core/conversation-flows/data-collection.md +103 -25
  121. package/docs/core/conversation-flows/route-dsl.md +45 -22
  122. package/docs/core/conversation-flows/routes.md +74 -18
  123. package/docs/core/conversation-flows/step-transitions.md +3 -3
  124. package/docs/core/conversation-flows/steps.md +39 -15
  125. package/docs/core/routing/intelligent-routing.md +18 -9
  126. package/docs/core/tools/tool-definition.md +8 -8
  127. package/docs/core/tools/tool-execution.md +26 -26
  128. package/docs/core/tools/tool-scoping.md +5 -5
  129. package/docs/guides/getting-started/README.md +54 -32
  130. package/examples/advanced-patterns/knowledge-based-agent.ts +37 -28
  131. package/examples/advanced-patterns/persistent-onboarding.ts +70 -41
  132. package/examples/advanced-patterns/route-lifecycle-hooks.ts +28 -2
  133. package/examples/advanced-patterns/streaming-responses.ts +28 -23
  134. package/examples/ai-providers/anthropic-integration.ts +40 -33
  135. package/examples/ai-providers/openai-integration.ts +25 -25
  136. package/examples/conversation-flows/completion-transitions.ts +36 -32
  137. package/examples/core-concepts/basic-agent.ts +76 -78
  138. package/examples/core-concepts/schema-driven-extraction.ts +20 -16
  139. package/examples/core-concepts/session-management.ts +65 -53
  140. package/examples/integrations/database-integration.ts +49 -34
  141. package/examples/integrations/healthcare-integration.ts +96 -91
  142. package/examples/integrations/search-integration.ts +79 -82
  143. package/examples/integrations/server-session-management.ts +25 -17
  144. package/examples/persistence/database-persistence.ts +61 -45
  145. package/examples/persistence/memory-sessions.ts +52 -63
  146. package/examples/persistence/redis-persistence.ts +81 -95
  147. package/examples/tools/basic-tools.ts +73 -62
  148. package/examples/tools/data-enrichment-tools.ts +52 -44
  149. package/package.json +1 -1
  150. package/src/core/Agent.ts +418 -128
  151. package/src/core/PersistenceManager.ts +51 -27
  152. package/src/core/PromptComposer.ts +1 -1
  153. package/src/core/ResponseEngine.ts +21 -19
  154. package/src/core/ResponsePipeline.ts +174 -59
  155. package/src/core/Route.ts +58 -6
  156. package/src/core/RoutingEngine.ts +174 -27
  157. package/src/core/SessionManager.ts +32 -8
  158. package/src/core/Step.ts +20 -12
  159. package/src/core/ToolExecutor.ts +19 -5
  160. package/src/types/agent.ts +46 -23
  161. package/src/types/index.ts +2 -0
  162. package/src/types/persistence.ts +0 -1
  163. package/src/types/route.ts +22 -16
  164. package/src/types/session.ts +6 -12
  165. package/src/types/tool.ts +15 -9
  166. package/src/utils/session.ts +6 -31
package/src/core/Agent.ts CHANGED
@@ -17,6 +17,8 @@ import type {
17
17
  AgentResponseStreamChunk,
18
18
  AgentResponse,
19
19
  StructuredSchema,
20
+ ValidationError,
21
+ ValidationResult,
20
22
  } from "../types";
21
23
  import { EventKind, MessageRole } from "../types/history";
22
24
  import {
@@ -42,26 +44,48 @@ import { ResponsePipeline } from "./ResponsePipeline";
42
44
  import { END_ROUTE_ID } from "../constants";
43
45
 
44
46
  /**
45
- * Main Agent class with generic context support
47
+ * Error thrown when data validation fails
46
48
  */
47
- export class Agent<TContext = unknown> {
48
- private terms: Term<TContext>[] = [];
49
- private guidelines: Guideline<TContext>[] = [];
50
- private tools: Tool<TContext, unknown[], unknown, unknown>[] = [];
51
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
52
- private routes: Route<TContext, any>[] = [];
49
+ class DataValidationError extends Error {
50
+ constructor(public errors: ValidationError[], message?: string) {
51
+ super(message || "Data validation failed");
52
+ this.name = "DataValidationError";
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Error thrown when route configuration is invalid
58
+ */
59
+ class RouteConfigurationError extends Error {
60
+ constructor(public routeTitle: string, public invalidFields: string[], message?: string) {
61
+ super(message || `Route configuration error in '${routeTitle}'`);
62
+ this.name = "RouteConfigurationError";
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Main Agent class with generic context and data support
68
+ */
69
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
70
+ export class Agent<TContext = any, TData = any> {
71
+ private terms: Term<TContext, TData>[] = [];
72
+ private guidelines: Guideline<TContext, TData>[] = [];
73
+ private tools: Tool<TContext, TData, unknown[], unknown>[] = [];
74
+ private routes: Route<TContext, TData>[] = [];
53
75
  private context: TContext | undefined;
54
- private persistenceManager: PersistenceManager | undefined;
55
- private routingEngine: RoutingEngine<TContext>;
56
- private responseEngine: ResponseEngine<TContext>;
57
- private responsePipeline: ResponsePipeline<TContext>;
58
- private currentSession?: SessionState;
76
+ private persistenceManager: PersistenceManager<TData> | undefined;
77
+ private routingEngine: RoutingEngine<TContext, TData>;
78
+ private responseEngine: ResponseEngine<TContext, TData>;
79
+ private responsePipeline: ResponsePipeline<TContext, TData>;
80
+ private currentSession?: SessionState<TData>;
59
81
  private knowledgeBase: Record<string, unknown> = {};
82
+ private schema?: StructuredSchema;
83
+ private collectedData: Partial<TData> = {};
60
84
 
61
85
  /** Public session manager for easy session management */
62
- public session: SessionManager<unknown>;
86
+ public session: SessionManager<TData>;
63
87
 
64
- constructor(private readonly options: AgentOptions<TContext>) {
88
+ constructor(private readonly options: AgentOptions<TContext, TData>) {
65
89
  // Set log level based on debug option
66
90
  if (options.debug) {
67
91
  logger.setLevel(LoggerLevel.DEBUG);
@@ -74,40 +98,81 @@ export class Agent<TContext = unknown> {
74
98
  );
75
99
  }
76
100
 
101
+ // Initialize and validate agent-level schema if provided
102
+ if (options.schema) {
103
+ this.schema = options.schema;
104
+ this.validateSchema(this.schema);
105
+ logger.debug("[Agent] Agent-level schema initialized and validated");
106
+ }
107
+
77
108
  // Initialize context if provided
78
109
  this.context = options.context;
79
110
 
111
+ // Initialize collected data with initial data if provided
112
+ if (options.initialData) {
113
+ if (this.schema) {
114
+ const validation = this.validateData(options.initialData);
115
+ if (!validation.valid) {
116
+ throw new Error(
117
+ `Initial data validation failed: ${validation.errors.map(e => e.message).join(', ')}`
118
+ );
119
+ }
120
+ }
121
+ this.collectedData = { ...options.initialData };
122
+ logger.debug("[Agent] Initial data set:", this.collectedData);
123
+ }
124
+
80
125
  // Initialize current session if provided
81
126
  this.currentSession = options.session;
82
127
 
83
128
  // Initialize routing and response engines
84
- this.routingEngine = new RoutingEngine<TContext>({
129
+ this.routingEngine = new RoutingEngine<TContext, TData>({
85
130
  maxCandidates: 5,
86
131
  allowRouteSwitch: true,
87
132
  switchThreshold: 70,
88
133
  });
89
- this.responseEngine = new ResponseEngine<TContext>();
90
- this.responsePipeline = new ResponsePipeline<TContext>(
134
+ this.responseEngine = new ResponseEngine<TContext, TData>();
135
+ this.responsePipeline = new ResponsePipeline<TContext, TData>(
91
136
  options,
92
137
  this.routes,
93
138
  this.tools,
94
139
  this.routingEngine,
95
140
  this.updateContext.bind(this),
96
- this.updateData.bind(this)
141
+ this.updateData.bind(this),
142
+ this.updateCollectedData.bind(this)
97
143
  );
98
144
 
99
145
  // Initialize persistence if configured
100
146
  if (options.persistence) {
101
- this.persistenceManager = new PersistenceManager(options.persistence);
147
+ try {
148
+ // Validate persistence configuration
149
+ if (!options.persistence.adapter) {
150
+ throw new Error("Persistence adapter is required when persistence is configured");
151
+ }
102
152
 
103
- // Initialize the adapter if it has an initialize method
104
- if (options.persistence.adapter.initialize) {
105
- options.persistence.adapter.initialize().catch((error) => {
106
- logger.error(
107
- "[Agent] Persistence adapter initialization failed:",
108
- error
109
- );
110
- });
153
+ if (!options.persistence.adapter.sessionRepository) {
154
+ throw new Error("Persistence adapter must provide a sessionRepository");
155
+ }
156
+
157
+ if (!options.persistence.adapter.messageRepository) {
158
+ throw new Error("Persistence adapter must provide a messageRepository");
159
+ }
160
+
161
+ this.persistenceManager = new PersistenceManager<TData>(options.persistence);
162
+
163
+ // Initialize the adapter if it has an initialize method
164
+ if (options.persistence.adapter.initialize) {
165
+ options.persistence.adapter.initialize().catch((error) => {
166
+ logger.error(
167
+ "[Agent] Persistence adapter initialization failed:",
168
+ error instanceof Error ? error.message : String(error)
169
+ );
170
+ });
171
+ }
172
+ } catch (error) {
173
+ const errorMessage = error instanceof Error ? error.message : String(error);
174
+ logger.error("[Agent] Failed to initialize persistence:", errorMessage);
175
+ throw new Error(`Failed to initialize persistence: ${errorMessage}`);
111
176
  }
112
177
  }
113
178
 
@@ -132,7 +197,7 @@ export class Agent<TContext = unknown> {
132
197
 
133
198
  if (options.routes) {
134
199
  options.routes.forEach((routeOptions) => {
135
- this.createRoute<unknown>(routeOptions);
200
+ this.createRoute(routeOptions);
136
201
  });
137
202
  }
138
203
 
@@ -142,7 +207,7 @@ export class Agent<TContext = unknown> {
142
207
  }
143
208
 
144
209
  // Initialize session manager
145
- this.session = new SessionManager(this.persistenceManager);
210
+ this.session = new SessionManager<TData>(this.persistenceManager);
146
211
 
147
212
  // Store sessionId for later use in getOrCreate calls
148
213
  if (options.sessionId) {
@@ -153,6 +218,143 @@ export class Agent<TContext = unknown> {
153
218
  }
154
219
  }
155
220
 
221
+ /**
222
+ * Validate the agent-level schema structure
223
+ * @private
224
+ */
225
+ private validateSchema(schema: StructuredSchema): void {
226
+ if (!schema || typeof schema !== 'object') {
227
+ throw new Error(
228
+ "Agent schema must be a valid JSON Schema object. " +
229
+ "Provide a schema with 'type': 'object' and 'properties' to define the data structure."
230
+ );
231
+ }
232
+
233
+ if (schema.type !== 'object') {
234
+ throw new Error(
235
+ `Agent schema must be of type 'object', but received '${String(schema.type)}'. ` +
236
+ "Agent-level schemas must define object structures for data collection."
237
+ );
238
+ }
239
+
240
+ if (!schema.properties || typeof schema.properties !== 'object') {
241
+ throw new Error(
242
+ "Agent schema must have a 'properties' field defining the data fields. " +
243
+ "Example: { type: 'object', properties: { name: { type: 'string' }, email: { type: 'string' } } }"
244
+ );
245
+ }
246
+
247
+ logger.debug("[Agent] Schema validation passed");
248
+ }
249
+
250
+ /**
251
+ * Validate data against the agent-level schema
252
+ */
253
+ validateData(data: Partial<TData>): ValidationResult {
254
+ if (!this.schema) {
255
+ // No schema defined, consider all data valid
256
+ return { valid: true, errors: [], warnings: [] };
257
+ }
258
+
259
+ const errors: ValidationError[] = [];
260
+ const warnings: ValidationError[] = [];
261
+
262
+ // Basic validation - check if provided fields exist in schema
263
+ if (this.schema.properties) {
264
+ for (const [key, value] of Object.entries(data)) {
265
+ if (!(key in this.schema.properties)) {
266
+ errors.push({
267
+ field: key,
268
+ value,
269
+ message: `Field '${key}' is not defined in agent schema`,
270
+ schemaPath: `properties.${key}`
271
+ });
272
+ }
273
+ }
274
+ }
275
+
276
+ // Check required fields if specified
277
+ if (this.schema.required && Array.isArray(this.schema.required)) {
278
+ for (const requiredField of this.schema.required) {
279
+ if (!(requiredField in data) || data[requiredField as keyof TData] === undefined) {
280
+ warnings.push({
281
+ field: requiredField,
282
+ value: undefined,
283
+ message: `Required field '${requiredField}' is missing`,
284
+ schemaPath: `required`
285
+ });
286
+ }
287
+ }
288
+ }
289
+
290
+ return {
291
+ valid: errors.length === 0,
292
+ errors,
293
+ warnings
294
+ };
295
+ }
296
+
297
+ /**
298
+ * Check if a field is valid according to the agent schema
299
+ * @param field - The field key to validate
300
+ * @returns true if field exists in schema or no schema is defined, false otherwise
301
+ */
302
+ isValidSchemaField(field: keyof TData): boolean {
303
+ if (!this.schema || !this.schema.properties) {
304
+ // No schema defined, consider all fields valid
305
+ return true;
306
+ }
307
+
308
+ return field as string in this.schema.properties;
309
+ }
310
+
311
+ /**
312
+ * Get the current collected data
313
+ */
314
+ getCollectedData(): Partial<TData> {
315
+ return { ...this.collectedData };
316
+ }
317
+
318
+ /**
319
+ * Update collected data with validation
320
+ */
321
+ async updateCollectedData(updates: Partial<TData>): Promise<void> {
322
+ // Validate the updates
323
+ const validation = this.validateData(updates);
324
+ if (!validation.valid) {
325
+ const errorMessages = validation.errors.map(e => e.message).join(', ');
326
+ throw new DataValidationError(validation.errors, `Data validation failed: ${errorMessages}`);
327
+ }
328
+
329
+ // Log warnings if any
330
+ if (validation.warnings.length > 0) {
331
+ const warningMessages = validation.warnings.map(w => w.message).join(', ');
332
+ logger.warn(`[Agent] Data validation warnings: ${warningMessages}`);
333
+ }
334
+
335
+ // Merge updates with current data
336
+ const previousData = { ...this.collectedData };
337
+ this.collectedData = {
338
+ ...this.collectedData,
339
+ ...updates
340
+ };
341
+
342
+ // Trigger agent-level lifecycle hook if configured
343
+ if (this.options.hooks?.onDataUpdate) {
344
+ this.collectedData = await this.options.hooks.onDataUpdate(
345
+ this.collectedData,
346
+ previousData
347
+ );
348
+ }
349
+
350
+ // Update current session if it exists to keep it in sync
351
+ if (this.currentSession) {
352
+ this.currentSession = mergeCollected(this.currentSession, this.collectedData);
353
+ }
354
+
355
+ logger.debug("[Agent] Collected data updated:", updates);
356
+ }
357
+
156
358
  /**
157
359
  * Get agent name
158
360
  */
@@ -182,12 +384,41 @@ export class Agent<TContext = unknown> {
182
384
  }
183
385
 
184
386
  /**
185
- * Create a new route (journey)
186
- * @template TData - Type of data collected throughout the route
387
+ * Create a new route (journey) using agent-level data type
187
388
  */
188
- createRoute<TData = unknown>(
389
+ createRoute(
189
390
  options: RouteOptions<TContext, TData>
190
391
  ): Route<TContext, TData> {
392
+ // Validate that requiredFields exist in agent schema
393
+ if (options.requiredFields && this.schema?.properties) {
394
+ const invalidRequiredFields = options.requiredFields.filter(
395
+ field => !(String(field) in this.schema!.properties!)
396
+ );
397
+ if (invalidRequiredFields.length > 0) {
398
+ throw new RouteConfigurationError(
399
+ options.title,
400
+ invalidRequiredFields.map(f => String(f)),
401
+ `Invalid required fields in route '${options.title}': ${invalidRequiredFields.join(', ')}. ` +
402
+ `Must be valid keys from agent schema. Available fields: ${Object.keys(this.schema.properties).join(', ')}.`
403
+ );
404
+ }
405
+ }
406
+
407
+ // Validate that optionalFields exist in agent schema
408
+ if (options.optionalFields && this.schema?.properties) {
409
+ const invalidOptionalFields = options.optionalFields.filter(
410
+ field => !(String(field) in this.schema!.properties!)
411
+ );
412
+ if (invalidOptionalFields.length > 0) {
413
+ throw new RouteConfigurationError(
414
+ options.title,
415
+ invalidOptionalFields.map(f => String(f)),
416
+ `Invalid optional fields in route '${options.title}': ${invalidOptionalFields.join(', ')}. ` +
417
+ `Must be valid keys from agent schema. Available fields: ${Object.keys(this.schema.properties).join(', ')}.`
418
+ );
419
+ }
420
+ }
421
+
191
422
  const route = new Route<TContext, TData>(options);
192
423
  this.routes.push(route);
193
424
  return route;
@@ -196,7 +427,7 @@ export class Agent<TContext = unknown> {
196
427
  /**
197
428
  * Create a domain term for the glossary
198
429
  */
199
- createTerm(term: Term<TContext>): this {
430
+ createTerm(term: Term<TContext, TData>): this {
200
431
  this.terms.push(term);
201
432
  return this;
202
433
  }
@@ -204,7 +435,7 @@ export class Agent<TContext = unknown> {
204
435
  /**
205
436
  * Create a behavioral guideline
206
437
  */
207
- createGuideline(guideline: Guideline<TContext>): this {
438
+ createGuideline(guideline: Guideline<TContext, TData>): this {
208
439
  const guidelineWithId = {
209
440
  ...guideline,
210
441
  id: guideline.id || `guideline_${this.guidelines.length}`,
@@ -217,7 +448,7 @@ export class Agent<TContext = unknown> {
217
448
  /**
218
449
  * Register a tool at the agent level
219
450
  */
220
- createTool(tool: Tool<TContext, unknown[], unknown, unknown>): this {
451
+ createTool(tool: Tool<TContext, TData, unknown[], unknown>): this {
221
452
  this.tools.push(tool);
222
453
  return this;
223
454
  }
@@ -225,7 +456,7 @@ export class Agent<TContext = unknown> {
225
456
  /**
226
457
  * Register multiple tools at the agent level
227
458
  */
228
- registerTools(tools: Tool<TContext, unknown[], unknown, unknown>[]): this {
459
+ registerTools(tools: Tool<TContext, TData, unknown[], unknown>[]): this {
229
460
  tools.forEach((tool) => this.createTool(tool));
230
461
  return this;
231
462
  }
@@ -267,7 +498,7 @@ export class Agent<TContext = unknown> {
267
498
  * Triggers both agent-level and route-specific onDataUpdate lifecycle hooks if configured
268
499
  * @internal
269
500
  */
270
- private async updateData<TData = unknown>(
501
+ private async updateData(
271
502
  session: SessionState<TData>,
272
503
  dataUpdate: Partial<TData>
273
504
  ): Promise<SessionState<TData>> {
@@ -297,9 +528,12 @@ export class Agent<TContext = unknown> {
297
528
  newCollected = (await this.options.hooks.onDataUpdate(
298
529
  newCollected,
299
530
  previousCollected
300
- )) as Partial<TData>;
531
+ ));
301
532
  }
302
533
 
534
+ // Update agent's collected data to stay in sync
535
+ this.collectedData = { ...newCollected };
536
+
303
537
  // Return updated session
304
538
  return mergeCollected(session, newCollected);
305
539
  }
@@ -316,11 +550,17 @@ export class Agent<TContext = unknown> {
316
550
  // Otherwise return the stored context
317
551
  return this.context;
318
552
  }
553
+ /**
554
+ * Get current schema
555
+ */
556
+ getSchema(): StructuredSchema | undefined {
557
+ return this.schema;
558
+ }
319
559
 
320
560
  /**
321
561
  * Generate a response based on history and context as a stream
322
562
  */
323
- async *respondStream<TData = Record<string, unknown>>(params: {
563
+ async *respondStream(params: {
324
564
  history: History;
325
565
  step?: StepRef;
326
566
  session?: SessionState<TData>;
@@ -340,6 +580,13 @@ export class Agent<TContext = unknown> {
340
580
  });
341
581
  const { effectiveContext } = responseContext;
342
582
  session = responseContext.session;
583
+
584
+ // Merge agent's collected data into session (agent data takes precedence)
585
+ if (Object.keys(this.collectedData).length > 0) {
586
+ session = mergeCollected(session, this.collectedData);
587
+ logger.debug("[Agent] Merged agent collected data into session:", this.collectedData);
588
+ }
589
+
343
590
  // Update our stored context if it was modified by beforeRespond hook
344
591
  this.context = this.responsePipeline.getStoredContext();
345
592
 
@@ -395,7 +642,8 @@ export class Agent<TContext = unknown> {
395
642
  // Build response schema for this route (with collect fields from step)
396
643
  const responseSchema = this.responseEngine.responseSchemaForRoute(
397
644
  selectedRoute,
398
- nextStep
645
+ nextStep,
646
+ this.schema
399
647
  );
400
648
 
401
649
  // Check if selected route and next step are defined
@@ -428,6 +676,7 @@ export class Agent<TContext = unknown> {
428
676
  ),
429
677
  context: effectiveContext,
430
678
  session,
679
+ agentSchema: this.schema,
431
680
  });
432
681
 
433
682
  // Collect available tools for AI
@@ -475,11 +724,12 @@ export class Agent<TContext = unknown> {
475
724
  continue;
476
725
  }
477
726
 
478
- const toolExecutor = new ToolExecutor<TContext, unknown>();
727
+ const toolExecutor = new ToolExecutor<TContext, TData>();
479
728
  const result = await toolExecutor.executeTool({
480
729
  tool: tool,
481
730
  context: effectiveContext,
482
731
  updateContext: this.updateContext.bind(this),
732
+ updateData: this.updateCollectedData.bind(this),
483
733
  history,
484
734
  data: session.data,
485
735
  toolArguments: toolCall.arguments,
@@ -494,7 +744,7 @@ export class Agent<TContext = unknown> {
494
744
 
495
745
  // Update collected data with tool results
496
746
  if (result.dataUpdate) {
497
- session = await this.updateData(session, result.dataUpdate);
747
+ session = await this.updateData(session, result.dataUpdate as Partial<TData>);
498
748
  logger.debug(
499
749
  `[Agent] Tool updated collected data:`,
500
750
  result.dataUpdate
@@ -577,8 +827,7 @@ export class Agent<TContext = unknown> {
577
827
 
578
828
  if (hasToolCalls) {
579
829
  logger.debug(
580
- `[Agent] Follow-up streaming call produced ${
581
- followUpToolCalls!.length
830
+ `[Agent] Follow-up streaming call produced ${followUpToolCalls!.length
582
831
  } additional tool calls`
583
832
  );
584
833
 
@@ -595,11 +844,12 @@ export class Agent<TContext = unknown> {
595
844
  continue;
596
845
  }
597
846
 
598
- const toolExecutor = new ToolExecutor<TContext, unknown>();
847
+ const toolExecutor = new ToolExecutor<TContext, TData>();
599
848
  const result = await toolExecutor.executeTool({
600
849
  tool: tool,
601
850
  context: effectiveContext,
602
851
  updateContext: this.updateContext.bind(this),
852
+ updateData: this.updateCollectedData.bind(this),
603
853
  history: updatedHistory,
604
854
  data: session.data,
605
855
  toolArguments: toolCall.arguments,
@@ -613,7 +863,7 @@ export class Agent<TContext = unknown> {
613
863
  }
614
864
 
615
865
  if (result.dataUpdate) {
616
- session = await this.updateData(session, result.dataUpdate);
866
+ session = await this.updateData(session, result.dataUpdate as Partial<TData>);
617
867
  logger.debug(
618
868
  `[Agent] Streaming follow-up tool updated collected data:`,
619
869
  result.dataUpdate
@@ -651,14 +901,19 @@ export class Agent<TContext = unknown> {
651
901
  Record<string, unknown>;
652
902
 
653
903
  for (const field of nextStep.collect) {
654
- if (field in structuredData) {
655
- collectedData[field] = structuredData[field];
904
+ const fieldKey = String(field);
905
+ if (fieldKey in structuredData) {
906
+ collectedData[fieldKey] = structuredData[fieldKey];
656
907
  }
657
908
  }
658
909
 
659
- // Merge collected data into session
910
+ // Merge collected data into session using agent-level data validation
660
911
  if (Object.keys(collectedData).length > 0) {
661
- session = await this.updateData(session, collectedData);
912
+ // Update agent-level collected data with validation
913
+ await this.updateCollectedData(collectedData as Partial<TData>);
914
+
915
+ // Update session with validated data
916
+ session = await this.updateData(session, collectedData as Partial<TData>);
662
917
  logger.debug(`[Agent] Collected data:`, collectedData);
663
918
  }
664
919
  }
@@ -735,7 +990,7 @@ export class Agent<TContext = unknown> {
735
990
  const endStepSpec = selectedRoute.endStepSpec;
736
991
 
737
992
  // Create a temporary step for completion message generation using endStep configuration
738
- const completionStep = new Step<TContext, unknown>(selectedRoute.id, {
993
+ const completionStep = new Step<TContext, TData>(selectedRoute.id, {
739
994
  description: endStepSpec.description,
740
995
  id: endStepSpec.id || END_ROUTE_ID,
741
996
  collect: endStepSpec.collect,
@@ -748,7 +1003,8 @@ export class Agent<TContext = unknown> {
748
1003
  // Build response schema for completion
749
1004
  const responseSchema = this.responseEngine.responseSchemaForRoute(
750
1005
  selectedRoute,
751
- completionStep
1006
+ completionStep,
1007
+ this.schema
752
1008
  );
753
1009
  const templateContext = {
754
1010
  context: effectiveContext,
@@ -777,6 +1033,7 @@ export class Agent<TContext = unknown> {
777
1033
  ),
778
1034
  context: effectiveContext,
779
1035
  session,
1036
+ agentSchema: this.schema,
780
1037
  });
781
1038
 
782
1039
  // Stream completion message using AI provider
@@ -909,7 +1166,7 @@ export class Agent<TContext = unknown> {
909
1166
  /**
910
1167
  * Generate a response based on history and context
911
1168
  */
912
- async respond<TData = Record<string, unknown>>(params: {
1169
+ async respond(params: {
913
1170
  history: History;
914
1171
  step?: StepRef;
915
1172
  session?: SessionState<TData>;
@@ -941,6 +1198,12 @@ export class Agent<TContext = unknown> {
941
1198
  cloneDeep(this.currentSession) ||
942
1199
  (await this.session.getOrCreate());
943
1200
 
1201
+ // Merge agent's collected data into session (agent data takes precedence)
1202
+ if (Object.keys(this.collectedData).length > 0) {
1203
+ session = mergeCollected(session, this.collectedData);
1204
+ logger.debug("[Agent] Merged agent collected data into session:", this.collectedData);
1205
+ }
1206
+
944
1207
  // PHASE 1: PREPARE - Execute prepare function if current step has one
945
1208
  if (session.currentRoute && session.currentStep) {
946
1209
  const currentRoute = this.routes.find(
@@ -962,9 +1225,9 @@ export class Agent<TContext = unknown> {
962
1225
  }
963
1226
 
964
1227
  // PHASE 2: ROUTING + STEP SELECTION - Determine which route and step to use (combined)
965
- let selectedRoute: Route<TContext, unknown> | undefined;
1228
+ let selectedRoute: Route<TContext, TData> | undefined;
966
1229
  let responseDirectives: string[] | undefined;
967
- let selectedStep: Step<TContext, unknown> | undefined;
1230
+ let selectedStep: Step<TContext, TData> | undefined;
968
1231
  let isRouteComplete = false;
969
1232
 
970
1233
  // Check for pending transition from previous route completion
@@ -1034,9 +1297,14 @@ export class Agent<TContext = unknown> {
1034
1297
  | Array<{ toolName: string; arguments: Record<string, unknown> }>
1035
1298
  | undefined = undefined;
1036
1299
  let responsePrompt: string;
1037
- let availableTools: Tool<TContext, unknown[], unknown, unknown>[] = [];
1300
+ let availableTools: Array<{
1301
+ id: string;
1302
+ name: string;
1303
+ description?: string;
1304
+ parameters?: unknown;
1305
+ }> = [];
1038
1306
  let responseSchema: StructuredSchema | undefined;
1039
- let nextStep: Step<TContext, unknown> | undefined;
1307
+ let nextStep: Step<TContext, TData> | undefined;
1040
1308
 
1041
1309
  // Get last user message (needed for both route and completion handling)
1042
1310
  const lastUserMessage = getLastMessageFromHistory(history);
@@ -1077,7 +1345,8 @@ export class Agent<TContext = unknown> {
1077
1345
  // Build response schema for this route (with collect fields from step)
1078
1346
  responseSchema = this.responseEngine.responseSchemaForRoute(
1079
1347
  selectedRoute,
1080
- nextStep
1348
+ nextStep,
1349
+ this.schema
1081
1350
  );
1082
1351
 
1083
1352
  // Build response prompt
@@ -1101,13 +1370,14 @@ export class Agent<TContext = unknown> {
1101
1370
  ),
1102
1371
  context: effectiveContext,
1103
1372
  session,
1373
+ agentSchema: this.schema,
1104
1374
  });
1105
1375
 
1106
1376
  // Collect available tools for AI
1107
1377
  availableTools = this.collectAvailableTools(
1108
1378
  selectedRoute,
1109
1379
  nextStep
1110
- ) as Tool<TContext, unknown[], unknown, unknown>[];
1380
+ );
1111
1381
  } else {
1112
1382
  // No route selected - generate basic response without route context
1113
1383
  logger.debug(`[Agent] No route selected, generating basic response`);
@@ -1123,7 +1393,7 @@ export class Agent<TContext = unknown> {
1123
1393
  });
1124
1394
 
1125
1395
  // Use agent-level tools only
1126
- availableTools = [...this.tools];
1396
+ availableTools = this.collectAvailableTools();
1127
1397
  responseSchema = undefined;
1128
1398
  }
1129
1399
 
@@ -1136,9 +1406,9 @@ export class Agent<TContext = unknown> {
1136
1406
  signal,
1137
1407
  parameters: responseSchema
1138
1408
  ? {
1139
- jsonSchema: responseSchema,
1140
- schemaName: "response_output",
1141
- }
1409
+ jsonSchema: responseSchema,
1410
+ schemaName: "response_output",
1411
+ }
1142
1412
  : undefined,
1143
1413
  });
1144
1414
 
@@ -1161,11 +1431,12 @@ export class Agent<TContext = unknown> {
1161
1431
  continue;
1162
1432
  }
1163
1433
 
1164
- const toolExecutor = new ToolExecutor<TContext, unknown>();
1434
+ const toolExecutor = new ToolExecutor<TContext, TData>();
1165
1435
  const toolResult = await toolExecutor.executeTool({
1166
1436
  tool: tool,
1167
1437
  context: effectiveContext,
1168
1438
  updateContext: this.updateContext.bind(this),
1439
+ updateData: this.updateCollectedData.bind(this),
1169
1440
  history,
1170
1441
  data: session.data,
1171
1442
  toolArguments: toolCall.arguments,
@@ -1180,7 +1451,7 @@ export class Agent<TContext = unknown> {
1180
1451
 
1181
1452
  // Update collected data with tool results
1182
1453
  if (toolResult.dataUpdate) {
1183
- session = await this.updateData(session, toolResult.dataUpdate);
1454
+ session = await this.updateData(session, toolResult.dataUpdate as Partial<TData>);
1184
1455
  logger.debug(
1185
1456
  `[Agent] Tool updated collected data:`,
1186
1457
  toolResult.dataUpdate
@@ -1251,8 +1522,7 @@ export class Agent<TContext = unknown> {
1251
1522
 
1252
1523
  if (hasToolCalls) {
1253
1524
  logger.debug(
1254
- `[Agent] Follow-up call produced ${
1255
- followUpToolCalls!.length
1525
+ `[Agent] Follow-up call produced ${followUpToolCalls!.length
1256
1526
  } additional tool calls`
1257
1527
  );
1258
1528
 
@@ -1266,11 +1536,12 @@ export class Agent<TContext = unknown> {
1266
1536
  continue;
1267
1537
  }
1268
1538
 
1269
- const toolExecutor = new ToolExecutor<TContext, unknown>();
1539
+ const toolExecutor = new ToolExecutor<TContext, TData>();
1270
1540
  const toolResult = await toolExecutor.executeTool({
1271
1541
  tool: tool,
1272
1542
  context: effectiveContext,
1273
1543
  updateContext: this.updateContext.bind(this),
1544
+ updateData: this.updateCollectedData.bind(this),
1274
1545
  history: updatedHistory,
1275
1546
  data: session.data,
1276
1547
  toolArguments: toolCall.arguments,
@@ -1284,7 +1555,7 @@ export class Agent<TContext = unknown> {
1284
1555
  }
1285
1556
 
1286
1557
  if (toolResult.dataUpdate) {
1287
- session = await this.updateData(session, toolResult.dataUpdate);
1558
+ session = await this.updateData(session, toolResult.dataUpdate as Partial<TData>);
1288
1559
  logger.debug(
1289
1560
  `[Agent] Follow-up tool updated collected data:`,
1290
1561
  toolResult.dataUpdate
@@ -1323,14 +1594,19 @@ export class Agent<TContext = unknown> {
1323
1594
  Record<string, unknown>;
1324
1595
 
1325
1596
  for (const field of nextStep.collect) {
1326
- if (field in structuredData) {
1327
- collectedData[field] = structuredData[field];
1597
+ const fieldKey = String(field);
1598
+ if (fieldKey in structuredData) {
1599
+ collectedData[fieldKey] = structuredData[fieldKey];
1328
1600
  }
1329
1601
  }
1330
1602
 
1331
- // Merge collected data into session
1603
+ // Merge collected data into session using agent-level data validation
1332
1604
  if (Object.keys(collectedData).length > 0) {
1333
- session = await this.updateData(session, collectedData);
1605
+ // Update agent-level collected data with validation
1606
+ await this.updateCollectedData(collectedData as Partial<TData>);
1607
+
1608
+ // Update session with validated data
1609
+ session = await this.updateData(session, collectedData as Partial<TData>);
1334
1610
  logger.debug(`[Agent] Collected data:`, collectedData);
1335
1611
  }
1336
1612
  }
@@ -1355,7 +1631,7 @@ export class Agent<TContext = unknown> {
1355
1631
  const endStepSpec = selectedRoute!.endStepSpec;
1356
1632
 
1357
1633
  // Create a temporary step for completion message generation using endStep configuration
1358
- const completionStep = new Step<TContext, unknown>(selectedRoute!.id, {
1634
+ const completionStep = new Step<TContext, TData>(selectedRoute!.id, {
1359
1635
  description: endStepSpec.description,
1360
1636
  id: endStepSpec.id || END_ROUTE_ID,
1361
1637
  collect: endStepSpec.collect,
@@ -1365,19 +1641,21 @@ export class Agent<TContext = unknown> {
1365
1641
  "Summarize what was accomplished and confirm completion based on the conversation history and collected data",
1366
1642
  });
1367
1643
 
1644
+ if (!selectedRoute) {
1645
+ throw new Error("Selected route is not defined");
1646
+ }
1647
+
1368
1648
  // Build response schema for completion
1369
1649
  const responseSchema = this.responseEngine.responseSchemaForRoute(
1370
- selectedRoute!,
1371
- completionStep
1650
+ selectedRoute,
1651
+ completionStep,
1652
+ this.schema
1372
1653
  );
1373
1654
  const templateContext = {
1374
1655
  context: effectiveContext,
1375
1656
  session,
1376
1657
  history,
1377
1658
  };
1378
- if (!selectedRoute) {
1379
- throw new Error("Selected route is not defined");
1380
- }
1381
1659
 
1382
1660
  // Build completion response prompt
1383
1661
  const completionPrompt = await this.responseEngine.buildResponsePrompt({
@@ -1400,6 +1678,7 @@ export class Agent<TContext = unknown> {
1400
1678
  ),
1401
1679
  context: effectiveContext,
1402
1680
  session,
1681
+ agentSchema: this.schema,
1403
1682
  });
1404
1683
 
1405
1684
  // Generate completion message using AI provider
@@ -1545,21 +1824,21 @@ export class Agent<TContext = unknown> {
1545
1824
  /**
1546
1825
  * Get all routes
1547
1826
  */
1548
- getRoutes(): Route<TContext, unknown>[] {
1827
+ getRoutes(): Route<TContext, TData>[] {
1549
1828
  return [...this.routes];
1550
1829
  }
1551
1830
 
1552
1831
  /**
1553
1832
  * Get all terms
1554
1833
  */
1555
- getTerms(): Term<TContext>[] {
1834
+ getTerms(): Term<TContext, TData>[] {
1556
1835
  return [...this.terms];
1557
1836
  }
1558
1837
 
1559
1838
  /**
1560
1839
  * Get all tools
1561
1840
  */
1562
- getTools(): Tool<TContext, unknown[], unknown, unknown>[] {
1841
+ getTools(): Tool<TContext, TData, unknown[], unknown>[] {
1563
1842
  return [...this.tools];
1564
1843
  }
1565
1844
 
@@ -1570,8 +1849,8 @@ export class Agent<TContext = unknown> {
1570
1849
  */
1571
1850
  private findAvailableTool(
1572
1851
  toolName: string,
1573
- route?: Route<TContext, unknown>
1574
- ): Tool<TContext, unknown[], unknown, unknown> | undefined {
1852
+ route?: Route<TContext, TData>
1853
+ ): Tool<TContext, TData, unknown[], unknown> | undefined {
1575
1854
  // Check route-level tools first (if route provided)
1576
1855
  if (route) {
1577
1856
  const routeTool = route
@@ -1591,8 +1870,8 @@ export class Agent<TContext = unknown> {
1591
1870
  * @private
1592
1871
  */
1593
1872
  private collectAvailableTools(
1594
- route?: Route<TContext, unknown>,
1595
- step?: Step<TContext, unknown>
1873
+ route?: Route<TContext, TData>,
1874
+ step?: Step<TContext, TData>
1596
1875
  ): Array<{
1597
1876
  id: string;
1598
1877
  name: string;
@@ -1601,7 +1880,7 @@ export class Agent<TContext = unknown> {
1601
1880
  }> {
1602
1881
  const availableTools = new Map<
1603
1882
  string,
1604
- Tool<TContext, unknown[], unknown, unknown>
1883
+ Tool<TContext, TData, unknown[], unknown>
1605
1884
  >();
1606
1885
 
1607
1886
  // Add agent-level tools
@@ -1619,7 +1898,7 @@ export class Agent<TContext = unknown> {
1619
1898
  // Filter by step-level allowed tools if specified
1620
1899
  if (step?.tools) {
1621
1900
  const allowedToolIds = new Set<string>();
1622
- const stepTools: Tool<TContext, unknown[], unknown, unknown>[] = [];
1901
+ const stepTools: Tool<TContext, TData, unknown[], unknown>[] = [];
1623
1902
 
1624
1903
  for (const toolRef of step.tools) {
1625
1904
  if (typeof toolRef === "string") {
@@ -1638,7 +1917,7 @@ export class Agent<TContext = unknown> {
1638
1917
  if (allowedToolIds.size > 0) {
1639
1918
  const filteredTools = new Map<
1640
1919
  string,
1641
- Tool<TContext, unknown[], unknown, unknown>
1920
+ Tool<TContext, TData, unknown[], unknown>
1642
1921
  >();
1643
1922
  for (const toolId of allowedToolIds) {
1644
1923
  const tool = availableTools.get(toolId);
@@ -1673,13 +1952,13 @@ export class Agent<TContext = unknown> {
1673
1952
  private async executePrepareFinalize(
1674
1953
  prepareOrFinalize:
1675
1954
  | string
1676
- | Tool<TContext, unknown[], unknown, unknown>
1677
- | ((context: TContext, data?: Partial<unknown>) => void | Promise<void>)
1955
+ | Tool<TContext, TData, unknown[], unknown>
1956
+ | ((context: TContext, data?: Partial<TData>) => void | Promise<void>)
1678
1957
  | undefined,
1679
1958
  context: TContext,
1680
- data?: Partial<unknown>,
1681
- route?: Route<TContext, unknown>,
1682
- step?: Step<TContext, unknown>
1959
+ data?: Partial<TData>,
1960
+ route?: Route<TContext, TData>,
1961
+ step?: Step<TContext, TData>
1683
1962
  ): Promise<void> {
1684
1963
  if (!prepareOrFinalize) return;
1685
1964
 
@@ -1688,13 +1967,13 @@ export class Agent<TContext = unknown> {
1688
1967
  await prepareOrFinalize(context, data);
1689
1968
  } else {
1690
1969
  // It's a tool reference - find and execute the tool
1691
- let tool: Tool<TContext, unknown[], unknown, unknown> | undefined;
1970
+ let tool: Tool<TContext, TData, unknown[], unknown> | undefined;
1692
1971
 
1693
1972
  if (typeof prepareOrFinalize === "string") {
1694
1973
  // Tool ID - find it in available tools
1695
1974
  const availableTools = new Map<
1696
1975
  string,
1697
- Tool<TContext, unknown[], unknown, unknown>
1976
+ Tool<TContext, TData, unknown[], unknown>
1698
1977
  >();
1699
1978
 
1700
1979
  // Add agent-level tools
@@ -1727,11 +2006,12 @@ export class Agent<TContext = unknown> {
1727
2006
  }
1728
2007
 
1729
2008
  if (tool) {
1730
- const toolExecutor = new ToolExecutor<TContext, unknown>();
2009
+ const toolExecutor = new ToolExecutor<TContext, TData>();
1731
2010
  const result = await toolExecutor.executeTool({
1732
2011
  tool,
1733
2012
  context,
1734
2013
  updateContext: this.updateContext.bind(this),
2014
+ updateData: this.updateCollectedData.bind(this),
1735
2015
  history: [], // Empty history for prepare/finalize
1736
2016
  data,
1737
2017
  });
@@ -1744,10 +2024,9 @@ export class Agent<TContext = unknown> {
1744
2024
  }
1745
2025
  } else {
1746
2026
  logger.warn(
1747
- `[Agent] Tool not found for prepare/finalize: ${
1748
- typeof prepareOrFinalize === "string"
1749
- ? prepareOrFinalize
1750
- : "inline tool"
2027
+ `[Agent] Tool not found for prepare/finalize: ${typeof prepareOrFinalize === "string"
2028
+ ? prepareOrFinalize
2029
+ : "inline tool"
1751
2030
  }`
1752
2031
  );
1753
2032
  }
@@ -1757,7 +2036,7 @@ export class Agent<TContext = unknown> {
1757
2036
  /**
1758
2037
  * Get all guidelines
1759
2038
  */
1760
- getGuidelines(): Guideline<TContext>[] {
2039
+ getGuidelines(): Guideline<TContext, TData>[] {
1761
2040
  return [...this.guidelines];
1762
2041
  }
1763
2042
 
@@ -1773,10 +2052,10 @@ export class Agent<TContext = unknown> {
1773
2052
  * @private
1774
2053
  */
1775
2054
  private mergeTerms(
1776
- agentTerms: Term<TContext>[],
1777
- routeTerms: Term<TContext>[]
1778
- ): Term<TContext>[] {
1779
- const merged = new Map<string, Term<TContext>>();
2055
+ agentTerms: Term<TContext, TData>[],
2056
+ routeTerms: Term<TContext, TData>[]
2057
+ ): Term<TContext, TData>[] {
2058
+ const merged = new Map<string, Term<TContext, TData>>();
1780
2059
 
1781
2060
  // Add agent terms first
1782
2061
  agentTerms.forEach((term) => {
@@ -1798,7 +2077,7 @@ export class Agent<TContext = unknown> {
1798
2077
  /**
1799
2078
  * Get the persistence manager (if configured)
1800
2079
  */
1801
- getPersistenceManager(): PersistenceManager | undefined {
2080
+ getPersistenceManager(): PersistenceManager<TData> | undefined {
1802
2081
  return this.persistenceManager;
1803
2082
  }
1804
2083
 
@@ -1832,21 +2111,20 @@ export class Agent<TContext = unknown> {
1832
2111
  }
1833
2112
 
1834
2113
  /**
1835
- * Get collected data from current session
2114
+ * Get collected data from current session or agent-level collected data
1836
2115
  * @param routeId - Optional route ID to get data for (uses current route if not provided)
1837
- * @returns The collected data from the current session
2116
+ * @returns The collected data from the current session or agent-level data
1838
2117
  */
1839
- getData<TData = unknown>(routeId?: string): Partial<TData> {
1840
- if (!this.currentSession) {
1841
- return {} as Partial<TData>;
1842
- }
1843
- if (routeId) {
1844
- return (
1845
- (this.currentSession.dataByRoute?.[routeId] as Partial<TData>) ||
1846
- ({} as Partial<TData>)
1847
- );
2118
+ getData(): Partial<TData> {
2119
+ // If we have a current session, use session data
2120
+ if (this.currentSession) {
2121
+ // With agent-level data, all routes share the same data structure
2122
+ // No need for route-specific data access
2123
+ return (this.currentSession.data) || {};
1848
2124
  }
1849
- return (this.currentSession.data as Partial<TData>) || {};
2125
+
2126
+ // Otherwise, return agent-level collected data
2127
+ return this.getCollectedData();
1850
2128
  }
1851
2129
 
1852
2130
  /**
@@ -1868,10 +2146,10 @@ export class Agent<TContext = unknown> {
1868
2146
  */
1869
2147
  async nextStepRoute(
1870
2148
  routeIdOrTitle: string,
1871
- session?: SessionState,
1872
- condition?: Template<TContext, unknown>,
2149
+ session?: SessionState<TData>,
2150
+ condition?: Template<TContext, TData>,
1873
2151
  history?: Event[]
1874
- ): Promise<SessionState> {
2152
+ ): Promise<SessionState<TData>> {
1875
2153
  const targetSession = session || this.currentSession;
1876
2154
 
1877
2155
  if (!targetSession) {
@@ -1900,12 +2178,12 @@ export class Agent<TContext = unknown> {
1900
2178
  };
1901
2179
  const renderedCondition = await render(condition, templateContext);
1902
2180
 
1903
- const updatedSession: SessionState = {
2181
+ const updatedSession: SessionState<TData> = {
1904
2182
  ...targetSession,
1905
2183
  pendingTransition: {
1906
2184
  targetRouteId: targetRoute.id,
1907
2185
  condition: renderedCondition,
1908
- reason: "manual",
2186
+ reason: "route_complete",
1909
2187
  },
1910
2188
  };
1911
2189
 
@@ -1915,7 +2193,7 @@ export class Agent<TContext = unknown> {
1915
2193
  }
1916
2194
 
1917
2195
  logger.debug(
1918
- `[Agent] Set pending manual transition to route: ${targetRoute.title}`
2196
+ `[Agent] Set pending transition to route: ${targetRoute.title}`
1919
2197
  );
1920
2198
 
1921
2199
  return updatedSession;
@@ -1925,7 +2203,7 @@ export class Agent<TContext = unknown> {
1925
2203
  * Simplified respond method using SessionManager
1926
2204
  * Automatically manages conversation history through the session
1927
2205
  */
1928
- async chat<TData = Record<string, unknown>>(
2206
+ async chat(
1929
2207
  message?: string,
1930
2208
  options?: {
1931
2209
  history?: History; // Optional: override session history for this response
@@ -1947,7 +2225,15 @@ export class Agent<TContext = unknown> {
1947
2225
  }
1948
2226
 
1949
2227
  // Get or create session
1950
- const session = await this.session.getOrCreate();
2228
+ let session = await this.session.getOrCreate();
2229
+
2230
+ // Merge agent's collected data into session (agent data takes precedence)
2231
+ if (Object.keys(this.collectedData).length > 0) {
2232
+ session = mergeCollected(session, this.collectedData);
2233
+ // Update the session manager with the merged data
2234
+ await this.session.setData(this.collectedData);
2235
+ logger.debug("[Agent] Merged agent collected data into chat session:", this.collectedData);
2236
+ }
1951
2237
 
1952
2238
  // Use existing respond method with session-managed history
1953
2239
  const result = await this.respond({
@@ -1962,6 +2248,10 @@ export class Agent<TContext = unknown> {
1962
2248
  await this.session.addMessage("assistant", result.message);
1963
2249
  }
1964
2250
 
1965
- return result as AgentResponse<TData>;
2251
+ // Ensure the result includes the current session
2252
+ return {
2253
+ ...result,
2254
+ session: result.session || this.session.current,
2255
+ };
1966
2256
  }
1967
2257
  }