@falai/agent 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (285) hide show
  1. package/README.md +21 -74
  2. package/dist/cjs/core/Agent.d.ts +22 -29
  3. package/dist/cjs/core/Agent.d.ts.map +1 -1
  4. package/dist/cjs/core/Agent.js +465 -275
  5. package/dist/cjs/core/Agent.js.map +1 -1
  6. package/dist/cjs/core/Events.d.ts +10 -1
  7. package/dist/cjs/core/Events.d.ts.map +1 -1
  8. package/dist/cjs/core/Events.js +3 -2
  9. package/dist/cjs/core/Events.js.map +1 -1
  10. package/dist/cjs/core/PersistenceManager.d.ts +19 -0
  11. package/dist/cjs/core/PersistenceManager.d.ts.map +1 -1
  12. package/dist/cjs/core/PersistenceManager.js +57 -0
  13. package/dist/cjs/core/PersistenceManager.js.map +1 -1
  14. package/dist/cjs/core/PromptComposer.d.ts +24 -0
  15. package/dist/cjs/core/PromptComposer.d.ts.map +1 -0
  16. package/dist/cjs/core/PromptComposer.js +127 -0
  17. package/dist/cjs/core/PromptComposer.js.map +1 -0
  18. package/dist/cjs/core/ResponseEngine.d.ts +19 -0
  19. package/dist/cjs/core/ResponseEngine.d.ts.map +1 -0
  20. package/dist/cjs/core/ResponseEngine.js +51 -0
  21. package/dist/cjs/core/ResponseEngine.js.map +1 -0
  22. package/dist/cjs/core/Route.d.ts +18 -12
  23. package/dist/cjs/core/Route.d.ts.map +1 -1
  24. package/dist/cjs/core/Route.js +15 -9
  25. package/dist/cjs/core/Route.js.map +1 -1
  26. package/dist/cjs/core/RoutingEngine.d.ts +38 -0
  27. package/dist/cjs/core/RoutingEngine.d.ts.map +1 -0
  28. package/dist/cjs/core/RoutingEngine.js +110 -0
  29. package/dist/cjs/core/RoutingEngine.js.map +1 -0
  30. package/dist/cjs/core/State.d.ts +15 -4
  31. package/dist/cjs/core/State.d.ts.map +1 -1
  32. package/dist/cjs/core/State.js +21 -2
  33. package/dist/cjs/core/State.js.map +1 -1
  34. package/dist/cjs/core/ToolExecutor.d.ts +29 -0
  35. package/dist/cjs/core/ToolExecutor.d.ts.map +1 -0
  36. package/dist/cjs/core/ToolExecutor.js +73 -0
  37. package/dist/cjs/core/ToolExecutor.js.map +1 -0
  38. package/dist/cjs/core/Transition.d.ts +5 -5
  39. package/dist/cjs/core/Transition.d.ts.map +1 -1
  40. package/dist/cjs/core/Transition.js.map +1 -1
  41. package/dist/cjs/index.d.ts +6 -8
  42. package/dist/cjs/index.d.ts.map +1 -1
  43. package/dist/cjs/index.js +8 -10
  44. package/dist/cjs/index.js.map +1 -1
  45. package/dist/cjs/providers/AnthropicProvider.d.ts.map +1 -1
  46. package/dist/cjs/providers/AnthropicProvider.js +10 -13
  47. package/dist/cjs/providers/AnthropicProvider.js.map +1 -1
  48. package/dist/cjs/providers/GeminiProvider.d.ts.map +1 -1
  49. package/dist/cjs/providers/GeminiProvider.js +12 -8
  50. package/dist/cjs/providers/GeminiProvider.js.map +1 -1
  51. package/dist/cjs/providers/OpenAIProvider.d.ts.map +1 -1
  52. package/dist/cjs/providers/OpenAIProvider.js +10 -53
  53. package/dist/cjs/providers/OpenAIProvider.js.map +1 -1
  54. package/dist/cjs/providers/OpenRouterProvider.d.ts.map +1 -1
  55. package/dist/cjs/providers/OpenRouterProvider.js +10 -53
  56. package/dist/cjs/providers/OpenRouterProvider.js.map +1 -1
  57. package/dist/cjs/types/agent.d.ts +13 -9
  58. package/dist/cjs/types/agent.d.ts.map +1 -1
  59. package/dist/cjs/types/ai.d.ts +8 -2
  60. package/dist/cjs/types/ai.d.ts.map +1 -1
  61. package/dist/cjs/types/history.d.ts +8 -0
  62. package/dist/cjs/types/history.d.ts.map +1 -1
  63. package/dist/cjs/types/index.d.ts +0 -3
  64. package/dist/cjs/types/index.d.ts.map +1 -1
  65. package/dist/cjs/types/index.js +1 -3
  66. package/dist/cjs/types/index.js.map +1 -1
  67. package/dist/cjs/types/route.d.ts +39 -4
  68. package/dist/cjs/types/route.d.ts.map +1 -1
  69. package/dist/cjs/types/routing.d.ts +16 -0
  70. package/dist/cjs/types/routing.d.ts.map +1 -0
  71. package/dist/cjs/types/routing.js +3 -0
  72. package/dist/cjs/types/routing.js.map +1 -0
  73. package/dist/cjs/types/schema.d.ts +22 -0
  74. package/dist/cjs/types/schema.d.ts.map +1 -0
  75. package/dist/cjs/types/schema.js +3 -0
  76. package/dist/cjs/types/schema.js.map +1 -0
  77. package/dist/cjs/types/session.d.ts +72 -0
  78. package/dist/cjs/types/session.d.ts.map +1 -0
  79. package/dist/cjs/types/session.js +140 -0
  80. package/dist/cjs/types/session.js.map +1 -0
  81. package/dist/cjs/types/tool.d.ts +11 -5
  82. package/dist/cjs/types/tool.d.ts.map +1 -1
  83. package/dist/cjs/utils/id.d.ts +0 -5
  84. package/dist/cjs/utils/id.d.ts.map +1 -1
  85. package/dist/cjs/utils/id.js +0 -10
  86. package/dist/cjs/utils/id.js.map +1 -1
  87. package/dist/cjs/utils/schema.d.ts +17 -0
  88. package/dist/cjs/utils/schema.d.ts.map +1 -0
  89. package/dist/cjs/utils/schema.js +32 -0
  90. package/dist/cjs/utils/schema.js.map +1 -0
  91. package/dist/core/Agent.d.ts +22 -29
  92. package/dist/core/Agent.d.ts.map +1 -1
  93. package/dist/core/Agent.js +465 -275
  94. package/dist/core/Agent.js.map +1 -1
  95. package/dist/core/Events.d.ts +10 -1
  96. package/dist/core/Events.d.ts.map +1 -1
  97. package/dist/core/Events.js +3 -2
  98. package/dist/core/Events.js.map +1 -1
  99. package/dist/core/PersistenceManager.d.ts +19 -0
  100. package/dist/core/PersistenceManager.d.ts.map +1 -1
  101. package/dist/core/PersistenceManager.js +57 -0
  102. package/dist/core/PersistenceManager.js.map +1 -1
  103. package/dist/core/PromptComposer.d.ts +24 -0
  104. package/dist/core/PromptComposer.d.ts.map +1 -0
  105. package/dist/core/PromptComposer.js +123 -0
  106. package/dist/core/PromptComposer.js.map +1 -0
  107. package/dist/core/ResponseEngine.d.ts +19 -0
  108. package/dist/core/ResponseEngine.d.ts.map +1 -0
  109. package/dist/core/ResponseEngine.js +47 -0
  110. package/dist/core/ResponseEngine.js.map +1 -0
  111. package/dist/core/Route.d.ts +18 -12
  112. package/dist/core/Route.d.ts.map +1 -1
  113. package/dist/core/Route.js +15 -9
  114. package/dist/core/Route.js.map +1 -1
  115. package/dist/core/RoutingEngine.d.ts +38 -0
  116. package/dist/core/RoutingEngine.d.ts.map +1 -0
  117. package/dist/core/RoutingEngine.js +106 -0
  118. package/dist/core/RoutingEngine.js.map +1 -0
  119. package/dist/core/State.d.ts +15 -4
  120. package/dist/core/State.d.ts.map +1 -1
  121. package/dist/core/State.js +21 -2
  122. package/dist/core/State.js.map +1 -1
  123. package/dist/core/ToolExecutor.d.ts +29 -0
  124. package/dist/core/ToolExecutor.d.ts.map +1 -0
  125. package/dist/core/ToolExecutor.js +69 -0
  126. package/dist/core/ToolExecutor.js.map +1 -0
  127. package/dist/core/Transition.d.ts +5 -5
  128. package/dist/core/Transition.d.ts.map +1 -1
  129. package/dist/core/Transition.js.map +1 -1
  130. package/dist/index.d.ts +6 -8
  131. package/dist/index.d.ts.map +1 -1
  132. package/dist/index.js +3 -5
  133. package/dist/index.js.map +1 -1
  134. package/dist/providers/AnthropicProvider.d.ts.map +1 -1
  135. package/dist/providers/AnthropicProvider.js +10 -13
  136. package/dist/providers/AnthropicProvider.js.map +1 -1
  137. package/dist/providers/GeminiProvider.d.ts.map +1 -1
  138. package/dist/providers/GeminiProvider.js +12 -8
  139. package/dist/providers/GeminiProvider.js.map +1 -1
  140. package/dist/providers/OpenAIProvider.d.ts.map +1 -1
  141. package/dist/providers/OpenAIProvider.js +10 -53
  142. package/dist/providers/OpenAIProvider.js.map +1 -1
  143. package/dist/providers/OpenRouterProvider.d.ts.map +1 -1
  144. package/dist/providers/OpenRouterProvider.js +10 -53
  145. package/dist/providers/OpenRouterProvider.js.map +1 -1
  146. package/dist/types/agent.d.ts +13 -9
  147. package/dist/types/agent.d.ts.map +1 -1
  148. package/dist/types/ai.d.ts +8 -2
  149. package/dist/types/ai.d.ts.map +1 -1
  150. package/dist/types/history.d.ts +8 -0
  151. package/dist/types/history.d.ts.map +1 -1
  152. package/dist/types/index.d.ts +0 -3
  153. package/dist/types/index.d.ts.map +1 -1
  154. package/dist/types/index.js +0 -1
  155. package/dist/types/index.js.map +1 -1
  156. package/dist/types/route.d.ts +39 -4
  157. package/dist/types/route.d.ts.map +1 -1
  158. package/dist/types/routing.d.ts +16 -0
  159. package/dist/types/routing.d.ts.map +1 -0
  160. package/dist/types/routing.js +2 -0
  161. package/dist/types/routing.js.map +1 -0
  162. package/dist/types/schema.d.ts +22 -0
  163. package/dist/types/schema.d.ts.map +1 -0
  164. package/dist/types/schema.js +2 -0
  165. package/dist/types/schema.js.map +1 -0
  166. package/dist/types/session.d.ts +72 -0
  167. package/dist/types/session.d.ts.map +1 -0
  168. package/dist/types/session.js +132 -0
  169. package/dist/types/session.js.map +1 -0
  170. package/dist/types/tool.d.ts +11 -5
  171. package/dist/types/tool.d.ts.map +1 -1
  172. package/dist/utils/id.d.ts +0 -5
  173. package/dist/utils/id.d.ts.map +1 -1
  174. package/dist/utils/id.js +0 -9
  175. package/dist/utils/id.js.map +1 -1
  176. package/dist/utils/schema.d.ts +17 -0
  177. package/dist/utils/schema.d.ts.map +1 -0
  178. package/dist/utils/schema.js +27 -0
  179. package/dist/utils/schema.js.map +1 -0
  180. package/docs/ADAPTERS.md +83 -3
  181. package/docs/API_REFERENCE.md +95 -104
  182. package/docs/ARCHITECTURE.md +284 -286
  183. package/docs/CONSTRUCTOR_OPTIONS.md +192 -135
  184. package/docs/CONTEXT_MANAGEMENT.md +311 -28
  185. package/docs/CONTRIBUTING.md +1 -1
  186. package/docs/DOMAINS.md +61 -0
  187. package/docs/GETTING_STARTED.md +177 -88
  188. package/docs/PERSISTENCE.md +170 -23
  189. package/docs/README.md +7 -10
  190. package/examples/business-onboarding.ts +21 -9
  191. package/examples/company-qna-agent.ts +508 -0
  192. package/examples/declarative-agent.ts +143 -26
  193. package/examples/domain-scoping.ts +31 -10
  194. package/examples/extracted-data-modification.ts +415 -0
  195. package/examples/healthcare-agent.ts +194 -90
  196. package/examples/openai-agent.ts +67 -25
  197. package/examples/opensearch-persistence.ts +455 -151
  198. package/examples/persistent-onboarding.ts +162 -96
  199. package/examples/prisma-persistence.ts +371 -125
  200. package/examples/redis-persistence.ts +393 -23
  201. package/examples/rules-prohibitions.ts +32 -11
  202. package/examples/streaming-agent.ts +61 -13
  203. package/examples/travel-agent.ts +266 -133
  204. package/package.json +1 -1
  205. package/src/core/Agent.ts +679 -332
  206. package/src/core/Events.ts +12 -2
  207. package/src/core/PersistenceManager.ts +83 -0
  208. package/src/core/PromptComposer.ts +143 -0
  209. package/src/core/ResponseEngine.ts +82 -0
  210. package/src/core/Route.ts +32 -17
  211. package/src/core/RoutingEngine.ts +165 -0
  212. package/src/core/State.ts +55 -15
  213. package/src/core/ToolExecutor.ts +117 -0
  214. package/src/core/Transition.ts +5 -5
  215. package/src/index.ts +12 -21
  216. package/src/providers/AnthropicProvider.ts +10 -13
  217. package/src/providers/GeminiProvider.ts +12 -8
  218. package/src/providers/OpenAIProvider.ts +10 -56
  219. package/src/providers/OpenRouterProvider.ts +10 -56
  220. package/src/types/agent.ts +16 -10
  221. package/src/types/ai.ts +6 -2
  222. package/src/types/history.ts +8 -0
  223. package/src/types/index.ts +0 -11
  224. package/src/types/route.ts +41 -5
  225. package/src/types/routing.ts +18 -0
  226. package/src/types/schema.ts +23 -0
  227. package/src/types/session.ts +207 -0
  228. package/src/types/tool.ts +29 -7
  229. package/src/utils/id.ts +0 -10
  230. package/src/utils/schema.ts +32 -0
  231. package/dist/cjs/core/ConditionEvaluator.d.ts +0 -72
  232. package/dist/cjs/core/ConditionEvaluator.d.ts.map +0 -1
  233. package/dist/cjs/core/ConditionEvaluator.js +0 -272
  234. package/dist/cjs/core/ConditionEvaluator.js.map +0 -1
  235. package/dist/cjs/core/Observation.d.ts +0 -24
  236. package/dist/cjs/core/Observation.d.ts.map +0 -1
  237. package/dist/cjs/core/Observation.js +0 -39
  238. package/dist/cjs/core/Observation.js.map +0 -1
  239. package/dist/cjs/core/PreparationEngine.d.ts +0 -105
  240. package/dist/cjs/core/PreparationEngine.d.ts.map +0 -1
  241. package/dist/cjs/core/PreparationEngine.js +0 -320
  242. package/dist/cjs/core/PreparationEngine.js.map +0 -1
  243. package/dist/cjs/core/PromptBuilder.d.ts +0 -136
  244. package/dist/cjs/core/PromptBuilder.d.ts.map +0 -1
  245. package/dist/cjs/core/PromptBuilder.js +0 -421
  246. package/dist/cjs/core/PromptBuilder.js.map +0 -1
  247. package/dist/cjs/types/observation.d.ts +0 -27
  248. package/dist/cjs/types/observation.d.ts.map +0 -1
  249. package/dist/cjs/types/observation.js +0 -6
  250. package/dist/cjs/types/observation.js.map +0 -1
  251. package/dist/cjs/types/prompt.d.ts +0 -46
  252. package/dist/cjs/types/prompt.d.ts.map +0 -1
  253. package/dist/cjs/types/prompt.js +0 -19
  254. package/dist/cjs/types/prompt.js.map +0 -1
  255. package/dist/core/ConditionEvaluator.d.ts +0 -72
  256. package/dist/core/ConditionEvaluator.d.ts.map +0 -1
  257. package/dist/core/ConditionEvaluator.js +0 -268
  258. package/dist/core/ConditionEvaluator.js.map +0 -1
  259. package/dist/core/Observation.d.ts +0 -24
  260. package/dist/core/Observation.d.ts.map +0 -1
  261. package/dist/core/Observation.js +0 -35
  262. package/dist/core/Observation.js.map +0 -1
  263. package/dist/core/PreparationEngine.d.ts +0 -105
  264. package/dist/core/PreparationEngine.d.ts.map +0 -1
  265. package/dist/core/PreparationEngine.js +0 -316
  266. package/dist/core/PreparationEngine.js.map +0 -1
  267. package/dist/core/PromptBuilder.d.ts +0 -136
  268. package/dist/core/PromptBuilder.d.ts.map +0 -1
  269. package/dist/core/PromptBuilder.js +0 -417
  270. package/dist/core/PromptBuilder.js.map +0 -1
  271. package/dist/types/observation.d.ts +0 -27
  272. package/dist/types/observation.d.ts.map +0 -1
  273. package/dist/types/observation.js +0 -5
  274. package/dist/types/observation.js.map +0 -1
  275. package/dist/types/prompt.d.ts +0 -46
  276. package/dist/types/prompt.d.ts.map +0 -1
  277. package/dist/types/prompt.js +0 -16
  278. package/dist/types/prompt.js.map +0 -1
  279. package/docs/STRUCTURE.md +0 -58
  280. package/src/core/ConditionEvaluator.ts +0 -381
  281. package/src/core/Observation.ts +0 -47
  282. package/src/core/PreparationEngine.ts +0 -500
  283. package/src/core/PromptBuilder.ts +0 -617
  284. package/src/types/observation.ts +0 -29
  285. package/src/types/prompt.ts +0 -49
