@symerian/symi 3.0.18 → 3.0.19
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/dist/build-info.json +3 -3
- package/dist/canvas-host/a2ui/.bundle.hash +1 -1
- package/package.json +1 -1
- package/extensions/copilot-proxy/README.md +0 -24
- package/extensions/copilot-proxy/index.ts +0 -154
- package/extensions/copilot-proxy/node_modules/.bin/symi +0 -21
- package/extensions/copilot-proxy/package.json +0 -15
- package/extensions/copilot-proxy/symi.plugin.json +0 -9
- package/extensions/device-pair/index.ts +0 -642
- package/extensions/device-pair/symi.plugin.json +0 -20
- package/extensions/diagnostics-otel/index.ts +0 -15
- package/extensions/diagnostics-otel/node_modules/.bin/acorn +0 -21
- package/extensions/diagnostics-otel/node_modules/.bin/symi +0 -21
- package/extensions/diagnostics-otel/package.json +0 -27
- package/extensions/diagnostics-otel/src/service.test.ts +0 -290
- package/extensions/diagnostics-otel/src/service.ts +0 -666
- package/extensions/diagnostics-otel/symi.plugin.json +0 -8
- package/extensions/google-antigravity-auth/README.md +0 -24
- package/extensions/google-antigravity-auth/index.ts +0 -424
- package/extensions/google-antigravity-auth/node_modules/.bin/symi +0 -21
- package/extensions/google-antigravity-auth/package.json +0 -15
- package/extensions/google-antigravity-auth/symi.plugin.json +0 -9
- package/extensions/google-gemini-cli-auth/README.md +0 -35
- package/extensions/google-gemini-cli-auth/index.ts +0 -75
- package/extensions/google-gemini-cli-auth/node_modules/.bin/symi +0 -21
- package/extensions/google-gemini-cli-auth/oauth.test.ts +0 -162
- package/extensions/google-gemini-cli-auth/oauth.ts +0 -636
- package/extensions/google-gemini-cli-auth/package.json +0 -15
- package/extensions/google-gemini-cli-auth/symi.plugin.json +0 -9
- package/extensions/learning-loop/index.ts +0 -159
- package/extensions/learning-loop/node_modules/.bin/symi +0 -21
- package/extensions/learning-loop/package.json +0 -18
- package/extensions/learning-loop/src/analytics/gateway-methods.ts +0 -230
- package/extensions/learning-loop/src/analytics/metrics-aggregator.ts +0 -153
- package/extensions/learning-loop/src/capture/run-tracker.ts +0 -181
- package/extensions/learning-loop/src/capture/serializer.ts +0 -74
- package/extensions/learning-loop/src/db.ts +0 -583
- package/extensions/learning-loop/src/feedback/explicit-feedback.ts +0 -58
- package/extensions/learning-loop/src/feedback/implicit-signals.ts +0 -89
- package/extensions/learning-loop/src/graph/edge-inference.ts +0 -189
- package/extensions/learning-loop/src/graph/graph-retrieval.ts +0 -144
- package/extensions/learning-loop/src/graph/graph-store.ts +0 -183
- package/extensions/learning-loop/src/hooks.ts +0 -244
- package/extensions/learning-loop/src/injection/cache.ts +0 -73
- package/extensions/learning-loop/src/injection/context-injector.ts +0 -104
- package/extensions/learning-loop/src/injection/prompt-builder.ts +0 -43
- package/extensions/learning-loop/src/learning/embedding-bridge.ts +0 -54
- package/extensions/learning-loop/src/learning/learning-extractor.ts +0 -217
- package/extensions/learning-loop/src/learning/learning-store.ts +0 -158
- package/extensions/learning-loop/src/learning/retrieval.ts +0 -87
- package/extensions/learning-loop/src/math/confidence-intervals.ts +0 -62
- package/extensions/learning-loop/src/math/ewma.ts +0 -51
- package/extensions/learning-loop/src/math/weighted-scorer.ts +0 -42
- package/extensions/learning-loop/src/schema.ts +0 -176
- package/extensions/learning-loop/src/scoring/normalization.ts +0 -32
- package/extensions/learning-loop/src/scoring/quality-engine.ts +0 -78
- package/extensions/learning-loop/src/scoring/signal-extractors.ts +0 -155
- package/extensions/learning-loop/src/test/context-injector.test.ts +0 -142
- package/extensions/learning-loop/src/test/fixes.test.ts +0 -1286
- package/extensions/learning-loop/src/test/graph.test.ts +0 -711
- package/extensions/learning-loop/src/test/integration.test.ts +0 -312
- package/extensions/learning-loop/src/test/learning-store.test.ts +0 -191
- package/extensions/learning-loop/src/test/math.test.ts +0 -148
- package/extensions/learning-loop/src/test/quality-engine.test.ts +0 -231
- package/extensions/learning-loop/src/test/run-tracker.test.ts +0 -143
- package/extensions/learning-loop/src/types.ts +0 -281
- package/extensions/learning-loop/symi.plugin.json +0 -46
- package/extensions/llm-task/README.md +0 -97
- package/extensions/llm-task/index.ts +0 -6
- package/extensions/llm-task/package.json +0 -12
- package/extensions/llm-task/src/llm-task-tool.test.ts +0 -138
- package/extensions/llm-task/src/llm-task-tool.ts +0 -249
- package/extensions/llm-task/symi.plugin.json +0 -21
- package/extensions/memory-lancedb/config.ts +0 -161
- package/extensions/memory-lancedb/index.test.ts +0 -330
- package/extensions/memory-lancedb/index.ts +0 -670
- package/extensions/memory-lancedb/node_modules/.bin/arrow2csv +0 -21
- package/extensions/memory-lancedb/node_modules/.bin/openai +0 -21
- package/extensions/memory-lancedb/node_modules/.bin/symi +0 -21
- package/extensions/memory-lancedb/package.json +0 -20
- package/extensions/memory-lancedb/symi.plugin.json +0 -71
- package/extensions/minimax-portal-auth/README.md +0 -33
- package/extensions/minimax-portal-auth/index.ts +0 -161
- package/extensions/minimax-portal-auth/node_modules/.bin/symi +0 -21
- package/extensions/minimax-portal-auth/oauth.ts +0 -247
- package/extensions/minimax-portal-auth/package.json +0 -15
- package/extensions/minimax-portal-auth/symi.plugin.json +0 -9
- package/extensions/model-equalizer/index.ts +0 -80
- package/extensions/model-equalizer/skills/model-equalizer/SKILL.md +0 -58
- package/extensions/model-equalizer/src/detection.ts +0 -62
- package/extensions/model-equalizer/src/enhancer.ts +0 -63
- package/extensions/model-equalizer/src/test/detection.test.ts +0 -218
- package/extensions/model-equalizer/src/test/enhancer.test.ts +0 -137
- package/extensions/model-equalizer/src/test/integration.test.ts +0 -185
- package/extensions/model-equalizer/src/types.ts +0 -24
- package/extensions/model-equalizer/symi.plugin.json +0 -12
- package/extensions/phone-control/index.ts +0 -421
- package/extensions/phone-control/symi.plugin.json +0 -10
- package/extensions/pipeline/README.md +0 -75
- package/extensions/pipeline/SKILL.md +0 -97
- package/extensions/pipeline/index.ts +0 -18
- package/extensions/pipeline/package.json +0 -11
- package/extensions/pipeline/src/pipeline-tool.test.ts +0 -345
- package/extensions/pipeline/src/pipeline-tool.ts +0 -266
- package/extensions/pipeline/src/windows-spawn.test.ts +0 -148
- package/extensions/pipeline/src/windows-spawn.ts +0 -193
- package/extensions/pipeline/symi.plugin.json +0 -10
- package/extensions/qwen-portal-auth/README.md +0 -24
- package/extensions/qwen-portal-auth/index.ts +0 -134
- package/extensions/qwen-portal-auth/oauth.ts +0 -190
- package/extensions/qwen-portal-auth/symi.plugin.json +0 -9
- package/extensions/talk-voice/index.ts +0 -150
- package/extensions/talk-voice/symi.plugin.json +0 -10
- package/extensions/thread-ownership/index.test.ts +0 -180
- package/extensions/thread-ownership/index.ts +0 -133
- package/extensions/thread-ownership/symi.plugin.json +0 -28
|
@@ -1,244 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Hook registration for the learning loop plugin.
|
|
3
|
-
*
|
|
4
|
-
* | Priority | Hook | Role |
|
|
5
|
-
* |----------|---------------------|-----------------------------------------|
|
|
6
|
-
* | 200 | before_prompt_build | Inject learnings (runs first) |
|
|
7
|
-
* | 100 | llm_input | Capture run start metadata |
|
|
8
|
-
* | 100 | llm_output | Capture usage + response data |
|
|
9
|
-
* | 100 | after_tool_call | Capture tool call traces |
|
|
10
|
-
* | 100 | subagent_ended | Capture subagent outcomes |
|
|
11
|
-
* | 50 | agent_end | Score run, extract learnings, persist |
|
|
12
|
-
* | 50 | message_received | Detect implicit feedback |
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import { hashText } from "../../../src/memory/internal.js";
|
|
16
|
-
import type { SymiPluginApi, PluginLogger } from "../../../src/plugins/types.js";
|
|
17
|
-
import type { MetricsAggregator } from "./analytics/metrics-aggregator.js";
|
|
18
|
-
import type { RunTracker } from "./capture/run-tracker.js";
|
|
19
|
-
import { normalizeCompletedRun, reconstructCompletedRun } from "./capture/serializer.js";
|
|
20
|
-
import type { DatabaseManager } from "./db.js";
|
|
21
|
-
import type { ImplicitSignals } from "./feedback/implicit-signals.js";
|
|
22
|
-
import type { EdgeInference } from "./graph/edge-inference.js";
|
|
23
|
-
import type { ContextInjector } from "./injection/context-injector.js";
|
|
24
|
-
import type { LearningExtractor } from "./learning/learning-extractor.js";
|
|
25
|
-
import type { LearningStore } from "./learning/learning-store.js";
|
|
26
|
-
import type { QualityEngine } from "./scoring/quality-engine.js";
|
|
27
|
-
import type { LearningLoopConfig } from "./types.js";
|
|
28
|
-
|
|
29
|
-
export function registerHooks(
|
|
30
|
-
api: SymiPluginApi,
|
|
31
|
-
deps: {
|
|
32
|
-
config: LearningLoopConfig;
|
|
33
|
-
logger: PluginLogger;
|
|
34
|
-
db: DatabaseManager;
|
|
35
|
-
tracker: RunTracker;
|
|
36
|
-
qualityEngine: QualityEngine;
|
|
37
|
-
learningStore: LearningStore;
|
|
38
|
-
extractor: LearningExtractor;
|
|
39
|
-
injector: ContextInjector;
|
|
40
|
-
metrics: MetricsAggregator;
|
|
41
|
-
implicit: ImplicitSignals;
|
|
42
|
-
edgeInference?: EdgeInference;
|
|
43
|
-
},
|
|
44
|
-
) {
|
|
45
|
-
const {
|
|
46
|
-
config,
|
|
47
|
-
logger,
|
|
48
|
-
db,
|
|
49
|
-
tracker,
|
|
50
|
-
qualityEngine,
|
|
51
|
-
learningStore,
|
|
52
|
-
extractor,
|
|
53
|
-
injector,
|
|
54
|
-
metrics,
|
|
55
|
-
implicit,
|
|
56
|
-
edgeInference,
|
|
57
|
-
} = deps;
|
|
58
|
-
|
|
59
|
-
// -----------------------------------------------------------------------
|
|
60
|
-
// before_prompt_build (priority 200) -- Inject learnings
|
|
61
|
-
// -----------------------------------------------------------------------
|
|
62
|
-
api.on(
|
|
63
|
-
"before_prompt_build",
|
|
64
|
-
async (event, ctx) => {
|
|
65
|
-
try {
|
|
66
|
-
const context = await injector.getContext(event.prompt);
|
|
67
|
-
if (context) {
|
|
68
|
-
return { prependContext: context };
|
|
69
|
-
}
|
|
70
|
-
} catch (err) {
|
|
71
|
-
logger.warn(`[learning-loop] before_prompt_build failed: ${String(err)}`);
|
|
72
|
-
}
|
|
73
|
-
return undefined;
|
|
74
|
-
},
|
|
75
|
-
{ priority: 200 },
|
|
76
|
-
);
|
|
77
|
-
|
|
78
|
-
// -----------------------------------------------------------------------
|
|
79
|
-
// llm_input (priority 100) -- Capture run start metadata
|
|
80
|
-
// -----------------------------------------------------------------------
|
|
81
|
-
api.on(
|
|
82
|
-
"llm_input",
|
|
83
|
-
(event, ctx) => {
|
|
84
|
-
try {
|
|
85
|
-
tracker.onLlmInput({
|
|
86
|
-
runId: event.runId,
|
|
87
|
-
sessionId: event.sessionId,
|
|
88
|
-
provider: event.provider,
|
|
89
|
-
model: event.model,
|
|
90
|
-
prompt: event.prompt,
|
|
91
|
-
sessionKey: ctx.sessionKey,
|
|
92
|
-
agentId: ctx.agentId,
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
// Record prompt for implicit feedback analysis
|
|
96
|
-
implicit.recordPrompt(event.runId, event.prompt);
|
|
97
|
-
} catch (err) {
|
|
98
|
-
logger.warn(`[learning-loop] llm_input handler failed: ${String(err)}`);
|
|
99
|
-
}
|
|
100
|
-
},
|
|
101
|
-
{ priority: 100 },
|
|
102
|
-
);
|
|
103
|
-
|
|
104
|
-
// -----------------------------------------------------------------------
|
|
105
|
-
// llm_output (priority 100) -- Capture usage + response data
|
|
106
|
-
// -----------------------------------------------------------------------
|
|
107
|
-
api.on(
|
|
108
|
-
"llm_output",
|
|
109
|
-
(event, ctx) => {
|
|
110
|
-
try {
|
|
111
|
-
tracker.onLlmOutput({
|
|
112
|
-
runId: event.runId,
|
|
113
|
-
sessionId: event.sessionId,
|
|
114
|
-
assistantTexts: event.assistantTexts,
|
|
115
|
-
usage: event.usage,
|
|
116
|
-
});
|
|
117
|
-
} catch (err) {
|
|
118
|
-
logger.warn(`[learning-loop] llm_output handler failed: ${String(err)}`);
|
|
119
|
-
}
|
|
120
|
-
},
|
|
121
|
-
{ priority: 100 },
|
|
122
|
-
);
|
|
123
|
-
|
|
124
|
-
// -----------------------------------------------------------------------
|
|
125
|
-
// after_tool_call (priority 100) -- Capture tool call traces
|
|
126
|
-
// -----------------------------------------------------------------------
|
|
127
|
-
api.on(
|
|
128
|
-
"after_tool_call",
|
|
129
|
-
(event, ctx) => {
|
|
130
|
-
try {
|
|
131
|
-
tracker.onToolCall({
|
|
132
|
-
toolName: event.toolName,
|
|
133
|
-
durationMs: event.durationMs,
|
|
134
|
-
success: !event.error,
|
|
135
|
-
error: event.error,
|
|
136
|
-
paramHash: hashText(JSON.stringify(event.params)),
|
|
137
|
-
sessionKey: ctx.sessionKey,
|
|
138
|
-
});
|
|
139
|
-
} catch (err) {
|
|
140
|
-
logger.warn(`[learning-loop] after_tool_call handler failed: ${String(err)}`);
|
|
141
|
-
}
|
|
142
|
-
},
|
|
143
|
-
{ priority: 100 },
|
|
144
|
-
);
|
|
145
|
-
|
|
146
|
-
// -----------------------------------------------------------------------
|
|
147
|
-
// subagent_ended (priority 100) -- Capture subagent outcomes
|
|
148
|
-
// -----------------------------------------------------------------------
|
|
149
|
-
api.on(
|
|
150
|
-
"subagent_ended",
|
|
151
|
-
(event, ctx) => {
|
|
152
|
-
// Subagent outcomes are tracked as metadata; no separate storage needed.
|
|
153
|
-
// The parent agent's agent_end will finalize the run.
|
|
154
|
-
},
|
|
155
|
-
{ priority: 100 },
|
|
156
|
-
);
|
|
157
|
-
|
|
158
|
-
// -----------------------------------------------------------------------
|
|
159
|
-
// agent_end (priority 50) -- Score run, extract learnings, persist
|
|
160
|
-
// -----------------------------------------------------------------------
|
|
161
|
-
api.on(
|
|
162
|
-
"agent_end",
|
|
163
|
-
async (event, ctx) => {
|
|
164
|
-
try {
|
|
165
|
-
const completedRaw = tracker.finalize({
|
|
166
|
-
sessionKey: ctx.sessionKey,
|
|
167
|
-
agentId: ctx.agentId,
|
|
168
|
-
sessionId: ctx.sessionId,
|
|
169
|
-
success: event.success,
|
|
170
|
-
error: event.error,
|
|
171
|
-
durationMs: event.durationMs,
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
if (!completedRaw) return;
|
|
175
|
-
|
|
176
|
-
const completed = normalizeCompletedRun(completedRaw);
|
|
177
|
-
|
|
178
|
-
// Score the run
|
|
179
|
-
const score = qualityEngine.scoreRun(completed);
|
|
180
|
-
|
|
181
|
-
// Persist run with score
|
|
182
|
-
db.insertRun(completed, score);
|
|
183
|
-
|
|
184
|
-
// Update metrics bucket
|
|
185
|
-
metrics.recordRun(completed, score);
|
|
186
|
-
|
|
187
|
-
// Track which learnings were injected into this run
|
|
188
|
-
injector.trackRunAppliedIds(completed.runId);
|
|
189
|
-
const appliedIds = injector.getAppliedIds(completed.runId);
|
|
190
|
-
|
|
191
|
-
// Extract learnings (async: embeds content if bridge is available)
|
|
192
|
-
const extractedIds = await extractor.extract(completed, score);
|
|
193
|
-
|
|
194
|
-
// Infer graph edges from run scoring
|
|
195
|
-
if (edgeInference) {
|
|
196
|
-
edgeInference.onRunScored(completed.runId, appliedIds, extractedIds);
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// Prune old runs if needed (learnings are preserved with run_id='__pruned__')
|
|
200
|
-
db.pruneOldRuns(config.capture.maxRuns);
|
|
201
|
-
|
|
202
|
-
// Prune genuinely stale learnings (unapplied, low-confidence, >90 days old)
|
|
203
|
-
db.pruneStaleLearnings(90 * 24 * 60 * 60 * 1000);
|
|
204
|
-
|
|
205
|
-
logger.debug?.(
|
|
206
|
-
`[learning-loop] run ${completed.runId}: score=${score.score.toFixed(3)} ` +
|
|
207
|
-
`(${completed.provider}/${completed.model}, ${completed.toolCalls.length} tool calls)`,
|
|
208
|
-
);
|
|
209
|
-
} catch (err) {
|
|
210
|
-
logger.warn(`[learning-loop] agent_end handler failed: ${String(err)}`);
|
|
211
|
-
}
|
|
212
|
-
},
|
|
213
|
-
{ priority: 50 },
|
|
214
|
-
);
|
|
215
|
-
|
|
216
|
-
// -----------------------------------------------------------------------
|
|
217
|
-
// message_received (priority 50) -- Detect implicit feedback
|
|
218
|
-
// -----------------------------------------------------------------------
|
|
219
|
-
api.on(
|
|
220
|
-
"message_received",
|
|
221
|
-
(event, ctx) => {
|
|
222
|
-
try {
|
|
223
|
-
const feedback = implicit.analyzeMessage(event.content, event.timestamp ?? Date.now());
|
|
224
|
-
|
|
225
|
-
// Close the feedback loop: rescore the associated run
|
|
226
|
-
if (feedback) {
|
|
227
|
-
const runRow = db.getRun(feedback.runId);
|
|
228
|
-
if (runRow) {
|
|
229
|
-
const toolCalls = db.getToolCalls(feedback.runId);
|
|
230
|
-
const completedRun = reconstructCompletedRun(runRow, toolCalls);
|
|
231
|
-
const newScore = qualityEngine.rescoreWithFeedback(completedRun, {
|
|
232
|
-
source: feedback.source as "explicit" | "implicit",
|
|
233
|
-
score: feedback.score,
|
|
234
|
-
});
|
|
235
|
-
db.updateRunScore(feedback.runId, newScore.score);
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
} catch (err) {
|
|
239
|
-
logger.warn(`[learning-loop] message_received handler failed: ${String(err)}`);
|
|
240
|
-
}
|
|
241
|
-
},
|
|
242
|
-
{ priority: 50 },
|
|
243
|
-
);
|
|
244
|
-
}
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* LRU cache for context injection results.
|
|
3
|
-
* Target: >85% hit rate, O(1) lookup.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
export type CacheEntry<T> = {
|
|
7
|
-
value: T;
|
|
8
|
-
expiresAt: number;
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
export type LRUCache<T> = ReturnType<typeof createLRUCache<T>>;
|
|
12
|
-
|
|
13
|
-
export function createLRUCache<T>(maxSize: number = 128, ttlMs: number = 60_000) {
|
|
14
|
-
const cache = new Map<string, CacheEntry<T>>();
|
|
15
|
-
let hits = 0;
|
|
16
|
-
let misses = 0;
|
|
17
|
-
|
|
18
|
-
function get(key: string): T | undefined {
|
|
19
|
-
const entry = cache.get(key);
|
|
20
|
-
if (!entry) {
|
|
21
|
-
misses++;
|
|
22
|
-
return undefined;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
if (Date.now() > entry.expiresAt) {
|
|
26
|
-
cache.delete(key);
|
|
27
|
-
misses++;
|
|
28
|
-
return undefined;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// Move to end (most recently used) by re-inserting
|
|
32
|
-
cache.delete(key);
|
|
33
|
-
cache.set(key, entry);
|
|
34
|
-
hits++;
|
|
35
|
-
return entry.value;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function set(key: string, value: T): void {
|
|
39
|
-
// Evict oldest if at capacity
|
|
40
|
-
if (cache.size >= maxSize) {
|
|
41
|
-
const oldest = cache.keys().next().value;
|
|
42
|
-
if (oldest !== undefined) {
|
|
43
|
-
cache.delete(oldest);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
cache.set(key, {
|
|
48
|
-
value,
|
|
49
|
-
expiresAt: Date.now() + ttlMs,
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function invalidate(key: string): void {
|
|
54
|
-
cache.delete(key);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function clear(): void {
|
|
58
|
-
cache.clear();
|
|
59
|
-
hits = 0;
|
|
60
|
-
misses = 0;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function getHitRate(): number {
|
|
64
|
-
const total = hits + misses;
|
|
65
|
-
return total === 0 ? 0 : hits / total;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function getStats(): { size: number; hits: number; misses: number; hitRate: number } {
|
|
69
|
-
return { size: cache.size, hits, misses, hitRate: getHitRate() };
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
return { get, set, invalidate, clear, getHitRate, getStats };
|
|
73
|
-
}
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Context injection for before_prompt_build hook.
|
|
3
|
-
*
|
|
4
|
-
* On each prompt build:
|
|
5
|
-
* 1. Hash prompt -> check LRU cache (target >85% hit rate, O(1))
|
|
6
|
-
* 2. Cache miss: query learnings via cosine similarity or FTS5 fallback
|
|
7
|
-
* 3. Apply temporal decay and MMR rerank for diversity
|
|
8
|
-
* 4. Format top N as <learnings> context block
|
|
9
|
-
* 5. Return { prependContext }
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import { hashText } from "../../../../src/memory/internal.js";
|
|
13
|
-
import type { DatabaseManager } from "../db.js";
|
|
14
|
-
import type { GraphRetrieval } from "../graph/graph-retrieval.js";
|
|
15
|
-
import type { EmbeddingBridge } from "../learning/embedding-bridge.js";
|
|
16
|
-
import type { LearningStore } from "../learning/learning-store.js";
|
|
17
|
-
import { retrieveLearnings, type RetrievalResult } from "../learning/retrieval.js";
|
|
18
|
-
import type { LearningLoopConfig, RunId } from "../types.js";
|
|
19
|
-
import { createLRUCache } from "./cache.js";
|
|
20
|
-
import { formatLearningsContext } from "./prompt-builder.js";
|
|
21
|
-
|
|
22
|
-
export type ContextInjector = ReturnType<typeof createContextInjector>;
|
|
23
|
-
|
|
24
|
-
export function createContextInjector(params: {
|
|
25
|
-
db: DatabaseManager;
|
|
26
|
-
learningStore: LearningStore;
|
|
27
|
-
config: LearningLoopConfig;
|
|
28
|
-
embeddingBridge?: EmbeddingBridge;
|
|
29
|
-
graphRetrieval?: GraphRetrieval;
|
|
30
|
-
}) {
|
|
31
|
-
const { db, learningStore, config, embeddingBridge, graphRetrieval } = params;
|
|
32
|
-
const cache = createLRUCache<string>(128, config.injection.cacheTtlMs);
|
|
33
|
-
const appliedIdsMap = new Map<RunId, string[]>();
|
|
34
|
-
let lastAppliedIds: string[] = [];
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Generate context to prepend to the agent prompt.
|
|
38
|
-
* Returns empty string if no relevant learnings exist.
|
|
39
|
-
*/
|
|
40
|
-
async function getContext(prompt: string): Promise<string> {
|
|
41
|
-
const promptKey = hashText(prompt);
|
|
42
|
-
|
|
43
|
-
// Check cache first
|
|
44
|
-
const cached = cache.get(promptKey);
|
|
45
|
-
if (cached !== undefined) return cached;
|
|
46
|
-
|
|
47
|
-
// Get query embedding if bridge is available
|
|
48
|
-
let queryEmbedding: number[] | null = null;
|
|
49
|
-
if (embeddingBridge?.available()) {
|
|
50
|
-
queryEmbedding = await embeddingBridge.embed(prompt);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Retrieve relevant learnings
|
|
54
|
-
const results = retrieveLearnings({
|
|
55
|
-
learningStore,
|
|
56
|
-
config,
|
|
57
|
-
queryEmbedding,
|
|
58
|
-
queryText: prompt,
|
|
59
|
-
graphRetrieval,
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
// Record applications and track applied IDs
|
|
63
|
-
const appliedIds: string[] = [];
|
|
64
|
-
for (const result of results) {
|
|
65
|
-
learningStore.recordApplication(result.learning.id);
|
|
66
|
-
appliedIds.push(result.learning.id);
|
|
67
|
-
}
|
|
68
|
-
lastAppliedIds = appliedIds;
|
|
69
|
-
|
|
70
|
-
// Format context
|
|
71
|
-
const context = formatLearningsContext(results, config);
|
|
72
|
-
|
|
73
|
-
// Cache result
|
|
74
|
-
cache.set(promptKey, context);
|
|
75
|
-
|
|
76
|
-
return context;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Associate the most recently injected learning IDs with a run ID.
|
|
81
|
-
*/
|
|
82
|
-
function trackRunAppliedIds(runId: RunId): void {
|
|
83
|
-
if (lastAppliedIds.length > 0) {
|
|
84
|
-
appliedIdsMap.set(runId, [...lastAppliedIds]);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Get the learning IDs that were injected into a specific run.
|
|
90
|
-
*/
|
|
91
|
-
function getAppliedIds(runId: RunId): string[] {
|
|
92
|
-
return appliedIdsMap.get(runId) ?? [];
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function getCacheStats() {
|
|
96
|
-
return cache.getStats();
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function clearCache(): void {
|
|
100
|
-
cache.clear();
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
return { getContext, trackRunAppliedIds, getAppliedIds, getCacheStats, clearCache };
|
|
104
|
-
}
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Format retrieved learnings into context text for prompt injection.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import type { RetrievalResult } from "../learning/retrieval.js";
|
|
6
|
-
import type { LearningLoopConfig } from "../types.js";
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Format learnings into a context block that will be prepended to the agent prompt.
|
|
10
|
-
*
|
|
11
|
-
* Respects maxTokens by estimating token count (chars / 4) and truncating.
|
|
12
|
-
* Returns empty string if no learnings pass the threshold.
|
|
13
|
-
*/
|
|
14
|
-
export function formatLearningsContext(
|
|
15
|
-
results: RetrievalResult[],
|
|
16
|
-
config: LearningLoopConfig,
|
|
17
|
-
): string {
|
|
18
|
-
if (results.length === 0) return "";
|
|
19
|
-
|
|
20
|
-
const maxTokens = config.injection.maxTokens;
|
|
21
|
-
const lines: string[] = [];
|
|
22
|
-
let estimatedTokens = 0;
|
|
23
|
-
|
|
24
|
-
// Header
|
|
25
|
-
const header = "<learnings>";
|
|
26
|
-
const footer = "</learnings>";
|
|
27
|
-
estimatedTokens += (header.length + footer.length) / 4;
|
|
28
|
-
|
|
29
|
-
for (const result of results) {
|
|
30
|
-
const contested = result.contested ? " [CONTESTED]" : "";
|
|
31
|
-
const line = `- [${result.learning.category}] ${result.learning.content}${contested}`;
|
|
32
|
-
const lineTokens = line.length / 4;
|
|
33
|
-
|
|
34
|
-
if (estimatedTokens + lineTokens > maxTokens) break;
|
|
35
|
-
|
|
36
|
-
lines.push(line);
|
|
37
|
-
estimatedTokens += lineTokens;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
if (lines.length === 0) return "";
|
|
41
|
-
|
|
42
|
-
return `${header}\n${lines.join("\n")}\n${footer}`;
|
|
43
|
-
}
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Bridge to the existing embedding provider system.
|
|
3
|
-
* Uses the same multi-provider system (OpenAI, Gemini, Voyage, local ONNX)
|
|
4
|
-
* that the memory system uses.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import type { EmbeddingProvider } from "../../../../src/memory/embeddings.js";
|
|
8
|
-
import type { PluginLogger } from "../../../../src/plugins/types.js";
|
|
9
|
-
|
|
10
|
-
export type EmbeddingBridge = {
|
|
11
|
-
embed: (text: string) => Promise<number[] | null>;
|
|
12
|
-
embedBatch: (texts: string[]) => Promise<Array<number[] | null>>;
|
|
13
|
-
available: () => boolean;
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Create an embedding bridge that wraps an existing EmbeddingProvider.
|
|
18
|
-
* If no provider is available, embed() returns null gracefully.
|
|
19
|
-
*/
|
|
20
|
-
export function createEmbeddingBridge(params: {
|
|
21
|
-
provider: EmbeddingProvider | null;
|
|
22
|
-
logger: PluginLogger;
|
|
23
|
-
}): EmbeddingBridge {
|
|
24
|
-
const { provider, logger } = params;
|
|
25
|
-
|
|
26
|
-
async function embed(text: string): Promise<number[] | null> {
|
|
27
|
-
if (!provider) return null;
|
|
28
|
-
|
|
29
|
-
try {
|
|
30
|
-
return await provider.embedQuery(text);
|
|
31
|
-
} catch (err) {
|
|
32
|
-
logger.warn(`[learning-loop] embedding failed: ${String(err)}`);
|
|
33
|
-
return null;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
async function embedBatch(texts: string[]): Promise<Array<number[] | null>> {
|
|
38
|
-
if (!provider) return texts.map(() => null);
|
|
39
|
-
|
|
40
|
-
try {
|
|
41
|
-
const results = await provider.embedBatch(texts);
|
|
42
|
-
return results;
|
|
43
|
-
} catch (err) {
|
|
44
|
-
logger.warn(`[learning-loop] batch embedding failed: ${String(err)}`);
|
|
45
|
-
return texts.map(() => null);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function available(): boolean {
|
|
50
|
-
return provider !== null;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
return { embed, embedBatch, available };
|
|
54
|
-
}
|