@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.
- package/LICENSE +21 -0
- package/README.md +73 -0
- package/dist/budget-guard.d.ts +75 -0
- package/dist/budget-guard.d.ts.map +1 -0
- package/dist/budget-guard.js +142 -0
- package/dist/budget-guard.js.map +1 -0
- package/dist/compaction/compaction.d.ts +99 -0
- package/dist/compaction/compaction.d.ts.map +1 -0
- package/dist/compaction/compaction.js +302 -0
- package/dist/compaction/compaction.js.map +1 -0
- package/dist/compaction/failsafe.d.ts +57 -0
- package/dist/compaction/failsafe.d.ts.map +1 -0
- package/dist/compaction/failsafe.js +135 -0
- package/dist/compaction/failsafe.js.map +1 -0
- package/dist/compaction/index.d.ts +381 -0
- package/dist/compaction/index.d.ts.map +1 -0
- package/dist/compaction/index.js +979 -0
- package/dist/compaction/index.js.map +1 -0
- package/dist/compaction/microcompaction.d.ts +219 -0
- package/dist/compaction/microcompaction.d.ts.map +1 -0
- package/dist/compaction/microcompaction.js +536 -0
- package/dist/compaction/microcompaction.js.map +1 -0
- package/dist/compaction/observational/buffering.d.ts +225 -0
- package/dist/compaction/observational/buffering.d.ts.map +1 -0
- package/dist/compaction/observational/buffering.js +354 -0
- package/dist/compaction/observational/buffering.js.map +1 -0
- package/dist/compaction/observational/constants.d.ts +70 -0
- package/dist/compaction/observational/constants.d.ts.map +1 -0
- package/dist/compaction/observational/constants.js +507 -0
- package/dist/compaction/observational/constants.js.map +1 -0
- package/dist/compaction/observational/index.d.ts +219 -0
- package/dist/compaction/observational/index.d.ts.map +1 -0
- package/dist/compaction/observational/index.js +641 -0
- package/dist/compaction/observational/index.js.map +1 -0
- package/dist/compaction/observational/observer.d.ts +97 -0
- package/dist/compaction/observational/observer.d.ts.map +1 -0
- package/dist/compaction/observational/observer.js +424 -0
- package/dist/compaction/observational/observer.js.map +1 -0
- package/dist/compaction/observational/recall-tool.d.ts +27 -0
- package/dist/compaction/observational/recall-tool.d.ts.map +1 -0
- package/dist/compaction/observational/recall-tool.js +93 -0
- package/dist/compaction/observational/recall-tool.js.map +1 -0
- package/dist/compaction/observational/reflector.d.ts +94 -0
- package/dist/compaction/observational/reflector.d.ts.map +1 -0
- package/dist/compaction/observational/reflector.js +167 -0
- package/dist/compaction/observational/reflector.js.map +1 -0
- package/dist/compaction/observational/types.d.ts +271 -0
- package/dist/compaction/observational/types.d.ts.map +1 -0
- package/dist/compaction/observational/types.js +15 -0
- package/dist/compaction/observational/types.js.map +1 -0
- package/dist/context-manager.d.ts +134 -0
- package/dist/context-manager.d.ts.map +1 -0
- package/dist/context-manager.js +170 -0
- package/dist/context-manager.js.map +1 -0
- package/dist/cortex-agent.d.ts +1020 -0
- package/dist/cortex-agent.d.ts.map +1 -0
- package/dist/cortex-agent.js +3589 -0
- package/dist/cortex-agent.js.map +1 -0
- package/dist/error-classifier.d.ts +48 -0
- package/dist/error-classifier.d.ts.map +1 -0
- package/dist/error-classifier.js +152 -0
- package/dist/error-classifier.js.map +1 -0
- package/dist/event-bridge.d.ts +166 -0
- package/dist/event-bridge.d.ts.map +1 -0
- package/dist/event-bridge.js +381 -0
- package/dist/event-bridge.js.map +1 -0
- package/dist/index.d.ts +55 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +57 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp-client.d.ts +119 -0
- package/dist/mcp-client.d.ts.map +1 -0
- package/dist/mcp-client.js +474 -0
- package/dist/mcp-client.js.map +1 -0
- package/dist/model-wrapper.d.ts +58 -0
- package/dist/model-wrapper.d.ts.map +1 -0
- package/dist/model-wrapper.js +86 -0
- package/dist/model-wrapper.js.map +1 -0
- package/dist/noop-logger.d.ts +4 -0
- package/dist/noop-logger.d.ts.map +1 -0
- package/dist/noop-logger.js +8 -0
- package/dist/noop-logger.js.map +1 -0
- package/dist/prompt-diagnostics.d.ts +47 -0
- package/dist/prompt-diagnostics.d.ts.map +1 -0
- package/dist/prompt-diagnostics.js +230 -0
- package/dist/prompt-diagnostics.js.map +1 -0
- package/dist/provider-manager.d.ts +224 -0
- package/dist/provider-manager.d.ts.map +1 -0
- package/dist/provider-manager.js +563 -0
- package/dist/provider-manager.js.map +1 -0
- package/dist/provider-registry.d.ts +115 -0
- package/dist/provider-registry.d.ts.map +1 -0
- package/dist/provider-registry.js +305 -0
- package/dist/provider-registry.js.map +1 -0
- package/dist/schema-converter.d.ts +20 -0
- package/dist/schema-converter.d.ts.map +1 -0
- package/dist/schema-converter.js +48 -0
- package/dist/schema-converter.js.map +1 -0
- package/dist/skill-preprocessor.d.ts +46 -0
- package/dist/skill-preprocessor.d.ts.map +1 -0
- package/dist/skill-preprocessor.js +237 -0
- package/dist/skill-preprocessor.js.map +1 -0
- package/dist/skill-registry.d.ts +107 -0
- package/dist/skill-registry.d.ts.map +1 -0
- package/dist/skill-registry.js +330 -0
- package/dist/skill-registry.js.map +1 -0
- package/dist/skill-tool.d.ts +54 -0
- package/dist/skill-tool.d.ts.map +1 -0
- package/dist/skill-tool.js +88 -0
- package/dist/skill-tool.js.map +1 -0
- package/dist/sub-agent-manager.d.ts +90 -0
- package/dist/sub-agent-manager.d.ts.map +1 -0
- package/dist/sub-agent-manager.js +192 -0
- package/dist/sub-agent-manager.js.map +1 -0
- package/dist/token-estimator.d.ts +23 -0
- package/dist/token-estimator.d.ts.map +1 -0
- package/dist/token-estimator.js +27 -0
- package/dist/token-estimator.js.map +1 -0
- package/dist/tool-contract.d.ts +68 -0
- package/dist/tool-contract.d.ts.map +1 -0
- package/dist/tool-contract.js +35 -0
- package/dist/tool-contract.js.map +1 -0
- package/dist/tool-result-persistence.d.ts +89 -0
- package/dist/tool-result-persistence.d.ts.map +1 -0
- package/dist/tool-result-persistence.js +152 -0
- package/dist/tool-result-persistence.js.map +1 -0
- package/dist/tools/bash/index.d.ts +71 -0
- package/dist/tools/bash/index.d.ts.map +1 -0
- package/dist/tools/bash/index.js +485 -0
- package/dist/tools/bash/index.js.map +1 -0
- package/dist/tools/bash/interactive.d.ts +47 -0
- package/dist/tools/bash/interactive.d.ts.map +1 -0
- package/dist/tools/bash/interactive.js +262 -0
- package/dist/tools/bash/interactive.js.map +1 -0
- package/dist/tools/bash/safety.d.ts +149 -0
- package/dist/tools/bash/safety.d.ts.map +1 -0
- package/dist/tools/bash/safety.js +1116 -0
- package/dist/tools/bash/safety.js.map +1 -0
- package/dist/tools/edit.d.ts +57 -0
- package/dist/tools/edit.d.ts.map +1 -0
- package/dist/tools/edit.js +310 -0
- package/dist/tools/edit.js.map +1 -0
- package/dist/tools/glob.d.ts +34 -0
- package/dist/tools/glob.d.ts.map +1 -0
- package/dist/tools/glob.js +268 -0
- package/dist/tools/glob.js.map +1 -0
- package/dist/tools/grep.d.ts +53 -0
- package/dist/tools/grep.d.ts.map +1 -0
- package/dist/tools/grep.js +673 -0
- package/dist/tools/grep.js.map +1 -0
- package/dist/tools/index.d.ts +62 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +52 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/read.d.ts +43 -0
- package/dist/tools/read.d.ts.map +1 -0
- package/dist/tools/read.js +459 -0
- package/dist/tools/read.js.map +1 -0
- package/dist/tools/runtime.d.ts +62 -0
- package/dist/tools/runtime.d.ts.map +1 -0
- package/dist/tools/runtime.js +116 -0
- package/dist/tools/runtime.js.map +1 -0
- package/dist/tools/shared/cwd-tracker.d.ts +32 -0
- package/dist/tools/shared/cwd-tracker.d.ts.map +1 -0
- package/dist/tools/shared/cwd-tracker.js +44 -0
- package/dist/tools/shared/cwd-tracker.js.map +1 -0
- package/dist/tools/shared/edit-history.d.ts +55 -0
- package/dist/tools/shared/edit-history.d.ts.map +1 -0
- package/dist/tools/shared/edit-history.js +72 -0
- package/dist/tools/shared/edit-history.js.map +1 -0
- package/dist/tools/shared/edit-matcher.d.ts +83 -0
- package/dist/tools/shared/edit-matcher.d.ts.map +1 -0
- package/dist/tools/shared/edit-matcher.js +359 -0
- package/dist/tools/shared/edit-matcher.js.map +1 -0
- package/dist/tools/shared/file-mutation-lock.d.ts +22 -0
- package/dist/tools/shared/file-mutation-lock.d.ts.map +1 -0
- package/dist/tools/shared/file-mutation-lock.js +35 -0
- package/dist/tools/shared/file-mutation-lock.js.map +1 -0
- package/dist/tools/shared/gitignore.d.ts +17 -0
- package/dist/tools/shared/gitignore.d.ts.map +1 -0
- package/dist/tools/shared/gitignore.js +59 -0
- package/dist/tools/shared/gitignore.js.map +1 -0
- package/dist/tools/shared/pdf-extractor.d.ts +96 -0
- package/dist/tools/shared/pdf-extractor.d.ts.map +1 -0
- package/dist/tools/shared/pdf-extractor.js +196 -0
- package/dist/tools/shared/pdf-extractor.js.map +1 -0
- package/dist/tools/shared/read-registry.d.ts +66 -0
- package/dist/tools/shared/read-registry.d.ts.map +1 -0
- package/dist/tools/shared/read-registry.js +65 -0
- package/dist/tools/shared/read-registry.js.map +1 -0
- package/dist/tools/shared/safe-env.d.ts +18 -0
- package/dist/tools/shared/safe-env.d.ts.map +1 -0
- package/dist/tools/shared/safe-env.js +70 -0
- package/dist/tools/shared/safe-env.js.map +1 -0
- package/dist/tools/sub-agent.d.ts +91 -0
- package/dist/tools/sub-agent.d.ts.map +1 -0
- package/dist/tools/sub-agent.js +89 -0
- package/dist/tools/sub-agent.js.map +1 -0
- package/dist/tools/task-output.d.ts +38 -0
- package/dist/tools/task-output.d.ts.map +1 -0
- package/dist/tools/task-output.js +186 -0
- package/dist/tools/task-output.js.map +1 -0
- package/dist/tools/tool-search/index.d.ts +40 -0
- package/dist/tools/tool-search/index.d.ts.map +1 -0
- package/dist/tools/tool-search/index.js +110 -0
- package/dist/tools/tool-search/index.js.map +1 -0
- package/dist/tools/tool-search/registry.d.ts +82 -0
- package/dist/tools/tool-search/registry.d.ts.map +1 -0
- package/dist/tools/tool-search/registry.js +238 -0
- package/dist/tools/tool-search/registry.js.map +1 -0
- package/dist/tools/undo-edit.d.ts +51 -0
- package/dist/tools/undo-edit.d.ts.map +1 -0
- package/dist/tools/undo-edit.js +231 -0
- package/dist/tools/undo-edit.js.map +1 -0
- package/dist/tools/web-fetch/cache.d.ts +49 -0
- package/dist/tools/web-fetch/cache.d.ts.map +1 -0
- package/dist/tools/web-fetch/cache.js +89 -0
- package/dist/tools/web-fetch/cache.js.map +1 -0
- package/dist/tools/web-fetch/index.d.ts +53 -0
- package/dist/tools/web-fetch/index.d.ts.map +1 -0
- package/dist/tools/web-fetch/index.js +513 -0
- package/dist/tools/web-fetch/index.js.map +1 -0
- package/dist/tools/write.d.ts +59 -0
- package/dist/tools/write.d.ts.map +1 -0
- package/dist/tools/write.js +316 -0
- package/dist/tools/write.js.map +1 -0
- package/dist/types.d.ts +881 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +16 -0
- package/dist/types.js.map +1 -0
- package/dist/working-tags.d.ts +44 -0
- package/dist/working-tags.d.ts.map +1 -0
- package/dist/working-tags.js +103 -0
- package/dist/working-tags.js.map +1 -0
- package/package.json +87 -0
- package/src/budget-guard.ts +170 -0
- package/src/compaction/compaction.ts +386 -0
- package/src/compaction/failsafe.ts +185 -0
- package/src/compaction/index.ts +1199 -0
- package/src/compaction/microcompaction.ts +709 -0
- package/src/compaction/observational/buffering.ts +430 -0
- package/src/compaction/observational/constants.ts +532 -0
- package/src/compaction/observational/index.ts +837 -0
- package/src/compaction/observational/observer.ts +510 -0
- package/src/compaction/observational/recall-tool.ts +130 -0
- package/src/compaction/observational/reflector.ts +221 -0
- package/src/compaction/observational/types.ts +343 -0
- package/src/context-manager.ts +237 -0
- package/src/cortex-agent.ts +4297 -0
- package/src/error-classifier.ts +199 -0
- package/src/event-bridge.ts +508 -0
- package/src/index.ts +292 -0
- package/src/mcp-client.ts +582 -0
- package/src/model-wrapper.ts +128 -0
- package/src/noop-logger.ts +9 -0
- package/src/prompt-diagnostics.ts +296 -0
- package/src/provider-manager.ts +823 -0
- package/src/provider-registry.ts +386 -0
- package/src/schema-converter.ts +51 -0
- package/src/skill-preprocessor.ts +314 -0
- package/src/skill-registry.ts +378 -0
- package/src/skill-tool.ts +130 -0
- package/src/sub-agent-manager.ts +236 -0
- package/src/token-estimator.ts +26 -0
- package/src/tool-contract.ts +113 -0
- package/src/tool-result-persistence.ts +197 -0
- package/src/tools/bash/index.ts +633 -0
- package/src/tools/bash/interactive.ts +302 -0
- package/src/tools/bash/safety.ts +1297 -0
- package/src/tools/edit.ts +422 -0
- package/src/tools/glob.ts +330 -0
- package/src/tools/grep.ts +819 -0
- package/src/tools/index.ts +110 -0
- package/src/tools/read.ts +580 -0
- package/src/tools/runtime.ts +173 -0
- package/src/tools/shared/cwd-tracker.ts +50 -0
- package/src/tools/shared/edit-history.ts +96 -0
- package/src/tools/shared/edit-matcher.ts +457 -0
- package/src/tools/shared/file-mutation-lock.ts +40 -0
- package/src/tools/shared/gitignore.ts +61 -0
- package/src/tools/shared/pdf-extractor.ts +290 -0
- package/src/tools/shared/read-registry.ts +93 -0
- package/src/tools/shared/safe-env.ts +82 -0
- package/src/tools/sub-agent.ts +171 -0
- package/src/tools/task-output.ts +236 -0
- package/src/tools/tool-search/index.ts +167 -0
- package/src/tools/tool-search/registry.ts +278 -0
- package/src/tools/undo-edit.ts +314 -0
- package/src/tools/web-fetch/cache.ts +112 -0
- package/src/tools/web-fetch/index.ts +604 -0
- package/src/tools/write.ts +385 -0
- package/src/types.ts +1057 -0
- package/src/working-tags.ts +118 -0
|
@@ -0,0 +1,837 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ObservationalMemoryEngine: core orchestrator for the observational memory
|
|
3
|
+
* system.
|
|
4
|
+
*
|
|
5
|
+
* Ties together the observer, reflector, and buffering coordinator. Manages
|
|
6
|
+
* the observation slot, handles activation and reflection, and exposes the
|
|
7
|
+
* state management API.
|
|
8
|
+
*
|
|
9
|
+
* The engine runs inside CompactionManager's transformContext hook. It does
|
|
10
|
+
* NOT directly call ContextManager.setSlot(); instead, it manages the slot
|
|
11
|
+
* content string and the integration layer handles writing it to the slot.
|
|
12
|
+
*
|
|
13
|
+
* References:
|
|
14
|
+
* - observational-memory-architecture.md
|
|
15
|
+
* - compaction-strategy.md
|
|
16
|
+
* - context-manager.md
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import type { CompleteFn } from '../compaction.js';
|
|
20
|
+
import type { AgentMessage, AgentContext } from '../../context-manager.js';
|
|
21
|
+
import type {
|
|
22
|
+
ObservationalMemoryConfig,
|
|
23
|
+
ObservationalMemoryState,
|
|
24
|
+
ObservationChunk,
|
|
25
|
+
ObservationEvent,
|
|
26
|
+
ReflectionEvent,
|
|
27
|
+
ContinuationHint,
|
|
28
|
+
RecallConfig,
|
|
29
|
+
} from './types.js';
|
|
30
|
+
import {
|
|
31
|
+
OBSERVATIONAL_MEMORY_DEFAULTS,
|
|
32
|
+
OBSERVATION_CONTEXT_PREAMBLE,
|
|
33
|
+
OBSERVATION_RECALL_INSTRUCTIONS,
|
|
34
|
+
} from './constants.js';
|
|
35
|
+
import { runObserver } from './observer.js';
|
|
36
|
+
import { runReflector, computeEffectiveReflectionThreshold } from './reflector.js';
|
|
37
|
+
import { BufferingCoordinator } from './buffering.js';
|
|
38
|
+
import { estimateTokens } from '../../token-estimator.js';
|
|
39
|
+
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
// Re-exports
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
|
|
44
|
+
export { BufferingCoordinator } from './buffering.js';
|
|
45
|
+
export { runObserver } from './observer.js';
|
|
46
|
+
export { runReflector, computeEffectiveReflectionThreshold } from './reflector.js';
|
|
47
|
+
export { createRecallTool } from './recall-tool.js';
|
|
48
|
+
|
|
49
|
+
export type {
|
|
50
|
+
ObservationalMemoryConfig,
|
|
51
|
+
ObservationalMemoryState,
|
|
52
|
+
ObservationChunk,
|
|
53
|
+
ObservationEvent,
|
|
54
|
+
ReflectionEvent,
|
|
55
|
+
ContinuationHint,
|
|
56
|
+
RecallConfig,
|
|
57
|
+
RecallResult,
|
|
58
|
+
ObserverOutput,
|
|
59
|
+
ReflectorOutput,
|
|
60
|
+
BufferState,
|
|
61
|
+
} from './types.js';
|
|
62
|
+
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
// ObservationalMemoryEngine
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Core orchestrator for the observational memory system.
|
|
69
|
+
*
|
|
70
|
+
* Coordinates the observer, reflector, and buffering coordinator to maintain
|
|
71
|
+
* a compressed observation log of the conversation. Called from
|
|
72
|
+
* CompactionManager during transformContext.
|
|
73
|
+
*/
|
|
74
|
+
export class ObservationalMemoryEngine {
|
|
75
|
+
private config: Required<Omit<ObservationalMemoryConfig, 'observerInstruction' | 'reflectorInstruction' | 'recall'>> & {
|
|
76
|
+
observerInstruction?: string;
|
|
77
|
+
reflectorInstruction?: string;
|
|
78
|
+
recall?: RecallConfig;
|
|
79
|
+
};
|
|
80
|
+
private buffering: BufferingCoordinator;
|
|
81
|
+
private completeFn: CompleteFn | null = null;
|
|
82
|
+
private observations: string = '';
|
|
83
|
+
private continuationHint: ContinuationHint | null = null;
|
|
84
|
+
private observationTokenCount: number = 0;
|
|
85
|
+
private generationCount: number = 0;
|
|
86
|
+
private contextWindow: number = 0;
|
|
87
|
+
private utilityModelContextWindow: number = 0;
|
|
88
|
+
private slotIndex: number;
|
|
89
|
+
private logger: { warn: (msg: string) => void; info: (msg: string) => void } | null = null;
|
|
90
|
+
|
|
91
|
+
// Event handler arrays (method-level registration, multiple handlers)
|
|
92
|
+
private observationHandlers: Array<(event: ObservationEvent) => void> = [];
|
|
93
|
+
private reflectionHandlers: Array<(event: ReflectionEvent) => void> = [];
|
|
94
|
+
|
|
95
|
+
constructor(config: Partial<ObservationalMemoryConfig>, slotIndex: number) {
|
|
96
|
+
this.slotIndex = slotIndex;
|
|
97
|
+
this.buffering = new BufferingCoordinator();
|
|
98
|
+
|
|
99
|
+
// Merge with defaults. Optional fields (observerInstruction,
|
|
100
|
+
// reflectorInstruction, recall) are only included when provided.
|
|
101
|
+
this.config = {
|
|
102
|
+
...OBSERVATIONAL_MEMORY_DEFAULTS,
|
|
103
|
+
...config,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// -------------------------------------------------------------------------
|
|
108
|
+
// Configuration
|
|
109
|
+
// -------------------------------------------------------------------------
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Set the LLM completion function (wired to utilityComplete on CortexAgent).
|
|
113
|
+
*/
|
|
114
|
+
setCompleteFn(fn: CompleteFn): void {
|
|
115
|
+
this.completeFn = fn;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Update the context window size.
|
|
120
|
+
*/
|
|
121
|
+
setContextWindow(contextWindow: number): void {
|
|
122
|
+
this.contextWindow = contextWindow;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Update the utility model context window (for clamps).
|
|
127
|
+
*/
|
|
128
|
+
setUtilityModelContextWindow(utilityModelContextWindow: number): void {
|
|
129
|
+
this.utilityModelContextWindow = utilityModelContextWindow;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Set the logger.
|
|
134
|
+
*/
|
|
135
|
+
setLogger(logger: { warn: (msg: string) => void; info: (msg: string) => void }): void {
|
|
136
|
+
this.logger = logger;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// -------------------------------------------------------------------------
|
|
140
|
+
// Event Registration
|
|
141
|
+
// -------------------------------------------------------------------------
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Register an observation event handler.
|
|
145
|
+
*/
|
|
146
|
+
onObservation(handler: (event: ObservationEvent) => void): void {
|
|
147
|
+
this.observationHandlers.push(handler);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Register a reflection event handler.
|
|
152
|
+
*/
|
|
153
|
+
onReflection(handler: (event: ReflectionEvent) => void): void {
|
|
154
|
+
this.reflectionHandlers.push(handler);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// -------------------------------------------------------------------------
|
|
158
|
+
// Core: applyInTransformContext
|
|
159
|
+
// -------------------------------------------------------------------------
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Core method called from CompactionManager during transformContext.
|
|
163
|
+
*
|
|
164
|
+
* Handles observation activation and reflection when context utilization
|
|
165
|
+
* exceeds the activation threshold. Updates the observation slot and
|
|
166
|
+
* trims observed messages from history.
|
|
167
|
+
*
|
|
168
|
+
* @param context - The AgentContext from transformContext
|
|
169
|
+
* @param utilization - Current total context utilization (0-1)
|
|
170
|
+
* @param slotCount - Number of slot messages at the start of the array
|
|
171
|
+
* @param getHistory - Get conversation history from the context (post-slot)
|
|
172
|
+
* @param setHistory - Set conversation history in the context (post-slot)
|
|
173
|
+
* @param getSourceHistory - Get the original transcript history (agent.state.messages post-slot)
|
|
174
|
+
* @param setSourceHistory - Replace the original transcript history
|
|
175
|
+
* @returns Modified context with updated observations and trimmed history
|
|
176
|
+
*/
|
|
177
|
+
async applyInTransformContext(
|
|
178
|
+
context: AgentContext,
|
|
179
|
+
utilization: number,
|
|
180
|
+
slotCount: number,
|
|
181
|
+
getHistory: (ctx: AgentContext) => AgentMessage[],
|
|
182
|
+
setHistory: (ctx: AgentContext, history: AgentMessage[]) => AgentContext,
|
|
183
|
+
getSourceHistory: () => AgentMessage[],
|
|
184
|
+
setSourceHistory: (history: AgentMessage[]) => void,
|
|
185
|
+
): Promise<AgentContext> {
|
|
186
|
+
if (utilization < this.config.activationThreshold) {
|
|
187
|
+
return context;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// --- Activation ---
|
|
191
|
+
const sourceHistory = getSourceHistory();
|
|
192
|
+
const watermark = this.buffering.getWatermark();
|
|
193
|
+
let compactedMessages: AgentMessage[] = [];
|
|
194
|
+
let newObservationText = '';
|
|
195
|
+
let activatedSync = false;
|
|
196
|
+
|
|
197
|
+
// Step 1: Activate completed buffer chunks
|
|
198
|
+
if (this.buffering.hasCompletedChunks()) {
|
|
199
|
+
const { chunks, watermark: chunkWatermark } = this.buffering.getCompletedChunks();
|
|
200
|
+
const merged = this.mergeChunks(chunks);
|
|
201
|
+
newObservationText = merged.observations;
|
|
202
|
+
|
|
203
|
+
if (merged.hint) {
|
|
204
|
+
this.continuationHint = merged.hint;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Messages covered by completed chunks (from start of source to watermark)
|
|
208
|
+
compactedMessages = sourceHistory.slice(0, chunkWatermark);
|
|
209
|
+
|
|
210
|
+
// Remove observed messages from source, keep unbuffered tail
|
|
211
|
+
setSourceHistory(sourceHistory.slice(chunkWatermark));
|
|
212
|
+
this.buffering.commitActivation();
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Step 2: Check if still over threshold after chunk activation
|
|
216
|
+
// Recompute utilization after trimming. Account for the full slot
|
|
217
|
+
// overhead (preamble, XML wrappers, continuation hints) rather than
|
|
218
|
+
// just the raw observation text, since the slot is always larger.
|
|
219
|
+
const postChunkSource = getSourceHistory();
|
|
220
|
+
const trimmedMessageTokens = estimateTokens(
|
|
221
|
+
compactedMessages.map(m => typeof m.content === 'string' ? m.content : JSON.stringify(m.content)).join('\n'),
|
|
222
|
+
);
|
|
223
|
+
// Estimate the actual slot size including overhead, not just the raw
|
|
224
|
+
// observation text. buildSlotContent() wraps observations in the
|
|
225
|
+
// preamble, <observations> tags, and optional continuation hints.
|
|
226
|
+
const slotOverheadTokens = newObservationText
|
|
227
|
+
? estimateTokens(this.buildSlotContentForEstimate(newObservationText))
|
|
228
|
+
: 0;
|
|
229
|
+
// Net change: removed messages, added observation slot content
|
|
230
|
+
const netTokenReduction = trimmedMessageTokens - slotOverheadTokens;
|
|
231
|
+
const postChunkUtilization = utilization - (this.contextWindow > 0 ? netTokenReduction / this.contextWindow : 0);
|
|
232
|
+
|
|
233
|
+
if (postChunkUtilization >= this.config.activationThreshold && this.completeFn) {
|
|
234
|
+
// Force sync observer on remaining unbuffered messages
|
|
235
|
+
const unbufferedMessages = postChunkSource;
|
|
236
|
+
|
|
237
|
+
if (unbufferedMessages.length > 0) {
|
|
238
|
+
activatedSync = true;
|
|
239
|
+
|
|
240
|
+
const output = await runObserver(
|
|
241
|
+
this.completeFn,
|
|
242
|
+
unbufferedMessages,
|
|
243
|
+
this.observations || null,
|
|
244
|
+
this.buildObserverConfig(),
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
// Append sync observations
|
|
248
|
+
if (newObservationText) {
|
|
249
|
+
newObservationText += '\n\n' + output.observations;
|
|
250
|
+
} else {
|
|
251
|
+
newObservationText = output.observations;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (output.currentTask || output.suggestedResponse) {
|
|
255
|
+
this.continuationHint = {
|
|
256
|
+
currentTask: output.currentTask ?? '',
|
|
257
|
+
suggestedResponse: output.suggestedResponse ?? '',
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// All unbuffered messages are now observed
|
|
262
|
+
compactedMessages = [...compactedMessages, ...unbufferedMessages];
|
|
263
|
+
setSourceHistory([]);
|
|
264
|
+
this.buffering.setWatermark(0);
|
|
265
|
+
// Invalidate any in-flight observers that were processing messages
|
|
266
|
+
// we just observed synchronously
|
|
267
|
+
this.buffering.advanceEpoch();
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Step 3: Merge new observations into existing
|
|
272
|
+
if (newObservationText) {
|
|
273
|
+
if (this.observations) {
|
|
274
|
+
this.observations += '\n\n' + newObservationText;
|
|
275
|
+
} else {
|
|
276
|
+
this.observations = newObservationText;
|
|
277
|
+
}
|
|
278
|
+
this.observationTokenCount = estimateTokens(this.observations);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Step 4: Fire observation event
|
|
282
|
+
if (compactedMessages.length > 0) {
|
|
283
|
+
this.fireObservationEvent({
|
|
284
|
+
compactedMessages,
|
|
285
|
+
observations: this.observations,
|
|
286
|
+
contextUtilization: utilization,
|
|
287
|
+
sync: activatedSync,
|
|
288
|
+
timestamp: new Date(),
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Step 5: Handle reflection (may replace this.observations with condensed version)
|
|
293
|
+
await this.handleReflection();
|
|
294
|
+
|
|
295
|
+
// Step 6: Build slot content AFTER reflection so it contains post-reflection
|
|
296
|
+
// observations. Previously this was captured before reflection, requiring
|
|
297
|
+
// an external patch in cortex-agent.ts to correct stale content.
|
|
298
|
+
const slotContent = this.buildSlotContent();
|
|
299
|
+
|
|
300
|
+
// Step 7: Rebuild context with updated observations and trimmed history
|
|
301
|
+
const updatedSourceHistory = getSourceHistory();
|
|
302
|
+
|
|
303
|
+
// Build new message array: slot region + observation slot + remaining source messages
|
|
304
|
+
const slotRegion = context.messages.slice(0, this.slotIndex);
|
|
305
|
+
const observationSlotMessage: AgentMessage = {
|
|
306
|
+
role: 'user',
|
|
307
|
+
content: slotContent,
|
|
308
|
+
timestamp: Date.now(),
|
|
309
|
+
};
|
|
310
|
+
// Messages after the slot region that are not part of the observation slot
|
|
311
|
+
const postSlotMessages = updatedSourceHistory;
|
|
312
|
+
|
|
313
|
+
const newMessages: AgentMessage[] = [
|
|
314
|
+
...slotRegion,
|
|
315
|
+
observationSlotMessage,
|
|
316
|
+
...context.messages.slice(this.slotIndex + 1, slotCount),
|
|
317
|
+
...postSlotMessages,
|
|
318
|
+
];
|
|
319
|
+
|
|
320
|
+
return setHistory(
|
|
321
|
+
{ ...context, messages: newMessages },
|
|
322
|
+
postSlotMessages,
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// -------------------------------------------------------------------------
|
|
327
|
+
// Turn-end handler
|
|
328
|
+
// -------------------------------------------------------------------------
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Called at each turn_end event. Handles async buffer triggering.
|
|
332
|
+
*
|
|
333
|
+
* Computes the dynamic buffer interval and launches an async observer
|
|
334
|
+
* if enough unobserved tokens have accumulated.
|
|
335
|
+
*
|
|
336
|
+
* @param totalTokens - Total tokens from the last LLM response
|
|
337
|
+
* @param contextWindow - Current context window size
|
|
338
|
+
* @param messages - Current conversation messages (post-slot)
|
|
339
|
+
* @param slotCount - Number of slot messages
|
|
340
|
+
*/
|
|
341
|
+
onTurnEnd(
|
|
342
|
+
totalTokens: number,
|
|
343
|
+
contextWindow: number,
|
|
344
|
+
messages: AgentMessage[],
|
|
345
|
+
slotCount: number,
|
|
346
|
+
): void {
|
|
347
|
+
if (!this.completeFn || contextWindow <= 0) return;
|
|
348
|
+
|
|
349
|
+
const currentUtilization = totalTokens / contextWindow;
|
|
350
|
+
const tokensUntilActivation = (this.config.activationThreshold - currentUtilization) * contextWindow;
|
|
351
|
+
|
|
352
|
+
// Already past threshold, activation will handle it in transformContext
|
|
353
|
+
if (tokensUntilActivation <= 0) return;
|
|
354
|
+
|
|
355
|
+
const bufferInterval = this.buffering.computeBufferInterval(tokensUntilActivation, {
|
|
356
|
+
bufferTargetCycles: this.config.bufferTargetCycles,
|
|
357
|
+
bufferTokenCap: this.config.bufferTokenCap,
|
|
358
|
+
bufferMinTokens: this.config.bufferMinTokens,
|
|
359
|
+
utilityModelContextWindow: this.utilityModelContextWindow,
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
// Skip slot messages to avoid processing them as conversation content
|
|
363
|
+
const history = messages.slice(slotCount);
|
|
364
|
+
|
|
365
|
+
// Compute unobserved tokens (messages after buffer watermark)
|
|
366
|
+
const watermark = this.buffering.getWatermark();
|
|
367
|
+
const unobservedMessages = history.slice(watermark);
|
|
368
|
+
const unobservedTokens = unobservedMessages.reduce((sum, msg) => {
|
|
369
|
+
const content = typeof msg.content === 'string'
|
|
370
|
+
? msg.content
|
|
371
|
+
: JSON.stringify(msg.content);
|
|
372
|
+
return sum + estimateTokens(content);
|
|
373
|
+
}, 0);
|
|
374
|
+
|
|
375
|
+
if (this.buffering.shouldBuffer(unobservedTokens, bufferInterval)) {
|
|
376
|
+
// Snapshot the unobserved messages
|
|
377
|
+
const snapshot = [...unobservedMessages];
|
|
378
|
+
const endIndex = watermark + unobservedMessages.length;
|
|
379
|
+
|
|
380
|
+
this.buffering.launchObserver(
|
|
381
|
+
this.completeFn,
|
|
382
|
+
snapshot,
|
|
383
|
+
endIndex,
|
|
384
|
+
this.observations || null,
|
|
385
|
+
this.buildObserverConfig(),
|
|
386
|
+
this.logger ?? undefined,
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// -------------------------------------------------------------------------
|
|
392
|
+
// Slot content
|
|
393
|
+
// -------------------------------------------------------------------------
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Build the full observation slot content.
|
|
397
|
+
*
|
|
398
|
+
* Assembles the preamble, optional recall instructions, observation block,
|
|
399
|
+
* and optional continuation hints into a single string.
|
|
400
|
+
*/
|
|
401
|
+
buildSlotContent(): string {
|
|
402
|
+
let content = OBSERVATION_CONTEXT_PREAMBLE;
|
|
403
|
+
|
|
404
|
+
if (this.config.recall) {
|
|
405
|
+
content += OBSERVATION_RECALL_INSTRUCTIONS;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
content += '\n\n<observations>\n' + this.observations + '\n</observations>';
|
|
409
|
+
|
|
410
|
+
if (this.continuationHint) {
|
|
411
|
+
content += '\n\n<current-task>\n' + this.continuationHint.currentTask + '\n</current-task>';
|
|
412
|
+
content += '\n\n<suggested-response>\n' + this.continuationHint.suggestedResponse + '\n</suggested-response>';
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
return content;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Estimate the full slot content size for a given observation text.
|
|
420
|
+
*
|
|
421
|
+
* Used by the post-chunk utilization estimate to account for slot
|
|
422
|
+
* overhead (preamble, XML wrappers, continuation hints) rather than
|
|
423
|
+
* just the raw observation text.
|
|
424
|
+
*/
|
|
425
|
+
private buildSlotContentForEstimate(observationText: string): string {
|
|
426
|
+
let content = OBSERVATION_CONTEXT_PREAMBLE;
|
|
427
|
+
|
|
428
|
+
if (this.config.recall) {
|
|
429
|
+
content += OBSERVATION_RECALL_INSTRUCTIONS;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
content += '\n\n<observations>\n' + observationText + '\n</observations>';
|
|
433
|
+
|
|
434
|
+
if (this.continuationHint) {
|
|
435
|
+
content += '\n\n<current-task>\n' + this.continuationHint.currentTask + '\n</current-task>';
|
|
436
|
+
content += '\n\n<suggested-response>\n' + this.continuationHint.suggestedResponse + '\n</suggested-response>';
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
return content;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// -------------------------------------------------------------------------
|
|
443
|
+
// Manual trigger
|
|
444
|
+
// -------------------------------------------------------------------------
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* Force a synchronous observation cycle.
|
|
448
|
+
*
|
|
449
|
+
* Used by consumers after critical corrections to ensure the observation
|
|
450
|
+
* log captures the correction immediately.
|
|
451
|
+
*
|
|
452
|
+
* @param messages - The full message array (may include slot messages)
|
|
453
|
+
* @param slotCount - Number of slot messages to skip
|
|
454
|
+
*/
|
|
455
|
+
async triggerObservation(
|
|
456
|
+
messages: AgentMessage[],
|
|
457
|
+
slotCount: number,
|
|
458
|
+
): Promise<void> {
|
|
459
|
+
const history = messages.slice(slotCount);
|
|
460
|
+
if (!this.completeFn || history.length === 0) return;
|
|
461
|
+
|
|
462
|
+
const output = await runObserver(
|
|
463
|
+
this.completeFn,
|
|
464
|
+
history,
|
|
465
|
+
this.observations || null,
|
|
466
|
+
this.buildObserverConfig(),
|
|
467
|
+
);
|
|
468
|
+
|
|
469
|
+
// Merge observations
|
|
470
|
+
if (this.observations) {
|
|
471
|
+
this.observations += '\n\n' + output.observations;
|
|
472
|
+
} else {
|
|
473
|
+
this.observations = output.observations;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
if (output.currentTask || output.suggestedResponse) {
|
|
477
|
+
this.continuationHint = {
|
|
478
|
+
currentTask: output.currentTask ?? '',
|
|
479
|
+
suggestedResponse: output.suggestedResponse ?? '',
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
this.observationTokenCount = estimateTokens(this.observations);
|
|
484
|
+
|
|
485
|
+
// Fire observation event
|
|
486
|
+
this.fireObservationEvent({
|
|
487
|
+
compactedMessages: history,
|
|
488
|
+
observations: this.observations,
|
|
489
|
+
contextUtilization: 0, // manual trigger, utilization unknown
|
|
490
|
+
sync: true,
|
|
491
|
+
timestamp: new Date(),
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// -------------------------------------------------------------------------
|
|
496
|
+
// Kickstart buffer (session resumption)
|
|
497
|
+
// -------------------------------------------------------------------------
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* Kick off an initial async buffer on unobserved messages.
|
|
501
|
+
* Called during session resumption for a head start before the first prompt().
|
|
502
|
+
*/
|
|
503
|
+
kickstartBuffer(messages: AgentMessage[], slotCount: number): void {
|
|
504
|
+
if (!this.completeFn) return;
|
|
505
|
+
const history = messages.slice(slotCount);
|
|
506
|
+
if (history.length === 0) return;
|
|
507
|
+
const watermark = this.buffering.getWatermark();
|
|
508
|
+
const unobserved = history.slice(watermark);
|
|
509
|
+
if (unobserved.length === 0) return;
|
|
510
|
+
const unobservedTokens = unobserved.reduce(
|
|
511
|
+
(sum, m) => sum + estimateTokens(typeof m.content === 'string' ? m.content : JSON.stringify(m.content)),
|
|
512
|
+
0,
|
|
513
|
+
);
|
|
514
|
+
if (unobservedTokens < this.config.bufferMinTokens) return;
|
|
515
|
+
// Launch async observer on the unobserved messages (non-blocking)
|
|
516
|
+
this.buffering.launchObserver(
|
|
517
|
+
this.completeFn,
|
|
518
|
+
unobserved,
|
|
519
|
+
watermark + unobserved.length,
|
|
520
|
+
this.observations || null,
|
|
521
|
+
this.buildObserverConfig(),
|
|
522
|
+
this.logger ?? undefined,
|
|
523
|
+
);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// -------------------------------------------------------------------------
|
|
527
|
+
// State management
|
|
528
|
+
// -------------------------------------------------------------------------
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* Returns the current state for session persistence.
|
|
532
|
+
*/
|
|
533
|
+
getState(): ObservationalMemoryState {
|
|
534
|
+
return {
|
|
535
|
+
observations: this.observations,
|
|
536
|
+
continuationHint: this.continuationHint,
|
|
537
|
+
observationTokenCount: this.observationTokenCount,
|
|
538
|
+
generationCount: this.generationCount,
|
|
539
|
+
bufferedChunks: this.buffering.getState().chunks,
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* Restore state from a previous session.
|
|
545
|
+
*
|
|
546
|
+
* Sets observations, continuation hint, token count, and generation count.
|
|
547
|
+
* Restores buffering state. If a completeFn is available and observations
|
|
548
|
+
* exist, updates the slot content. Kicks off an initial async buffer on
|
|
549
|
+
* unobserved messages as a non-blocking head start.
|
|
550
|
+
*/
|
|
551
|
+
restoreState(state: ObservationalMemoryState): void {
|
|
552
|
+
this.observations = state.observations;
|
|
553
|
+
this.continuationHint = state.continuationHint;
|
|
554
|
+
this.observationTokenCount = state.observationTokenCount;
|
|
555
|
+
this.generationCount = state.generationCount;
|
|
556
|
+
|
|
557
|
+
// Discard buffered chunks from the previous session. Chunks represent
|
|
558
|
+
// observations that completed async but were never activated (merged
|
|
559
|
+
// into this.observations + messages trimmed). Restoring them with
|
|
560
|
+
// watermark=0 would merge their observations without trimming any
|
|
561
|
+
// messages, duplicating context. The observer will re-observe
|
|
562
|
+
// unobserved messages naturally on the next buffer cycle.
|
|
563
|
+
this.buffering.restoreState({
|
|
564
|
+
chunks: [],
|
|
565
|
+
watermark: 0,
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
* Returns the current slot content string.
|
|
571
|
+
*/
|
|
572
|
+
getSlotContent(): string {
|
|
573
|
+
return this.buildSlotContent();
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
/**
|
|
577
|
+
* Returns just the observation text.
|
|
578
|
+
*/
|
|
579
|
+
getObservations(): string {
|
|
580
|
+
return this.observations;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
/**
|
|
584
|
+
* Token count of activated observations only.
|
|
585
|
+
*/
|
|
586
|
+
getObservationTokenCount(): number {
|
|
587
|
+
return this.observationTokenCount;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
/**
|
|
592
|
+
* Whether the observer or reflector is currently running in the background.
|
|
593
|
+
*/
|
|
594
|
+
isProcessing(): boolean {
|
|
595
|
+
return this.buffering.isObserverInFlight() || this.buffering.isReflectorInFlight();
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
/**
|
|
599
|
+
* Whether the observer specifically is in-flight.
|
|
600
|
+
*/
|
|
601
|
+
isObserverInFlight(): boolean {
|
|
602
|
+
return this.buffering.isObserverInFlight();
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
/**
|
|
606
|
+
* Whether the reflector specifically is in-flight.
|
|
607
|
+
*/
|
|
608
|
+
isReflectorInFlight(): boolean {
|
|
609
|
+
return this.buffering.isReflectorInFlight();
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
/**
|
|
613
|
+
* Abort all in-flight operations. Delegates to buffering.abort().
|
|
614
|
+
*/
|
|
615
|
+
abort(): void {
|
|
616
|
+
this.buffering.abort();
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
/**
|
|
620
|
+
* Whether recall is configured.
|
|
621
|
+
*/
|
|
622
|
+
hasRecall(): boolean {
|
|
623
|
+
return this.config.recall !== undefined;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
/**
|
|
627
|
+
* Get the recall config if provided.
|
|
628
|
+
*/
|
|
629
|
+
getRecallConfig(): RecallConfig | undefined {
|
|
630
|
+
return this.config.recall;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
// -------------------------------------------------------------------------
|
|
634
|
+
// Private: Reflection handling
|
|
635
|
+
// -------------------------------------------------------------------------
|
|
636
|
+
|
|
637
|
+
/**
|
|
638
|
+
* Check and handle reflection after observation activation.
|
|
639
|
+
*
|
|
640
|
+
* Determines whether reflection should run (sync, async, or none) based
|
|
641
|
+
* on the current observation token count relative to the effective
|
|
642
|
+
* reflection threshold.
|
|
643
|
+
*/
|
|
644
|
+
private async handleReflection(): Promise<void> {
|
|
645
|
+
if (!this.completeFn) return;
|
|
646
|
+
|
|
647
|
+
const effectiveThreshold = computeEffectiveReflectionThreshold(
|
|
648
|
+
this.contextWindow,
|
|
649
|
+
this.config.reflectionThreshold,
|
|
650
|
+
this.utilityModelContextWindow,
|
|
651
|
+
);
|
|
652
|
+
|
|
653
|
+
const reflectionAction = this.buffering.shouldReflect(
|
|
654
|
+
this.observationTokenCount,
|
|
655
|
+
effectiveThreshold,
|
|
656
|
+
this.config.reflectionBufferActivation,
|
|
657
|
+
);
|
|
658
|
+
|
|
659
|
+
if (reflectionAction === 'none') return;
|
|
660
|
+
|
|
661
|
+
if (reflectionAction === 'sync') {
|
|
662
|
+
const previousObservations = this.observations;
|
|
663
|
+
|
|
664
|
+
// Check for a ready buffered reflection first
|
|
665
|
+
if (this.buffering.hasBufferedReflection()) {
|
|
666
|
+
const buffered = this.buffering.consumeBufferedReflection();
|
|
667
|
+
if (buffered) {
|
|
668
|
+
this.observations = buffered.observations;
|
|
669
|
+
this.observationTokenCount = estimateTokens(this.observations);
|
|
670
|
+
this.generationCount++;
|
|
671
|
+
|
|
672
|
+
this.fireReflectionEvent({
|
|
673
|
+
previousObservations,
|
|
674
|
+
newObservations: this.observations,
|
|
675
|
+
generationCount: this.generationCount,
|
|
676
|
+
compressionLevel: buffered.compressionLevel,
|
|
677
|
+
timestamp: new Date(),
|
|
678
|
+
});
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
// No buffered reflection, run synchronously
|
|
684
|
+
const output = await runReflector(
|
|
685
|
+
this.completeFn,
|
|
686
|
+
this.observations,
|
|
687
|
+
this.buildReflectorConfig(effectiveThreshold),
|
|
688
|
+
);
|
|
689
|
+
|
|
690
|
+
this.observations = output.observations;
|
|
691
|
+
this.observationTokenCount = estimateTokens(this.observations);
|
|
692
|
+
this.generationCount++;
|
|
693
|
+
|
|
694
|
+
this.fireReflectionEvent({
|
|
695
|
+
previousObservations,
|
|
696
|
+
newObservations: this.observations,
|
|
697
|
+
generationCount: this.generationCount,
|
|
698
|
+
compressionLevel: output.compressionLevel,
|
|
699
|
+
timestamp: new Date(),
|
|
700
|
+
});
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
if (reflectionAction === 'async') {
|
|
705
|
+
// Launch async reflector
|
|
706
|
+
const effectiveThresholdForReflector = computeEffectiveReflectionThreshold(
|
|
707
|
+
this.contextWindow,
|
|
708
|
+
this.config.reflectionThreshold,
|
|
709
|
+
this.utilityModelContextWindow,
|
|
710
|
+
);
|
|
711
|
+
|
|
712
|
+
this.buffering.launchReflector(
|
|
713
|
+
this.completeFn,
|
|
714
|
+
this.observations,
|
|
715
|
+
this.buildReflectorConfig(effectiveThresholdForReflector),
|
|
716
|
+
this.logger ?? undefined,
|
|
717
|
+
);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
// -------------------------------------------------------------------------
|
|
722
|
+
// Private: Config builders (exactOptionalPropertyTypes safe)
|
|
723
|
+
// -------------------------------------------------------------------------
|
|
724
|
+
|
|
725
|
+
/**
|
|
726
|
+
* Build the observer config object without assigning undefined to optional
|
|
727
|
+
* properties (exactOptionalPropertyTypes is enabled).
|
|
728
|
+
*/
|
|
729
|
+
private buildObserverConfig(): { previousObserverTokens: number; observerInstruction?: string } {
|
|
730
|
+
const config: { previousObserverTokens: number; observerInstruction?: string } = {
|
|
731
|
+
previousObserverTokens: this.config.previousObserverTokens,
|
|
732
|
+
};
|
|
733
|
+
if (this.config.observerInstruction !== undefined) {
|
|
734
|
+
config.observerInstruction = this.config.observerInstruction;
|
|
735
|
+
}
|
|
736
|
+
return config;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
/**
|
|
740
|
+
* Build the reflector config object without assigning undefined to optional
|
|
741
|
+
* properties (exactOptionalPropertyTypes is enabled).
|
|
742
|
+
*/
|
|
743
|
+
private buildReflectorConfig(threshold: number): { reflectionThreshold: number; reflectorInstruction?: string } {
|
|
744
|
+
const config: { reflectionThreshold: number; reflectorInstruction?: string } = {
|
|
745
|
+
reflectionThreshold: threshold,
|
|
746
|
+
};
|
|
747
|
+
if (this.config.reflectorInstruction !== undefined) {
|
|
748
|
+
config.reflectorInstruction = this.config.reflectorInstruction;
|
|
749
|
+
}
|
|
750
|
+
return config;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
// -------------------------------------------------------------------------
|
|
754
|
+
// Private: Chunk merging
|
|
755
|
+
// -------------------------------------------------------------------------
|
|
756
|
+
|
|
757
|
+
/**
|
|
758
|
+
* Merge buffered observation chunks into a single observation text.
|
|
759
|
+
*
|
|
760
|
+
* Concatenates all chunk observation texts with double newlines. Uses
|
|
761
|
+
* the latest chunk's currentTask and suggestedResponse (latest wins).
|
|
762
|
+
*/
|
|
763
|
+
private mergeChunks(
|
|
764
|
+
chunks: ObservationChunk[],
|
|
765
|
+
): { observations: string; hint: ContinuationHint | null } {
|
|
766
|
+
const observationParts: string[] = [];
|
|
767
|
+
|
|
768
|
+
// Find the latest chunk that produced at least one meaningful hint field.
|
|
769
|
+
// Each observer only sees its own batch of messages, so the latest chunk
|
|
770
|
+
// reflects the most recent state of the conversation. We do NOT mix fields
|
|
771
|
+
// across chunks: that would risk injecting a stale currentTask from an
|
|
772
|
+
// earlier chunk when the conversation has since moved on to something else.
|
|
773
|
+
//
|
|
774
|
+
// The parser already rejects placeholder-only content, so an observer that
|
|
775
|
+
// echoed the prompt template without filling it in will have undefined
|
|
776
|
+
// hint fields (not empty strings).
|
|
777
|
+
let latestHintChunk: ObservationChunk | null = null;
|
|
778
|
+
|
|
779
|
+
for (const chunk of chunks) {
|
|
780
|
+
observationParts.push(chunk.observations);
|
|
781
|
+
if (chunk.currentTask || chunk.suggestedResponse) {
|
|
782
|
+
latestHintChunk = chunk;
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
// If no chunk produced hints, preserve the existing hint from prior
|
|
787
|
+
// activation cycles. That hint is still more relevant than nothing, since
|
|
788
|
+
// it came from the observer that ran at the end of the previous cycle
|
|
789
|
+
// (itself more recent than anything older in this cycle).
|
|
790
|
+
const hint: ContinuationHint | null = latestHintChunk
|
|
791
|
+
? {
|
|
792
|
+
currentTask: latestHintChunk.currentTask ?? '',
|
|
793
|
+
suggestedResponse: latestHintChunk.suggestedResponse ?? '',
|
|
794
|
+
}
|
|
795
|
+
: this.continuationHint;
|
|
796
|
+
|
|
797
|
+
return {
|
|
798
|
+
observations: observationParts.join('\n\n'),
|
|
799
|
+
hint,
|
|
800
|
+
};
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
// -------------------------------------------------------------------------
|
|
804
|
+
// Private: Event firing
|
|
805
|
+
// -------------------------------------------------------------------------
|
|
806
|
+
|
|
807
|
+
/**
|
|
808
|
+
* Fire all observation handlers. Each handler is individually try/catch
|
|
809
|
+
* wrapped to prevent one handler from breaking others.
|
|
810
|
+
*/
|
|
811
|
+
private fireObservationEvent(event: ObservationEvent): void {
|
|
812
|
+
for (const handler of this.observationHandlers) {
|
|
813
|
+
try {
|
|
814
|
+
handler(event);
|
|
815
|
+
} catch (err) {
|
|
816
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
817
|
+
this.logger?.warn(`Observation handler threw: ${message}`);
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
/**
|
|
823
|
+
* Fire all reflection handlers. Each handler is individually try/catch
|
|
824
|
+
* wrapped to prevent one handler from breaking others.
|
|
825
|
+
*/
|
|
826
|
+
private fireReflectionEvent(event: ReflectionEvent): void {
|
|
827
|
+
for (const handler of this.reflectionHandlers) {
|
|
828
|
+
try {
|
|
829
|
+
handler(event);
|
|
830
|
+
} catch (err) {
|
|
831
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
832
|
+
this.logger?.warn(`Reflection handler threw: ${message}`);
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
|