@@ -1,12 +1,27 @@
1
1
  /**
2
2
  * Core Agent implementation
3
3
  */
4
+ import { createSession, enterRoute, enterState, mergeExtracted, } from "../types/session";
5
+ import { EventKind } from "../types/history";
6
+ import { PromptComposer } from "./PromptComposer";
4
7
  import { Route } from "./Route";
5
8
  import { DomainRegistry } from "./DomainRegistry";
6
- import { PromptBuilder } from "./PromptBuilder";
7
- import { Observation } from "./Observation";
8
9
  import { PersistenceManager } from "./PersistenceManager";
9
- import { PreparationEngine } from "./PreparationEngine";
10
+ import { RoutingEngine } from "./RoutingEngine";
11
+ import { ResponseEngine } from "./ResponseEngine";
12
+ import { ToolExecutor } from "./ToolExecutor";
13
+ /**
14
+ * Helper to extract last message from history
15
+ */
16
+ function getLastMessageFromHistory(history) {
17
+ for (let i = history.length - 1; i >= 0; i--) {
18
+ const event = history[i];
19
+ if (event.kind === EventKind.MESSAGE) {
20
+ return event.data.message;
21
+ }
22
+ }
23
+ return "";
24
+ }
10
25
  /**
11
26
  * Main Agent class with generic context support
12
27
  */
