@animus-labs/cortex 0.2.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 (293) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +73 -0
  3. package/dist/budget-guard.d.ts +75 -0
  4. package/dist/budget-guard.d.ts.map +1 -0
  5. package/dist/budget-guard.js +142 -0
  6. package/dist/budget-guard.js.map +1 -0
  7. package/dist/compaction/compaction.d.ts +99 -0
  8. package/dist/compaction/compaction.d.ts.map +1 -0
  9. package/dist/compaction/compaction.js +302 -0
  10. package/dist/compaction/compaction.js.map +1 -0
  11. package/dist/compaction/failsafe.d.ts +57 -0
  12. package/dist/compaction/failsafe.d.ts.map +1 -0
  13. package/dist/compaction/failsafe.js +135 -0
  14. package/dist/compaction/failsafe.js.map +1 -0
  15. package/dist/compaction/index.d.ts +381 -0
  16. package/dist/compaction/index.d.ts.map +1 -0
  17. package/dist/compaction/index.js +979 -0
  18. package/dist/compaction/index.js.map +1 -0
  19. package/dist/compaction/microcompaction.d.ts +219 -0
  20. package/dist/compaction/microcompaction.d.ts.map +1 -0
  21. package/dist/compaction/microcompaction.js +536 -0
  22. package/dist/compaction/microcompaction.js.map +1 -0
  23. package/dist/compaction/observational/buffering.d.ts +225 -0
  24. package/dist/compaction/observational/buffering.d.ts.map +1 -0
  25. package/dist/compaction/observational/buffering.js +354 -0
  26. package/dist/compaction/observational/buffering.js.map +1 -0
  27. package/dist/compaction/observational/constants.d.ts +70 -0
  28. package/dist/compaction/observational/constants.d.ts.map +1 -0
  29. package/dist/compaction/observational/constants.js +507 -0
  30. package/dist/compaction/observational/constants.js.map +1 -0
  31. package/dist/compaction/observational/index.d.ts +219 -0
  32. package/dist/compaction/observational/index.d.ts.map +1 -0
  33. package/dist/compaction/observational/index.js +641 -0
  34. package/dist/compaction/observational/index.js.map +1 -0
  35. package/dist/compaction/observational/observer.d.ts +97 -0
  36. package/dist/compaction/observational/observer.d.ts.map +1 -0
  37. package/dist/compaction/observational/observer.js +424 -0
  38. package/dist/compaction/observational/observer.js.map +1 -0
  39. package/dist/compaction/observational/recall-tool.d.ts +27 -0
  40. package/dist/compaction/observational/recall-tool.d.ts.map +1 -0
  41. package/dist/compaction/observational/recall-tool.js +93 -0
  42. package/dist/compaction/observational/recall-tool.js.map +1 -0
  43. package/dist/compaction/observational/reflector.d.ts +94 -0
  44. package/dist/compaction/observational/reflector.d.ts.map +1 -0
  45. package/dist/compaction/observational/reflector.js +167 -0
  46. package/dist/compaction/observational/reflector.js.map +1 -0
  47. package/dist/compaction/observational/types.d.ts +271 -0
  48. package/dist/compaction/observational/types.d.ts.map +1 -0
  49. package/dist/compaction/observational/types.js +15 -0
  50. package/dist/compaction/observational/types.js.map +1 -0
  51. package/dist/context-manager.d.ts +134 -0
  52. package/dist/context-manager.d.ts.map +1 -0
  53. package/dist/context-manager.js +170 -0
  54. package/dist/context-manager.js.map +1 -0
  55. package/dist/cortex-agent.d.ts +1020 -0
  56. package/dist/cortex-agent.d.ts.map +1 -0
  57. package/dist/cortex-agent.js +3589 -0
  58. package/dist/cortex-agent.js.map +1 -0
  59. package/dist/error-classifier.d.ts +48 -0
  60. package/dist/error-classifier.d.ts.map +1 -0
  61. package/dist/error-classifier.js +152 -0
  62. package/dist/error-classifier.js.map +1 -0
  63. package/dist/event-bridge.d.ts +166 -0
  64. package/dist/event-bridge.d.ts.map +1 -0
  65. package/dist/event-bridge.js +381 -0
  66. package/dist/event-bridge.js.map +1 -0
  67. package/dist/index.d.ts +55 -0
  68. package/dist/index.d.ts.map +1 -0
  69. package/dist/index.js +57 -0
  70. package/dist/index.js.map +1 -0
  71. package/dist/mcp-client.d.ts +119 -0
  72. package/dist/mcp-client.d.ts.map +1 -0
  73. package/dist/mcp-client.js +474 -0
  74. package/dist/mcp-client.js.map +1 -0
  75. package/dist/model-wrapper.d.ts +58 -0
  76. package/dist/model-wrapper.d.ts.map +1 -0
  77. package/dist/model-wrapper.js +86 -0
  78. package/dist/model-wrapper.js.map +1 -0
  79. package/dist/noop-logger.d.ts +4 -0
  80. package/dist/noop-logger.d.ts.map +1 -0
  81. package/dist/noop-logger.js +8 -0
  82. package/dist/noop-logger.js.map +1 -0
  83. package/dist/prompt-diagnostics.d.ts +47 -0
  84. package/dist/prompt-diagnostics.d.ts.map +1 -0
  85. package/dist/prompt-diagnostics.js +230 -0
  86. package/dist/prompt-diagnostics.js.map +1 -0
  87. package/dist/provider-manager.d.ts +224 -0
  88. package/dist/provider-manager.d.ts.map +1 -0
  89. package/dist/provider-manager.js +563 -0
  90. package/dist/provider-manager.js.map +1 -0
  91. package/dist/provider-registry.d.ts +115 -0
  92. package/dist/provider-registry.d.ts.map +1 -0
  93. package/dist/provider-registry.js +305 -0
  94. package/dist/provider-registry.js.map +1 -0
  95. package/dist/schema-converter.d.ts +20 -0
  96. package/dist/schema-converter.d.ts.map +1 -0
  97. package/dist/schema-converter.js +48 -0
  98. package/dist/schema-converter.js.map +1 -0
  99. package/dist/skill-preprocessor.d.ts +46 -0
  100. package/dist/skill-preprocessor.d.ts.map +1 -0
  101. package/dist/skill-preprocessor.js +237 -0
  102. package/dist/skill-preprocessor.js.map +1 -0
  103. package/dist/skill-registry.d.ts +107 -0
  104. package/dist/skill-registry.d.ts.map +1 -0
  105. package/dist/skill-registry.js +330 -0
  106. package/dist/skill-registry.js.map +1 -0
  107. package/dist/skill-tool.d.ts +54 -0
  108. package/dist/skill-tool.d.ts.map +1 -0
  109. package/dist/skill-tool.js +88 -0
  110. package/dist/skill-tool.js.map +1 -0
  111. package/dist/sub-agent-manager.d.ts +90 -0
  112. package/dist/sub-agent-manager.d.ts.map +1 -0
  113. package/dist/sub-agent-manager.js +192 -0
  114. package/dist/sub-agent-manager.js.map +1 -0
  115. package/dist/token-estimator.d.ts +23 -0
  116. package/dist/token-estimator.d.ts.map +1 -0
  117. package/dist/token-estimator.js +27 -0
  118. package/dist/token-estimator.js.map +1 -0
  119. package/dist/tool-contract.d.ts +68 -0
  120. package/dist/tool-contract.d.ts.map +1 -0
  121. package/dist/tool-contract.js +35 -0
  122. package/dist/tool-contract.js.map +1 -0
  123. package/dist/tool-result-persistence.d.ts +89 -0
  124. package/dist/tool-result-persistence.d.ts.map +1 -0
  125. package/dist/tool-result-persistence.js +152 -0
  126. package/dist/tool-result-persistence.js.map +1 -0
  127. package/dist/tools/bash/index.d.ts +71 -0
  128. package/dist/tools/bash/index.d.ts.map +1 -0
  129. package/dist/tools/bash/index.js +485 -0
  130. package/dist/tools/bash/index.js.map +1 -0
  131. package/dist/tools/bash/interactive.d.ts +47 -0
  132. package/dist/tools/bash/interactive.d.ts.map +1 -0
  133. package/dist/tools/bash/interactive.js +262 -0
  134. package/dist/tools/bash/interactive.js.map +1 -0
  135. package/dist/tools/bash/safety.d.ts +149 -0
  136. package/dist/tools/bash/safety.d.ts.map +1 -0
  137. package/dist/tools/bash/safety.js +1116 -0
  138. package/dist/tools/bash/safety.js.map +1 -0
  139. package/dist/tools/edit.d.ts +57 -0
  140. package/dist/tools/edit.d.ts.map +1 -0
  141. package/dist/tools/edit.js +310 -0
  142. package/dist/tools/edit.js.map +1 -0
  143. package/dist/tools/glob.d.ts +34 -0
  144. package/dist/tools/glob.d.ts.map +1 -0
  145. package/dist/tools/glob.js +268 -0
  146. package/dist/tools/glob.js.map +1 -0
  147. package/dist/tools/grep.d.ts +53 -0
  148. package/dist/tools/grep.d.ts.map +1 -0
  149. package/dist/tools/grep.js +673 -0
  150. package/dist/tools/grep.js.map +1 -0
  151. package/dist/tools/index.d.ts +62 -0
  152. package/dist/tools/index.d.ts.map +1 -0
  153. package/dist/tools/index.js +52 -0
  154. package/dist/tools/index.js.map +1 -0
  155. package/dist/tools/read.d.ts +43 -0
  156. package/dist/tools/read.d.ts.map +1 -0
  157. package/dist/tools/read.js +459 -0
  158. package/dist/tools/read.js.map +1 -0
  159. package/dist/tools/runtime.d.ts +62 -0
  160. package/dist/tools/runtime.d.ts.map +1 -0
  161. package/dist/tools/runtime.js +116 -0
  162. package/dist/tools/runtime.js.map +1 -0
  163. package/dist/tools/shared/cwd-tracker.d.ts +32 -0
  164. package/dist/tools/shared/cwd-tracker.d.ts.map +1 -0
  165. package/dist/tools/shared/cwd-tracker.js +44 -0
  166. package/dist/tools/shared/cwd-tracker.js.map +1 -0
  167. package/dist/tools/shared/edit-history.d.ts +55 -0
  168. package/dist/tools/shared/edit-history.d.ts.map +1 -0
  169. package/dist/tools/shared/edit-history.js +72 -0
  170. package/dist/tools/shared/edit-history.js.map +1 -0
  171. package/dist/tools/shared/edit-matcher.d.ts +83 -0
  172. package/dist/tools/shared/edit-matcher.d.ts.map +1 -0
  173. package/dist/tools/shared/edit-matcher.js +359 -0
  174. package/dist/tools/shared/edit-matcher.js.map +1 -0
  175. package/dist/tools/shared/file-mutation-lock.d.ts +22 -0
  176. package/dist/tools/shared/file-mutation-lock.d.ts.map +1 -0
  177. package/dist/tools/shared/file-mutation-lock.js +35 -0
  178. package/dist/tools/shared/file-mutation-lock.js.map +1 -0
  179. package/dist/tools/shared/gitignore.d.ts +17 -0
  180. package/dist/tools/shared/gitignore.d.ts.map +1 -0
  181. package/dist/tools/shared/gitignore.js +59 -0
  182. package/dist/tools/shared/gitignore.js.map +1 -0
  183. package/dist/tools/shared/pdf-extractor.d.ts +96 -0
  184. package/dist/tools/shared/pdf-extractor.d.ts.map +1 -0
  185. package/dist/tools/shared/pdf-extractor.js +196 -0
  186. package/dist/tools/shared/pdf-extractor.js.map +1 -0
  187. package/dist/tools/shared/read-registry.d.ts +66 -0
  188. package/dist/tools/shared/read-registry.d.ts.map +1 -0
  189. package/dist/tools/shared/read-registry.js +65 -0
  190. package/dist/tools/shared/read-registry.js.map +1 -0
  191. package/dist/tools/shared/safe-env.d.ts +18 -0
  192. package/dist/tools/shared/safe-env.d.ts.map +1 -0
  193. package/dist/tools/shared/safe-env.js +70 -0
  194. package/dist/tools/shared/safe-env.js.map +1 -0
  195. package/dist/tools/sub-agent.d.ts +91 -0
  196. package/dist/tools/sub-agent.d.ts.map +1 -0
  197. package/dist/tools/sub-agent.js +89 -0
  198. package/dist/tools/sub-agent.js.map +1 -0
  199. package/dist/tools/task-output.d.ts +38 -0
  200. package/dist/tools/task-output.d.ts.map +1 -0
  201. package/dist/tools/task-output.js +186 -0
  202. package/dist/tools/task-output.js.map +1 -0
  203. package/dist/tools/tool-search/index.d.ts +40 -0
  204. package/dist/tools/tool-search/index.d.ts.map +1 -0
  205. package/dist/tools/tool-search/index.js +110 -0
  206. package/dist/tools/tool-search/index.js.map +1 -0
  207. package/dist/tools/tool-search/registry.d.ts +82 -0
  208. package/dist/tools/tool-search/registry.d.ts.map +1 -0
  209. package/dist/tools/tool-search/registry.js +238 -0
  210. package/dist/tools/tool-search/registry.js.map +1 -0
  211. package/dist/tools/undo-edit.d.ts +51 -0
  212. package/dist/tools/undo-edit.d.ts.map +1 -0
  213. package/dist/tools/undo-edit.js +231 -0
  214. package/dist/tools/undo-edit.js.map +1 -0
  215. package/dist/tools/web-fetch/cache.d.ts +49 -0
  216. package/dist/tools/web-fetch/cache.d.ts.map +1 -0
  217. package/dist/tools/web-fetch/cache.js +89 -0
  218. package/dist/tools/web-fetch/cache.js.map +1 -0
  219. package/dist/tools/web-fetch/index.d.ts +53 -0
  220. package/dist/tools/web-fetch/index.d.ts.map +1 -0
  221. package/dist/tools/web-fetch/index.js +513 -0
  222. package/dist/tools/web-fetch/index.js.map +1 -0
  223. package/dist/tools/write.d.ts +59 -0
  224. package/dist/tools/write.d.ts.map +1 -0
  225. package/dist/tools/write.js +316 -0
  226. package/dist/tools/write.js.map +1 -0
  227. package/dist/types.d.ts +881 -0
  228. package/dist/types.d.ts.map +1 -0
  229. package/dist/types.js +16 -0
  230. package/dist/types.js.map +1 -0
  231. package/dist/working-tags.d.ts +44 -0
  232. package/dist/working-tags.d.ts.map +1 -0
  233. package/dist/working-tags.js +103 -0
  234. package/dist/working-tags.js.map +1 -0
  235. package/package.json +87 -0
  236. package/src/budget-guard.ts +170 -0
  237. package/src/compaction/compaction.ts +386 -0
  238. package/src/compaction/failsafe.ts +185 -0
  239. package/src/compaction/index.ts +1199 -0
  240. package/src/compaction/microcompaction.ts +709 -0
  241. package/src/compaction/observational/buffering.ts +430 -0
  242. package/src/compaction/observational/constants.ts +532 -0
  243. package/src/compaction/observational/index.ts +837 -0
  244. package/src/compaction/observational/observer.ts +510 -0
  245. package/src/compaction/observational/recall-tool.ts +130 -0
  246. package/src/compaction/observational/reflector.ts +221 -0
  247. package/src/compaction/observational/types.ts +343 -0
  248. package/src/context-manager.ts +237 -0
  249. package/src/cortex-agent.ts +4297 -0
  250. package/src/error-classifier.ts +199 -0
  251. package/src/event-bridge.ts +508 -0
  252. package/src/index.ts +292 -0
  253. package/src/mcp-client.ts +582 -0
  254. package/src/model-wrapper.ts +128 -0
  255. package/src/noop-logger.ts +9 -0
  256. package/src/prompt-diagnostics.ts +296 -0
  257. package/src/provider-manager.ts +823 -0
  258. package/src/provider-registry.ts +386 -0
  259. package/src/schema-converter.ts +51 -0
  260. package/src/skill-preprocessor.ts +314 -0
  261. package/src/skill-registry.ts +378 -0
  262. package/src/skill-tool.ts +130 -0
  263. package/src/sub-agent-manager.ts +236 -0
  264. package/src/token-estimator.ts +26 -0
  265. package/src/tool-contract.ts +113 -0
  266. package/src/tool-result-persistence.ts +197 -0
  267. package/src/tools/bash/index.ts +633 -0
  268. package/src/tools/bash/interactive.ts +302 -0
  269. package/src/tools/bash/safety.ts +1297 -0
  270. package/src/tools/edit.ts +422 -0
  271. package/src/tools/glob.ts +330 -0
  272. package/src/tools/grep.ts +819 -0
  273. package/src/tools/index.ts +110 -0
  274. package/src/tools/read.ts +580 -0
  275. package/src/tools/runtime.ts +173 -0
  276. package/src/tools/shared/cwd-tracker.ts +50 -0
  277. package/src/tools/shared/edit-history.ts +96 -0
  278. package/src/tools/shared/edit-matcher.ts +457 -0
  279. package/src/tools/shared/file-mutation-lock.ts +40 -0
  280. package/src/tools/shared/gitignore.ts +61 -0
  281. package/src/tools/shared/pdf-extractor.ts +290 -0
  282. package/src/tools/shared/read-registry.ts +93 -0
  283. package/src/tools/shared/safe-env.ts +82 -0
  284. package/src/tools/sub-agent.ts +171 -0
  285. package/src/tools/task-output.ts +236 -0
  286. package/src/tools/tool-search/index.ts +167 -0
  287. package/src/tools/tool-search/registry.ts +278 -0
  288. package/src/tools/undo-edit.ts +314 -0
  289. package/src/tools/web-fetch/cache.ts +112 -0
  290. package/src/tools/web-fetch/index.ts +604 -0
  291. package/src/tools/write.ts +385 -0
  292. package/src/types.ts +1057 -0
  293. package/src/working-tags.ts +118 -0