@@ -16,25 +31,26 @@ export class Agent {
16
31
  this.terms = [];
17
32
  this.guidelines = [];
18
33
  this.capabilities = [];
34
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
19
35
  this.routes = [];
20
- this.observations = [];
21
36
  this.domainRegistry = new DomainRegistry();
22
37
  /**
23
38
  * Dynamic domain property - populated via addDomain
24
39
  */
25
40
  this.domain = {};
26
- // Initialize with default values
27
- if (!this.options.maxEngineIterations) {
28
- this.options.maxEngineIterations = 1;
29
- }
30
41
  // Validate context configuration
31
42
  if (options.context !== undefined && options.contextProvider) {
32
43
  throw new Error("Cannot provide both 'context' and 'contextProvider'. Choose one.");
33
44
  }
34
45
  // Initialize context if provided
35
46
  this.context = options.context;
36
- // Initialize preparation engine with AI provider
37
- this.preparationEngine = new PreparationEngine(options.ai);
47
+ // Initialize routing and response engines
48
+ this.routingEngine = new RoutingEngine({
49
+ maxCandidates: 5,
50
+ allowRouteSwitch: true,
51
+ switchThreshold: 70,
52
+ });
53
+ this.responseEngine = new ResponseEngine();
38
54
  // Initialize persistence if configured
39
55
  if (options.persistence) {
40
56
  this.persistenceManager = new PersistenceManager(options.persistence);
@@ -64,23 +80,6 @@ export class Agent {
64
80
  this.createRoute(routeOptions);
65
81
  });
66
82
  }
67
- if (options.observations) {
68
- options.observations.forEach((obsOptions) => {
69
- const obs = this.createObservation(obsOptions.description);
70
- // If route refs were provided, resolve and disambiguate
71
- if (obsOptions.routeRefs && obsOptions.routeRefs.length > 0) {
72
- const resolvedRoutes = obsOptions.routeRefs
73
- .map((ref) => {
74
- // Try to find route by ID or title
75
- return this.routes.find((r) => r.id === ref || r.title === ref);
76
- })
77
- .filter((r) => r !== undefined);
78
- if (resolvedRoutes.length > 0) {
79
- obs.disambiguate(resolvedRoutes);
80
- }
81
- }
82
- });
83
- }
84
83
  }
85
84
  /**
86
85
  * Get agent name
@@ -102,6 +101,7 @@ export class Agent {
102
101
  }
103
102
  /**
104
103
  * Create a new route (journey)
104
+ * @template TExtracted - Type of data extracted throughout the route
105
105
  */
106
106
  createRoute(options) {
107
107
  const route = new Route(options);
@@ -138,21 +138,28 @@ export class Agent {
138
138
  this.capabilities.push(capabilityWithId);
139
139
  return this;
140
140
  }
141
- /**
142
- * Create an observation for disambiguation
143
- */
144
- createObservation(description) {
145
- const observation = new Observation({ description });
146
- this.observations.push(observation);
147
- return observation;
148
- }
149
141
  /**
150
142
  * Add a domain with its tools/methods
143
+ * Automatically tags all ToolRef objects with their domain name for security enforcement
151
144
  */
152
145
  addDomain(name, domainObject) {
153
- this.domainRegistry.register(name, domainObject);
146
+ // Tag all tools in this domain with the domain name for security enforcement
147
+ const taggedDomain = { ...domainObject };
148
+ for (const key in taggedDomain) {
149
+ const value = taggedDomain[key];
150
+ // Check if value is a ToolRef (has handler, id, name properties)
151
+ if (value &&
152
+ typeof value === "object" &&
153
+ "handler" in value &&
154
+ "id" in value &&
155
+ "name" in value) {
156
+ // Tag the tool with its domain name
157
+ value.domainName = name;
158
+ }
159
+ }
160
+ this.domainRegistry.register(name, taggedDomain);
154
161
  // Attach to the domain property for easy access
155
- this.domain[name] = domainObject;
162
+ this.domain[name] = taggedDomain;
156
163
  }
157
164
  /**
158
165
  * Update the agent's context
@@ -170,6 +177,26 @@ export class Agent {
170
177
  await this.options.hooks.onContextUpdate(this.context, previousContext);
171
178
  }
172
179
  }
180
+ /**
181
+ * Update extracted data in session with lifecycle hook support
182
+ * Triggers the onExtractedUpdate lifecycle hook if configured
183
+ * @internal
184
+ */
185
+ async updateExtracted(session, extractedUpdate) {
186
+ const previousExtracted = { ...session.extracted };
187
+ // Merge new extracted data
188
+ let newExtracted = {
189
+ ...session.extracted,
190
+ ...extractedUpdate,
191
+ };
192
+ // Trigger lifecycle hook if configured
193
+ if (this.options.hooks?.onExtractedUpdate) {
194
+ const updatedExtracted = (await this.options.hooks.onExtractedUpdate(newExtracted, previousExtracted));
195
+ newExtracted = updatedExtracted;
196
+ }
197
+ // Return updated session
198
+ return mergeExtracted(session, newExtracted);
199
+ }
173
200
  /**
174
201
  * Get current context (fetches from provider if configured)
175
202
  * @internal
@@ -182,6 +209,46 @@ export class Agent {
182
209
  // Otherwise return the stored context
183
210
  return this.context;
184
211
  }
212
+ /**
213
+ * Determine the next state in a route based on extracted data
214
+ * @internal
215
+ */
216
+ getNextState(route, currentState, extracted) {
217
+ // If no current state, start from initial state
218
+ if (!currentState) {
219
+ // Check if initial state should be skipped
220
+ if (route.initialState.shouldSkip(extracted)) {
221
+ return this.getNextState(route, route.initialState, extracted);
222
+ }
223
+ return route.initialState;
224
+ }
225
+ // Get transitions from current state
226
+ const transitions = currentState.getTransitions();
227
+ // If no transitions, stay in current state
228
+ if (transitions.length === 0) {
229
+ return currentState;
230
+ }
231
+ // Try to find the next state to transition to
232
+ for (const transition of transitions) {
233
+ const target = transition.getTarget();
234
+ if (!target)
235
+ continue;
236
+ // Check if target state should be skipped
237
+ if (target.shouldSkip(extracted)) {
238
+ // Recursively find next non-skipped state
239
+ return this.getNextState(route, target, extracted);
240
+ }
241
+ // Check if target state has required data
242
+ if (!target.hasRequiredData(extracted)) {
243
+ // Cannot enter this state yet, stay in current state
244
+ continue;
245
+ }
246
+ // Found valid next state
247
+ return target;
248
+ }
249
+ // No valid transition found, stay in current state
250
+ return currentState;
251
+ }
185
252
  /**
186
253
  * Generate a response based on history and context as a stream
187
254
  */
@@ -196,138 +263,208 @@ export class Agent {
196
263
  this.context = currentContext;
197
264
  }
198
265
  // Merge context with override
199
- let effectiveContext = {
266
+ const effectiveContext = {
200
267
  ...currentContext,
201
268
  ...contextOverride,
202
269
  };
203
- // RUN PREPARATION ITERATIONS
204
- // This is where tools execute automatically based on:
205
- // 1. Matched guidelines with associated tools
206
- // 2. State machine transitions with toolState
207
- //
208
- // The AI will NEVER see these tools - they execute before message generation
209
- const preparationResult = await this.preparationEngine.prepare({
210
- history,
211
- currentState: params.state,
212
- context: effectiveContext,
213
- routes: this.routes,
214
- guidelines: this.guidelines,
215
- maxIterations: this.options.maxEngineIterations || 1,
216
- });
217
- // Update context with results from tool executions
218
- effectiveContext = preparationResult.finalContext;
219
- // Log tool executions for debugging
220
- if (preparationResult.toolExecutions.length > 0) {
221
- console.log(`[Agent] Preparation complete: ${preparationResult.toolExecutions.length} tools executed in ${preparationResult.iterations.length} iterations`);
270
+ // Initialize or get session
271
+ let session = params.session || createSession();
272
+ // PHASE 1: TOOL EXECUTION - Execute tools if current state has toolState
273
+ if (session.currentRoute && session.currentState) {
274
+ const currentRoute = this.routes.find((r) => r.id === session.currentRoute?.id);
275
+ if (currentRoute) {
276
+ const currentState = currentRoute.getState(session.currentState.id);
277
+ if (currentState) {
278
+ const transitions = currentState.getTransitions();
279
+ const toolTransition = transitions.find((t) => t.spec.toolState);
280
+ if (toolTransition?.spec.toolState) {
281
+ const toolExecutor = new ToolExecutor();
282
+ // Get allowed domains from current route for security enforcement
283
+ const allowedDomains = currentRoute.getDomains();
284
+ const result = await toolExecutor.executeTool(toolTransition.spec.toolState, effectiveContext, this.updateContext.bind(this), history, session.extracted, allowedDomains);
285
+ // Update context with tool results
286
+ if (result.contextUpdate) {
287
+ await this.updateContext(result.contextUpdate);
288
+ }
289
+ // Update extracted data with tool results
290
+ if (result.extractedUpdate) {
291
+ session = await this.updateExtracted(session, result.extractedUpdate);
292
+ console.log(`[Agent] Tool updated extracted data:`, result.extractedUpdate);
293
+ }
294
+ console.log(`[Agent] Executed tool: ${result.toolName} (success: ${result.success})`);
295
+ }
296
+ }
297
+ }
222
298
  }
223
- // Build prompt (same as respond method)
224
- const promptBuilder = new PromptBuilder();
225
- // Add agent identity
226
- if (this.options.description) {
227
- promptBuilder.addAgentIdentity({
299
+ // PHASE 2: ROUTING - Determine which route to use
300
+ let selectedRoute;
301
+ let responseDirectives;
302
+ if (this.routes.length > 0) {
303
+ // Get last user message
304
+ const lastUserMessage = getLastMessageFromHistory(history);
305
+ // Build routing schema
306
+ const routingSchema = this.routingEngine.buildDynamicRoutingSchema(this.routes);
307
+ // Build routing prompt with session context
308
+ const routingPrompt = this.routingEngine.buildRoutingPrompt(history, this.routes, lastUserMessage, {
228
309
  name: this.options.name,
310
+ goal: this.options.goal,
229
311
  description: this.options.description,
312
+ personality: this.options.personality,
313
+ }, session // Pass session for context-aware routing
314
+ );
315
+ // Call AI to score routes (non-streaming for routing decision)
316
+ const routingResult = await this.options.ai.generateMessage({
317
+ prompt: routingPrompt,
318
+ history,
319
+ context: effectiveContext,
320
+ signal,
321
+ parameters: {
322
+ jsonSchema: routingSchema,
323
+ schemaName: "routing_output",
324
+ },
230
325
  });
231
- }
232
- // Add interaction history
233
- promptBuilder.addInteractionHistoryForMessageGeneration(history);
234
- // Add glossary
235
- if (this.terms.length > 0) {
236
- promptBuilder.addGlossary(this.terms);
237
- }
238
- // Add guidelines (convert to GuidelineMatch format, filter enabled only)
239
- const enabledGuidelines = this.guidelines.filter((g) => g.enabled !== false);
240
- if (enabledGuidelines.length > 0) {
241
- const guidelineMatches = enabledGuidelines.map((g) => ({
242
- guideline: g,
243
- }));
244
- promptBuilder.addGuidelinesForMessageGeneration(guidelineMatches);
245
- }
246
- // Add capabilities
247
- if (this.capabilities.length > 0) {
248
- promptBuilder.addCapabilitiesForMessageGeneration(this.capabilities);
249
- }
250
- // Add observations
251
- if (this.observations.length > 0) {
252
- const observationsWithRoutes = this.observations
253
- .map((obs) => ({
254
- description: obs.description,
255
- routes: obs.getRoutes().map((routeRef) => {
256
- const route = this.routes.find((r) => r.id === routeRef.id);
257
- return { title: route?.title || routeRef.id };
258
- }),
259
- }))
260
- .filter((obs) => obs.routes.length > 0);
261
- if (observationsWithRoutes.length > 0) {
262
- promptBuilder.addObservations(observationsWithRoutes);
326
+ // Select best route from scores
327
+ if (routingResult.structured?.routes) {
328
+ const decision = this.routingEngine.decideRouteFromScores({
329
+ context: routingResult.structured.context,
330
+ routes: routingResult.structured.routes,
331
+ responseDirectives: routingResult.structured.responseDirectives,
332
+ });
333
+ selectedRoute = this.routes.find((r) => r.id === decision.routeId);
334
+ responseDirectives = routingResult.structured.responseDirectives;
335
+ if (selectedRoute) {
336
+ console.log(`[Agent] Selected route: ${selectedRoute.title} (score: ${decision.maxScore})`);
337
+ // Update session with selected route (if changed)
338
+ if (!session.currentRoute ||
339
+ session.currentRoute.id !== selectedRoute.id) {
340
+ session = enterRoute(session, selectedRoute.id, selectedRoute.title);
341
+ // Merge initial data if provided by the route
342
+ if (selectedRoute.initialData) {
343
+ session = mergeExtracted(session, selectedRoute.initialData);
344
+ console.log(`[Agent] Merged initial data:`, selectedRoute.initialData);
345
+ }
346
+ console.log(`[Agent] Entered route: ${selectedRoute.title}`);
347
+ }
348
+ }
263
349
  }
264
350
  }
265
- // Add active routes with their rules and prohibitions
266
- if (this.routes.length > 0) {
267
- promptBuilder.addActiveRoutes(this.routes.map((r) => ({
268
- title: r.title,
269
- description: r.description,
270
- conditions: r.conditions,
271
- domains: r.getDomains(),
272
- rules: r.getRules(),
273
- prohibitions: r.getProhibitions(),
274
- })));
275
- }
276
- // NOTE: Domains/tools are NOT added to the prompt.
277
- // Tools execute automatically based on state transitions and guideline matching,
278
- // NOT based on AI decisions. The AI should never see available tools.
279
- // Add JSON response schema instructions
280
- promptBuilder.addJsonResponseSchema();
281
- // Build final prompt
282
- const prompt = promptBuilder.build();
283
- // Generate message stream using AI provider with JSON mode enabled
284
- const stream = this.options.ai.generateMessageStream({
285
- prompt,
286
- history,
287
- context: effectiveContext,
288
- signal,
289
- parameters: {
290
- jsonMode: true,
291
- },
292
- });
293
- // Stream chunks to caller
294
- for await (const chunk of stream) {
295
- // Extract route and state from structured response on final chunk
296
- let route = null;
297
- let state = null;
298
- let toolCalls;
299
- if (chunk.done && chunk.structured) {
300
- // Find route by title
301
- if (chunk.structured.route) {
302
- const foundRoute = this.routes.find((r) => r.title === chunk.structured?.route);
303
- if (foundRoute) {
304
- route = {
305
- id: foundRoute.id,
306
- title: foundRoute.title,
307
- };
351
+ // PHASE 3: RESPONSE - Stream message using selected route
352
+ if (selectedRoute) {
353
+ // Determine next state based on current extracted data
354
+ const currentStateRef = session.currentState;
355
+ const currentState = currentStateRef
356
+ ? selectedRoute.getState(currentStateRef.id)
357
+ : undefined;
358
+ const nextState = this.getNextState(selectedRoute, currentState, session.extracted);
359
+ // Update session with next state
360
+ session = enterState(session, nextState.id, nextState.description);
361
+ console.log(`[Agent] Entered state: ${nextState.id}`);
362
+ // Get last user message
363
+ const lastUserMessage = getLastMessageFromHistory(history);
364
+ // Build response schema for this route (with gather fields from state)
365
+ const responseSchema = this.responseEngine.responseSchemaForRoute(selectedRoute, nextState);
366
+ // Build response prompt
367
+ const responsePrompt = this.responseEngine.buildResponsePrompt(selectedRoute, selectedRoute.getRules(), selectedRoute.getProhibitions(), responseDirectives, history, lastUserMessage, {
368
+ name: this.options.name,
369
+ goal: this.options.goal,
370
+ description: this.options.description,
371
+ personality: this.options.personality,
372
+ });
373
+ // Generate message stream using AI provider
374
+ const stream = this.options.ai.generateMessageStream({
375
+ prompt: responsePrompt,
376
+ history,
377
+ context: effectiveContext,
378
+ signal,
379
+ parameters: {
380
+ jsonSchema: responseSchema,
381
+ schemaName: "response_stream_output",
382
+ },
383
+ });
384
+ // Stream chunks to caller
385
+ for await (const chunk of stream) {
386
+ const toolCalls = undefined;
387
+ // Extract gathered data on final chunk
388
+ if (chunk.done && chunk.structured && nextState.gatherFields) {
389
+ const gatheredData = {};
390
+ // The structured response includes both base fields and gathered extraction fields
391
+ const structuredData = chunk.structured;
392
+ for (const field of nextState.gatherFields) {
393
+ if (field in structuredData) {
394
+ gatheredData[field] = structuredData[field];
395
+ }
396
+ }
397
+ // Merge gathered data into session
398
+ if (Object.keys(gatheredData).length > 0) {
399
+ session = await this.updateExtracted(session, gatheredData);
400
+ console.log(`[Agent] Extracted data:`, gatheredData);
308
401
  }
309
402
  }
310
- // Create state reference if provided
311
- if (chunk.structured.state) {
312
- state = {
313
- id: "dynamic_state",
314
- description: chunk.structured.state,
315
- };
403
+ // Extract any additional data from structured response on final chunk
404
+ if (chunk.done &&
405
+ chunk.structured &&
406
+ typeof chunk.structured === "object" &&
407
+ "contextUpdate" in chunk.structured) {
408
+ await this.updateContext(chunk.structured
409
+ .contextUpdate);
316
410
  }
317
- // Extract tool calls
318
- if (chunk.structured.toolCalls &&
319
- chunk.structured.toolCalls.length > 0) {
320
- toolCalls = chunk.structured.toolCalls;
411
+ // Auto-save session state on final chunk
412
+ if (chunk.done &&
413
+ this.persistenceManager &&
414
+ session.metadata?.sessionId &&
415
+ this.options.persistence?.autoSave !== false) {
416
+ await this.persistenceManager.saveSessionState(session.metadata.sessionId, session);
417
+ console.log(`[Agent] Auto-saved session state to persistence: ${session.metadata.sessionId}`);
321
418
  }
419
+ yield {
420
+ delta: chunk.delta,
421
+ accumulated: chunk.accumulated,
422
+ done: chunk.done,
423
+ session, // Return updated session
424
+ toolCalls,
425
+ };
426
+ }
427
+ }
428
+ else {
429
+ // Fallback: No routes defined, stream a simple response
430
+ const fallbackPrompt = new PromptComposer()
431
+ .addAgentMeta({
432
+ name: this.options.name,
433
+ goal: this.options.goal,
434
+ description: this.options.description,
435
+ })
436
+ .addPersonality(this.options.personality)
437
+ .addInteractionHistory(history)
438
+ .addGlossary(this.terms)
439
+ .addGuidelines(this.guidelines)
440
+ .addCapabilities(this.capabilities)
441
+ .build();
442
+ const stream = this.options.ai.generateMessageStream({
443
+ prompt: fallbackPrompt,
444
+ history,
445
+ context: effectiveContext,
446
+ signal,
447
+ parameters: {
448
+ jsonSchema: {
449
+ type: "object",
450
+ properties: {
451
+ message: { type: "string" },
452
+ },
453
+ required: ["message"],
454
+ additionalProperties: false,
455
+ },
456
+ schemaName: "fallback_stream_response",
457
+ },
458
+ });
459
+ for await (const chunk of stream) {
460
+ yield {
461
+ delta: chunk.delta,
462
+ accumulated: chunk.accumulated,
463
+ done: chunk.done,
464
+ session, // Return updated session
465
+ toolCalls: undefined,
466
+ };
322
467
  }
323
- yield {
324
- delta: chunk.delta,
325
- accumulated: chunk.accumulated,
326
- done: chunk.done,
327
- route: route || undefined,
328
- state: state || undefined,
329
- toolCalls,
330
- };
331
468
  }
332
469
  }
333
470
  /**
@@ -344,135 +481,194 @@ export class Agent {
344
481
  this.context = currentContext;
345
482
  }
346
483
  // Merge context with override
347
- let effectiveContext = {
484
+ const effectiveContext = {
348
485
  ...currentContext,
349
486
  ...contextOverride,
350
487
  };
351
- // RUN PREPARATION ITERATIONS
352
- // This is where tools execute automatically based on:
353
- // 1. Matched guidelines with associated tools
354
- // 2. State machine transitions with toolState
355
- //
356
- // The AI will NEVER see these tools - they execute before message generation
357
- const preparationResult = await this.preparationEngine.prepare({
358
- history,
359
- currentState: params.state,
360
- context: effectiveContext,
361
- routes: this.routes,
362
- guidelines: this.guidelines,
363
- maxIterations: this.options.maxEngineIterations || 1,
364
- });
365
- // Update context with results from tool executions
366
- effectiveContext = preparationResult.finalContext;
367
- // Log tool executions for debugging
368
- if (preparationResult.toolExecutions.length > 0) {
369
- console.log(`[Agent] Preparation complete: ${preparationResult.toolExecutions.length} tools executed in ${preparationResult.iterations.length} iterations`);
488
+ // Initialize or get session
489
+ let session = params.session || createSession();
490
+ // PHASE 1: TOOL EXECUTION - Execute tools if current state has toolState
491
+ if (session.currentRoute && session.currentState) {
492
+ const currentRoute = this.routes.find((r) => r.id === session.currentRoute?.id);
493
+ if (currentRoute) {
494
+ const currentState = currentRoute.getState(session.currentState.id);
495
+ if (currentState) {
496
+ const transitions = currentState.getTransitions();
497
+ const toolTransition = transitions.find((t) => t.spec.toolState);
498
+ if (toolTransition?.spec.toolState) {
499
+ const toolExecutor = new ToolExecutor();
500
+ // Get allowed domains from current route for security enforcement
501
+ const allowedDomains = currentRoute.getDomains();
502
+ const result = await toolExecutor.executeTool(toolTransition.spec.toolState, effectiveContext, this.updateContext.bind(this), history, session.extracted, allowedDomains);
503
+ // Update context with tool results
504
+ if (result.contextUpdate) {
505
+ await this.updateContext(result.contextUpdate);
506
+ }
507
+ // Update extracted data with tool results
508
+ if (result.extractedUpdate) {
509
+ session = await this.updateExtracted(session, result.extractedUpdate);
510
+ console.log(`[Agent] Tool updated extracted data:`, result.extractedUpdate);
511
+ }
512
+ console.log(`[Agent] Executed tool: ${result.toolName} (success: ${result.success})`);
513
+ }
514
+ }
515
+ }
370
516
  }
371
- // Build prompt
372
- const promptBuilder = new PromptBuilder();
373
- // Add agent identity
374
- if (this.options.description) {
375
- promptBuilder.addAgentIdentity({
517
+ // PHASE 2: ROUTING - Determine which route to use
518
+ let selectedRoute;
519
+ let responseDirectives;
520
+ if (this.routes.length > 0) {
521
+ // Get last user message
522
+ const lastUserMessage = getLastMessageFromHistory(history);
523
+ // Build routing schema
524
+ const routingSchema = this.routingEngine.buildDynamicRoutingSchema(this.routes);
525
+ // Build routing prompt with session context
526
+ const routingPrompt = this.routingEngine.buildRoutingPrompt(history, this.routes, lastUserMessage, {
376
527
  name: this.options.name,
528
+ goal: this.options.goal,
377
529
  description: this.options.description,
530
+ personality: this.options.personality,
531
+ }, session // Pass session for context-aware routing
532
+ );
533
+ // Call AI to score routes
534
+ const routingResult = await this.options.ai.generateMessage({
535
+ prompt: routingPrompt,
536
+ history,
537
+ context: effectiveContext,
538
+ signal,
539
+ parameters: {
540
+ jsonSchema: routingSchema,
541
+ schemaName: "routing_output",
542
+ },
378
543
  });
379
- }
380
- // Add interaction history
381
- promptBuilder.addInteractionHistoryForMessageGeneration(history);
382
- // Add glossary
383
- if (this.terms.length > 0) {
384
- promptBuilder.addGlossary(this.terms);
385
- }
386
- // Add guidelines (convert to GuidelineMatch format, filter enabled only)
387
- const enabledGuidelines = this.guidelines.filter((g) => g.enabled !== false);
388
- if (enabledGuidelines.length > 0) {
389
- const guidelineMatches = enabledGuidelines.map((g) => ({
390
- guideline: g,
391
- }));
392
- promptBuilder.addGuidelinesForMessageGeneration(guidelineMatches);
393
- }
394
- // Add capabilities
395
- if (this.capabilities.length > 0) {
396
- promptBuilder.addCapabilitiesForMessageGeneration(this.capabilities);
397
- }
398
- // Add observations
399
- if (this.observations.length > 0) {
400
- const observationsWithRoutes = this.observations
401
- .map((obs) => ({
402
- description: obs.description,
403
- routes: obs.getRoutes().map((routeRef) => {
404
- const route = this.routes.find((r) => r.id === routeRef.id);
405
- return { title: route?.title || routeRef.id };
406
- }),
407
- }))
408
- .filter((obs) => obs.routes.length > 0);
409
- if (observationsWithRoutes.length > 0) {
410
- promptBuilder.addObservations(observationsWithRoutes);
544
+ // Select best route from scores
545
+ if (routingResult.structured?.routes) {
546
+ const decision = this.routingEngine.decideRouteFromScores({
547
+ context: routingResult.structured.context,
548
+ routes: routingResult.structured.routes,
549
+ responseDirectives: routingResult.structured.responseDirectives,
550
+ });
551
+ selectedRoute = this.routes.find((r) => r.id === decision.routeId);
552
+ responseDirectives = routingResult.structured.responseDirectives;
553
+ if (selectedRoute) {
554
+ console.log(`[Agent] Selected route: ${selectedRoute.title} (score: ${decision.maxScore})`);
555
+ // Update session with selected route (if changed)
556
+ if (!session.currentRoute ||
557
+ session.currentRoute.id !== selectedRoute.id) {
558
+ session = enterRoute(session, selectedRoute.id, selectedRoute.title);
559
+ // Merge initial data if provided by the route
560
+ if (selectedRoute.initialData) {
561
+ session = mergeExtracted(session, selectedRoute.initialData);
562
+ console.log(`[Agent] Merged initial data:`, selectedRoute.initialData);
563
+ }
564
+ console.log(`[Agent] Entered route: ${selectedRoute.title}`);
565
+ }
566
+ }
411
567
  }
412
568
  }
413
- // Add active routes with their rules and prohibitions
414
- if (this.routes.length > 0) {
415
- promptBuilder.addActiveRoutes(this.routes.map((r) => ({
416
- title: r.title,
417
- description: r.description,
418
- conditions: r.conditions,
419
- domains: r.getDomains(),
420
- rules: r.getRules(),
421
- prohibitions: r.getProhibitions(),
422
- })));
423
- }
424
- // NOTE: Domains/tools are NOT added to the prompt.
425
- // Tools execute automatically based on state transitions and guideline matching,
426
- // NOT based on AI decisions. The AI should never see available tools.
427
- // Add JSON response schema instructions
428
- promptBuilder.addJsonResponseSchema();
429
- // Build final prompt
430
- const prompt = promptBuilder.build();
431
- // Generate message using AI provider with JSON mode enabled
432
- const result = await this.options.ai.generateMessage({
433
- prompt,
434
- history,
435
- context: effectiveContext,
436
- signal,
437
- parameters: {
438
- jsonMode: true,
439
- },
440
- });
441
- // Parse structured response
442
- let message = result.message;
443
- let route = null;
444
- let state = null;
445
- let toolCalls;
446
- if (result.structured) {
447
- // Extract data from structured response
448
- message = result.structured.message || message;
449
- // Find route by title
450
- if (result.structured.route) {
451
- const foundRoute = this.routes.find((r) => r.title === result.structured?.route);
452
- if (foundRoute) {
453
- route = {
454
- id: foundRoute.id,
455
- title: foundRoute.title,
456
- };
569
+ // PHASE 3: RESPONSE - Generate message using selected route
570
+ let message;
571
+ const toolCalls = undefined;
572
+ if (selectedRoute) {
573
+ // Determine next state based on current extracted data
574
+ const currentStateRef = session.currentState;
575
+ const currentState = currentStateRef
576
+ ? selectedRoute.getState(currentStateRef.id)
577
+ : undefined;
578
+ const nextState = this.getNextState(selectedRoute, currentState, session.extracted);
579
+ // Update session with next state
580
+ session = enterState(session, nextState.id, nextState.description);
581
+ console.log(`[Agent] Entered state: ${nextState.id}`);
582
+ // Get last user message
583
+ const lastUserMessage = getLastMessageFromHistory(history);
584
+ // Build response schema for this route (with gather fields from state)
585
+ const responseSchema = this.responseEngine.responseSchemaForRoute(selectedRoute, nextState);
586
+ // Build response prompt
587
+ const responsePrompt = this.responseEngine.buildResponsePrompt(selectedRoute, selectedRoute.getRules(), selectedRoute.getProhibitions(), responseDirectives, history, lastUserMessage, {
588
+ name: this.options.name,
589
+ goal: this.options.goal,
590
+ description: this.options.description,
591
+ personality: this.options.personality,
592
+ });
593
+ // Generate message using AI provider
594
+ const result = await this.options.ai.generateMessage({
595
+ prompt: responsePrompt,
596
+ history,
597
+ context: effectiveContext,
598
+ signal,
599
+ parameters: {
600
+ jsonSchema: responseSchema,
601
+ schemaName: "response_output",
602
+ },
603
+ });
604
+ message = result.structured?.message || result.message;
605
+ // Extract gathered data from response
606
+ if (result.structured && nextState.gatherFields) {
607
+ const gatheredData = {};
608
+ // The structured response includes both base fields and gathered extraction fields
609
+ const structuredData = result.structured;
610
+ for (const field of nextState.gatherFields) {
611
+ if (field in structuredData) {
612
+ gatheredData[field] = structuredData[field];
613
+ }
614
+ }
615
+ // Merge gathered data into session
616
+ if (Object.keys(gatheredData).length > 0) {
617
+ session = mergeExtracted(session, gatheredData);
618
+ console.log(`[Agent] Extracted data:`, gatheredData);
457
619
  }
458
620
  }
459
- // Create state reference if provided
460
- if (result.structured.state) {
461
- state = {
462
- id: "dynamic_state",
463
- description: result.structured.state,
464
- };
465
- }
466
- // Extract tool calls
467
- if (result.structured.toolCalls &&
468
- result.structured.toolCalls.length > 0) {
469
- toolCalls = result.structured.toolCalls;
621
+ // Extract any additional data from structured response
622
+ if (result.structured &&
623
+ typeof result.structured === "object" &&
624
+ "contextUpdate" in result.structured) {
625
+ await this.updateContext(result.structured
626
+ .contextUpdate);
470
627
  }
471
628
  }
629
+ else {
630
+ // Fallback: No routes defined, generate a simple response
631
+ const fallbackPrompt = new PromptComposer()
632
+ .addAgentMeta({
633
+ name: this.options.name,
634
+ goal: this.options.goal,
635
+ description: this.options.description,
636
+ })
637
+ .addPersonality(this.options.personality)
638
+ .addInteractionHistory(history)
639
+ .addGlossary(this.terms)
640
+ .addGuidelines(this.guidelines)
641
+ .addCapabilities(this.capabilities)
642
+ .build();
643
+ const result = await this.options.ai.generateMessage({
644
+ prompt: fallbackPrompt,
645
+ history,
646
+ context: effectiveContext,
647
+ signal,
648
+ parameters: {
649
+ jsonSchema: {
650
+ type: "object",
651
+ properties: {
652
+ message: { type: "string" },
653
+ },
654
+ required: ["message"],
655
+ additionalProperties: false,
656
+ },
657
+ schemaName: "fallback_response",
658
+ },
659
+ });
660
+ message = result.structured?.message || result.message;
661
+ }
662
+ // Auto-save session state to persistence if configured
663
+ if (this.persistenceManager &&
664
+ session.metadata?.sessionId &&
665
+ this.options.persistence?.autoSave !== false) {
666
+ await this.persistenceManager.saveSessionState(session.metadata.sessionId, session);
667
+ console.log(`[Agent] Auto-saved session state to persistence: ${session.metadata.sessionId}`);
668
+ }
472
669
  return {
473
670
  message,
474
- route: route || undefined,
475
- state: state || undefined,
671
+ session, // Return updated session with route/state info
476
672
  toolCalls,
477
673
  };
478
674
  }
@@ -500,12 +696,6 @@ export class Agent {
500
696
  getCapabilities() {
501
697
  return [...this.capabilities];
502
698
  }
503
- /**
504
- * Get all observations
505
- */
506
- getObservations() {
507
- return [...this.observations];
508
- }
509
699
  /**
510
700
  * Get the domain registry
511
701
  */