@@ -0,0 +1,430 @@
1
+ /**
2
+ * Buffering coordinator for the observational memory system.
3
+ *
4
+ * Manages the async lifecycle of observer and reflector operations,
5
+ * ensuring at-most-one-in-flight per operation type, computing dynamic
6
+ * buffer intervals, and handling abort/cleanup.
7
+ *
8
+ * The coordinator does not own the observation slot or conversation
9
+ * history. It produces observation chunks and buffered reflections that
10
+ * the ObservationalMemoryEngine consumes during activation.
11
+ *
12
+ * References:
13
+ * - observational-memory-architecture.md (Observer System, Reflector System)
14
+ * - observer.ts (runObserver)
15
+ * - reflector.ts (runReflector)
16
+ */
17
+
18
+ import type { CompleteFn } from '../compaction.js';
19
+ import type { AgentMessage } from '../../context-manager.js';
20
+ import type { ObservationChunk, ObserverOutput, ReflectorOutput } from './types.js';
21
+ import { runObserver } from './observer.js';
22
+ import { runReflector } from './reflector.js';
23
+ import { estimateTokens } from '../../token-estimator.js';
24
+
25
+ // ---------------------------------------------------------------------------
26
+ // BufferingCoordinator
27
+ // ---------------------------------------------------------------------------
28
+
29
+ /**
30
+ * Coordinates async observer and reflector operations for the
31
+ * observational memory system.
32
+ *
33
+ * Ensures at-most-one observer and at-most-one reflector call are
34
+ * in-flight at any time. Completed observer results are stored as
35
+ * {@link ObservationChunk}s until the engine activates them. Completed
36
+ * reflector results are stored until the engine swaps them in.
37
+ *
38
+ * All in-flight operations are fire-and-forget from the caller's
39
+ * perspective. The coordinator attaches `.then()` / `.catch()` handlers
40
+ * internally and never surfaces unhandled rejections.
41
+ */
42
+ export class BufferingCoordinator {
43
+ // --- Internal state ---
44
+
45
+ private chunks: ObservationChunk[] = [];
46
+ private bufferWatermark: number = 0;
47
+ private inFlightObserver: Promise<ObserverOutput> | null = null;
48
+ private inFlightObserverEndIndex: number | null = null;
49
+ private inFlightReflector: Promise<ReflectorOutput> | null = null;
50
+ private bufferedReflection: string | null = null;
51
+ private bufferedReflectionCompressionLevel: number = 0;
52
+ private aborted: boolean = false;
53
+
54
+ /**
55
+ * Activation epoch. Incremented each time activation consumes chunks or
56
+ * a sync observer trims messages. In-flight observers capture the epoch
57
+ * at launch and discard their result if the epoch has changed by the time
58
+ * they complete. This prevents stale chunks from landing after sync
59
+ * activation has already processed those messages.
60
+ */
61
+ private activationEpoch: number = 0;
62
+
63
+ // -------------------------------------------------------------------------
64
+ // Buffer Interval Calculation
65
+ // -------------------------------------------------------------------------
66
+
67
+ /**
68
+ * Compute the dynamic buffer interval based on current context state.
69
+ *
70
+ * The interval targets `bufferTargetCycles` observer calls between the
71
+ * current utilization and the activation threshold. It is clamped between
72
+ * `bufferMinTokens` and `effectiveBufferCap` (the lesser of
73
+ * `bufferTokenCap` and 60% of the utility model's context window).
74
+ *
75
+ * @param tokensUntilActivation - tokens remaining before activation threshold
76
+ * @param config - buffer interval configuration
77
+ * @returns the buffer interval in tokens
78
+ */
79
+ computeBufferInterval(
80
+ tokensUntilActivation: number,
81
+ config: {
82
+ bufferTargetCycles: number;
83
+ bufferTokenCap: number;
84
+ bufferMinTokens: number;
85
+ utilityModelContextWindow: number;
86
+ },
87
+ ): number {
88
+ const effectiveBufferCap = Math.min(
89
+ config.bufferTokenCap,
90
+ config.utilityModelContextWindow * 0.6,
91
+ );
92
+ const dynamicInterval = tokensUntilActivation / config.bufferTargetCycles;
93
+ return Math.max(
94
+ Math.min(dynamicInterval, effectiveBufferCap),
95
+ config.bufferMinTokens,
96
+ );
97
+ }
98
+
99
+ // -------------------------------------------------------------------------
100
+ // Observer Buffering
101
+ // -------------------------------------------------------------------------
102
+
103
+ /**
104
+ * Check if a buffer observation should be triggered based on
105
+ * unobserved tokens.
106
+ *
107
+ * Returns true when the unobserved token count meets or exceeds the
108
+ * buffer interval, no observer call is currently in flight, and the
109
+ * coordinator has not been aborted.
110
+ *
111
+ * @param unobservedTokens - estimated tokens of messages after the buffer watermark
112
+ * @param bufferInterval - computed from {@link computeBufferInterval}
113
+ * @returns true if a buffer observation should launch
114
+ */
115
+ shouldBuffer(unobservedTokens: number, bufferInterval: number): boolean {
116
+ return (
117
+ unobservedTokens >= bufferInterval &&
118
+ !this.isObserverInFlight() &&
119
+ !this.aborted
120
+ );
121
+ }
122
+
123
+ /**
124
+ * Launch an async observer call. Does NOT await it.
125
+ *
126
+ * Stores the in-flight promise and tracks the end index of messages
127
+ * being processed. When the observer completes, its output is converted
128
+ * to an {@link ObservationChunk} and appended to the internal chunk
129
+ * list. If the coordinator has been aborted before the observer
130
+ * completes, the result is discarded.
131
+ *
132
+ * @param complete - the LLM completion function
133
+ * @param messages - the unobserved messages to process (snapshot)
134
+ * @param endIndex - the index in conversation history where these messages end
135
+ * @param previousObservations - current observation text for context
136
+ * @param config - observer config
137
+ * @param logger - optional logger for error reporting
138
+ */
139
+ launchObserver(
140
+ complete: CompleteFn,
141
+ messages: AgentMessage[],
142
+ endIndex: number,
143
+ previousObservations: string | null,
144
+ config: { previousObserverTokens: number; observerInstruction?: string },
145
+ logger?: { warn: (msg: string) => void },
146
+ ): void {
147
+ if (this.aborted) return;
148
+
149
+ // Estimate tokens from the message snapshot for the chunk metadata
150
+ const messageTokensObserved = messages.reduce((sum, msg) => {
151
+ if (typeof msg.content === 'string') {
152
+ return sum + estimateTokens(msg.content);
153
+ }
154
+ if (Array.isArray(msg.content)) {
155
+ const text = msg.content
156
+ .map((part) => {
157
+ if (typeof part.text === 'string') return part.text;
158
+ return JSON.stringify(part);
159
+ })
160
+ .join(' ');
161
+ return sum + estimateTokens(text);
162
+ }
163
+ return sum;
164
+ }, 0);
165
+
166
+ const promise = runObserver(complete, messages, previousObservations, config);
167
+ this.inFlightObserver = promise;
168
+ this.inFlightObserverEndIndex = endIndex;
169
+
170
+ // Capture the activation epoch at launch. If activation fires (sync or
171
+ // chunk-based) before this observer completes, the epoch will have
172
+ // advanced and the result is stale (those messages were already observed).
173
+ const launchEpoch = this.activationEpoch;
174
+
175
+ promise
176
+ .then((output: ObserverOutput) => {
177
+ if (this.aborted) return;
178
+
179
+ // Discard if activation already processed these messages
180
+ if (this.activationEpoch !== launchEpoch) {
181
+ this.inFlightObserver = null;
182
+ this.inFlightObserverEndIndex = null;
183
+ return;
184
+ }
185
+
186
+ const chunk: ObservationChunk = {
187
+ observations: output.observations,
188
+ messageTokensObserved,
189
+ createdAt: new Date(),
190
+ };
191
+
192
+ if (output.currentTask) {
193
+ chunk.currentTask = output.currentTask;
194
+ }
195
+ if (output.suggestedResponse) {
196
+ chunk.suggestedResponse = output.suggestedResponse;
197
+ }
198
+
199
+ this.chunks.push(chunk);
200
+ this.bufferWatermark = this.inFlightObserverEndIndex ?? endIndex;
201
+ this.inFlightObserver = null;
202
+ this.inFlightObserverEndIndex = null;
203
+ })
204
+ .catch((err: unknown) => {
205
+ const message = err instanceof Error ? err.message : String(err);
206
+ if (logger) {
207
+ logger.warn(`Observer buffer call failed: ${message}`);
208
+ }
209
+ this.inFlightObserver = null;
210
+ this.inFlightObserverEndIndex = null;
211
+ });
212
+ }
213
+
214
+ /**
215
+ * Check if there are completed buffer chunks ready for activation.
216
+ */
217
+ hasCompletedChunks(): boolean {
218
+ return this.chunks.length > 0;
219
+ }
220
+
221
+ /**
222
+ * Get all completed chunks and the watermark up to which messages
223
+ * are covered.
224
+ *
225
+ * Does NOT clear state. Call {@link commitActivation} after
226
+ * successfully activating.
227
+ */
228
+ getCompletedChunks(): { chunks: ObservationChunk[]; watermark: number } {
229
+ return { chunks: [...this.chunks], watermark: this.bufferWatermark };
230
+ }
231
+
232
+ /**
233
+ * Called after successful activation to reset buffer state.
234
+ *
235
+ * Clears accumulated chunks and resets the watermark to 0 since the
236
+ * messages it pointed to have been removed from the conversation
237
+ * history.
238
+ */
239
+ commitActivation(): void {
240
+ this.chunks = [];
241
+ this.bufferWatermark = 0;
242
+ this.activationEpoch++;
243
+ }
244
+
245
+ // -------------------------------------------------------------------------
246
+ // Reflector Buffering
247
+ // -------------------------------------------------------------------------
248
+
249
+ /**
250
+ * Check if reflection should be triggered based on observation token count.
251
+ *
252
+ * Returns:
253
+ * - `'sync'` when observation tokens are at or above the effective threshold
254
+ * (the caller decides whether to use a buffered reflection or force a sync call)
255
+ * - `'async'` when observation tokens are between the buffer activation point
256
+ * and the threshold, and no reflector is currently in flight
257
+ * - `'none'` otherwise
258
+ *
259
+ * @param observationTokens - current observation slot token count
260
+ * @param effectiveThreshold - from computeEffectiveReflectionThreshold
261
+ * @param reflectionBufferActivation - fraction at which to start async reflection
262
+ * @returns action indicator
263
+ */
264
+ shouldReflect(
265
+ observationTokens: number,
266
+ effectiveThreshold: number,
267
+ reflectionBufferActivation: number,
268
+ ): 'none' | 'async' | 'sync' {
269
+ if (observationTokens >= effectiveThreshold) {
270
+ return 'sync';
271
+ }
272
+
273
+ const asyncTrigger = effectiveThreshold * reflectionBufferActivation;
274
+ if (
275
+ observationTokens >= asyncTrigger &&
276
+ !this.isReflectorInFlight() &&
277
+ !this.aborted
278
+ ) {
279
+ return 'async';
280
+ }
281
+
282
+ return 'none';
283
+ }
284
+
285
+ /**
286
+ * Launch an async reflector call. Does NOT await it.
287
+ *
288
+ * When the reflector completes, its result is stored in
289
+ * `bufferedReflection` for later consumption via
290
+ * {@link consumeBufferedReflection}. If the coordinator has been
291
+ * aborted before the reflector completes, the result is discarded.
292
+ *
293
+ * @param complete - the LLM completion function
294
+ * @param observations - the current observation text to consolidate
295
+ * @param config - reflector config
296
+ * @param logger - optional logger for error reporting
297
+ */
298
+ launchReflector(
299
+ complete: CompleteFn,
300
+ observations: string,
301
+ config: { reflectionThreshold: number; reflectorInstruction?: string },
302
+ logger?: { warn: (msg: string) => void },
303
+ ): void {
304
+ if (this.aborted) return;
305
+
306
+ const promise = runReflector(complete, observations, config);
307
+ this.inFlightReflector = promise;
308
+
309
+ promise
310
+ .then((output: ReflectorOutput) => {
311
+ if (this.aborted) return;
312
+
313
+ this.bufferedReflection = output.observations;
314
+ this.bufferedReflectionCompressionLevel = output.compressionLevel;
315
+ this.inFlightReflector = null;
316
+ })
317
+ .catch((err: unknown) => {
318
+ const message = err instanceof Error ? err.message : String(err);
319
+ if (logger) {
320
+ logger.warn(`Reflector buffer call failed: ${message}`);
321
+ }
322
+ this.inFlightReflector = null;
323
+ });
324
+ }
325
+
326
+ /**
327
+ * Check if a buffered reflection is ready to swap in.
328
+ */
329
+ hasBufferedReflection(): boolean {
330
+ return this.bufferedReflection !== null;
331
+ }
332
+
333
+ /**
334
+ * Get the buffered reflection and clear it.
335
+ *
336
+ * Returns the consolidated observations and the compression level that
337
+ * was applied, or null if no buffered reflection is available.
338
+ */
339
+ consumeBufferedReflection(): {
340
+ observations: string;
341
+ compressionLevel: number;
342
+ } | null {
343
+ if (this.bufferedReflection === null) return null;
344
+
345
+ const result = {
346
+ observations: this.bufferedReflection,
347
+ compressionLevel: this.bufferedReflectionCompressionLevel,
348
+ };
349
+
350
+ this.bufferedReflection = null;
351
+ this.bufferedReflectionCompressionLevel = 0;
352
+
353
+ return result;
354
+ }
355
+
356
+ // -------------------------------------------------------------------------
357
+ // Lifecycle
358
+ // -------------------------------------------------------------------------
359
+
360
+ /**
361
+ * Get the current state for session persistence.
362
+ *
363
+ * In-flight operations are NOT included (they are lost on session
364
+ * save). Only completed chunks and the watermark are persisted.
365
+ */
366
+ getState(): { chunks: ObservationChunk[]; watermark: number } {
367
+ return { chunks: [...this.chunks], watermark: this.bufferWatermark };
368
+ }
369
+
370
+ /**
371
+ * Restore state from a previous session.
372
+ */
373
+ restoreState(state: { chunks: ObservationChunk[]; watermark: number }): void {
374
+ this.chunks = [...state.chunks];
375
+ this.bufferWatermark = state.watermark;
376
+ }
377
+
378
+ /**
379
+ * Abort all in-flight operations. Called on agent destruction.
380
+ *
381
+ * Sets the aborted flag so that any in-flight promise handlers
382
+ * discard their results when they eventually resolve.
383
+ */
384
+ abort(): void {
385
+ this.aborted = true;
386
+ this.inFlightObserver = null;
387
+ this.inFlightObserverEndIndex = null;
388
+ this.inFlightReflector = null;
389
+ }
390
+
391
+ /**
392
+ * Whether an observer call is currently in flight.
393
+ */
394
+ isObserverInFlight(): boolean {
395
+ return this.inFlightObserver !== null;
396
+ }
397
+
398
+ /**
399
+ * Whether a reflector call is currently in flight.
400
+ */
401
+ isReflectorInFlight(): boolean {
402
+ return this.inFlightReflector !== null;
403
+ }
404
+
405
+ /**
406
+ * Get the buffer watermark (index into conversation history marking
407
+ * where the last completed observation ended).
408
+ */
409
+ getWatermark(): number {
410
+ return this.bufferWatermark;
411
+ }
412
+
413
+ /**
414
+ * Set the watermark. Used during initialization or after manual
415
+ * adjustments to the conversation history.
416
+ */
417
+ setWatermark(index: number): void {
418
+ this.bufferWatermark = index;
419
+ }
420
+
421
+ /**
422
+ * Advance the activation epoch. Called when a sync activation trims
423
+ * messages outside of the normal commitActivation() flow (e.g., the
424
+ * engine's Step 2 sync observer path). This invalidates any in-flight
425
+ * observers that were launched before the sync activation.
426
+ */
427
+ advanceEpoch(): void {
428
+ this.activationEpoch++;
429
+ }
430
+ }