nemoris 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +49 -49
- package/LICENSE +21 -21
- package/README.md +209 -209
- package/SECURITY.md +59 -119
- package/bin/nemoris +46 -46
- package/config/agents/agent.toml.example +28 -28
- package/config/agents/content.toml +23 -0
- package/config/agents/default.toml +22 -22
- package/config/agents/heartbeat.toml +35 -0
- package/config/agents/iris.toml +23 -0
- package/config/agents/lab.toml +23 -0
- package/config/agents/main.toml +45 -0
- package/config/agents/nemo.toml +21 -0
- package/config/agents/ops.toml +38 -0
- package/config/agents/orchestrator.toml +18 -18
- package/config/agents/revenue.toml +23 -0
- package/config/agents/testyboo.toml +19 -0
- package/config/delivery.toml +73 -73
- package/config/embeddings.toml +5 -5
- package/config/identity/content-purpose.md +11 -0
- package/config/identity/content-soul.md +45 -0
- package/config/identity/default-purpose.md +1 -1
- package/config/identity/default-soul.md +3 -3
- package/config/identity/heartbeat-purpose.md +9 -0
- package/config/identity/heartbeat-soul.md +16 -0
- package/config/identity/iris-purpose.md +17 -0
- package/config/identity/iris-soul.md +68 -0
- package/config/identity/lab-purpose.md +10 -0
- package/config/identity/lab-soul.md +38 -0
- package/config/identity/main-purpose.md +17 -0
- package/config/identity/main-soul.md +66 -0
- package/config/identity/main-user.md +22 -0
- package/config/identity/ops-purpose.md +9 -0
- package/config/identity/ops-soul.md +16 -0
- package/config/identity/orchestrator-purpose.md +1 -1
- package/config/identity/orchestrator-soul.md +1 -1
- package/config/identity/revenue-purpose.md +9 -0
- package/config/identity/revenue-soul.md +41 -0
- package/config/identity/testyboo-purpose.md +13 -0
- package/config/identity/testyboo-soul.md +20 -0
- package/config/improvement-targets.toml +15 -15
- package/config/jobs/heartbeat-check.toml +30 -30
- package/config/jobs/memory-rollup.toml +46 -46
- package/config/jobs/workspace-health.toml +63 -63
- package/config/mcp.toml +16 -16
- package/config/output-contracts.toml +17 -17
- package/config/peers.toml +32 -32
- package/config/peers.toml.example +32 -32
- package/config/policies/memory-default.toml +10 -10
- package/config/policies/memory-heartbeat.toml +5 -5
- package/config/policies/memory-ops.toml +10 -10
- package/config/policies/tools-heartbeat-minimal.toml +8 -8
- package/config/policies/tools-interactive-safe.toml +8 -8
- package/config/policies/tools-ops-bounded.toml +8 -8
- package/config/policies/tools-orchestrator.toml +7 -7
- package/config/providers/anthropic.toml +15 -15
- package/config/providers/ollama.toml +5 -5
- package/config/providers/openai-codex.toml +9 -9
- package/config/providers/openrouter.toml +5 -5
- package/config/router.toml +22 -22
- package/config/runtime.toml +114 -114
- package/config/skills/self-improvement.toml +15 -15
- package/config/skills/telegram-onboarding-spec.md +240 -240
- package/config/skills/workspace-monitor.toml +15 -15
- package/config/task-router.toml +42 -42
- package/install.sh +50 -50
- package/package.json +91 -90
- package/src/auth/auth-profiles.js +169 -169
- package/src/auth/openai-codex-oauth.js +285 -285
- package/src/battle.js +449 -449
- package/src/cli/help.js +265 -265
- package/src/cli/output-filter.js +49 -49
- package/src/cli/runtime-control.js +704 -704
- package/src/cli-main.js +2763 -2763
- package/src/cli.js +78 -78
- package/src/config/loader.js +332 -332
- package/src/config/schema-validator.js +214 -214
- package/src/config/toml-lite.js +8 -8
- package/src/daemon/action-handlers.js +71 -71
- package/src/daemon/healing-tick.js +87 -87
- package/src/daemon/health-probes.js +90 -90
- package/src/daemon/notifier.js +57 -57
- package/src/daemon/nurse.js +218 -218
- package/src/daemon/repair-log.js +106 -106
- package/src/daemon/rule-staging.js +90 -90
- package/src/daemon/rules.js +29 -29
- package/src/daemon/telegram-commands.js +54 -54
- package/src/daemon/updater.js +85 -85
- package/src/jobs/job-runner.js +78 -78
- package/src/mcp/consumer.js +129 -129
- package/src/memory/active-recall.js +171 -171
- package/src/memory/backend-manager.js +97 -97
- package/src/memory/backends/file-backend.js +38 -38
- package/src/memory/backends/qmd-backend.js +219 -219
- package/src/memory/embedding-guards.js +24 -24
- package/src/memory/embedding-index.js +118 -118
- package/src/memory/embedding-service.js +179 -179
- package/src/memory/file-index.js +177 -177
- package/src/memory/memory-signature.js +5 -5
- package/src/memory/memory-store.js +648 -648
- package/src/memory/retrieval-planner.js +66 -66
- package/src/memory/scoring.js +145 -145
- package/src/memory/simhash.js +78 -78
- package/src/memory/sqlite-active-store.js +824 -824
- package/src/memory/write-policy.js +36 -36
- package/src/onboarding/aliases.js +33 -33
- package/src/onboarding/auth/api-key.js +224 -224
- package/src/onboarding/auth/ollama-detect.js +42 -42
- package/src/onboarding/clack-prompter.js +77 -77
- package/src/onboarding/doctor.js +530 -530
- package/src/onboarding/lock.js +42 -42
- package/src/onboarding/model-catalog.js +344 -344
- package/src/onboarding/phases/auth.js +576 -589
- package/src/onboarding/phases/build.js +130 -130
- package/src/onboarding/phases/choose.js +82 -82
- package/src/onboarding/phases/detect.js +98 -98
- package/src/onboarding/phases/hatch.js +216 -216
- package/src/onboarding/phases/identity.js +79 -79
- package/src/onboarding/phases/ollama.js +345 -345
- package/src/onboarding/phases/scaffold.js +99 -99
- package/src/onboarding/phases/telegram.js +377 -377
- package/src/onboarding/phases/validate.js +204 -204
- package/src/onboarding/phases/verify.js +206 -206
- package/src/onboarding/platform.js +482 -482
- package/src/onboarding/status-bar.js +95 -95
- package/src/onboarding/templates.js +794 -794
- package/src/onboarding/toml-writer.js +38 -38
- package/src/onboarding/tui.js +250 -250
- package/src/onboarding/uninstall.js +153 -153
- package/src/onboarding/wizard.js +516 -499
- package/src/providers/anthropic.js +168 -168
- package/src/providers/base.js +247 -247
- package/src/providers/circuit-breaker.js +136 -136
- package/src/providers/ollama.js +163 -163
- package/src/providers/openai-codex.js +149 -149
- package/src/providers/openrouter.js +136 -136
- package/src/providers/registry.js +36 -36
- package/src/providers/router.js +16 -16
- package/src/runtime/bootstrap-cache.js +47 -47
- package/src/runtime/capabilities-prompt.js +25 -25
- package/src/runtime/completion-ping.js +99 -99
- package/src/runtime/config-validator.js +121 -121
- package/src/runtime/context-ledger.js +360 -360
- package/src/runtime/cutover-readiness.js +42 -42
- package/src/runtime/daemon.js +729 -729
- package/src/runtime/delivery-ack.js +195 -195
- package/src/runtime/delivery-adapters/local-file.js +41 -41
- package/src/runtime/delivery-adapters/openclaw-cli.js +94 -94
- package/src/runtime/delivery-adapters/openclaw-peer.js +98 -98
- package/src/runtime/delivery-adapters/shadow.js +13 -13
- package/src/runtime/delivery-adapters/standalone-http.js +98 -98
- package/src/runtime/delivery-adapters/telegram.js +104 -104
- package/src/runtime/delivery-adapters/tui.js +128 -128
- package/src/runtime/delivery-manager.js +807 -807
- package/src/runtime/delivery-store.js +168 -168
- package/src/runtime/dependency-health.js +118 -118
- package/src/runtime/envelope.js +114 -114
- package/src/runtime/evaluation.js +1089 -1089
- package/src/runtime/exec-approvals.js +216 -216
- package/src/runtime/executor.js +500 -500
- package/src/runtime/failure-ping.js +67 -67
- package/src/runtime/flows.js +83 -83
- package/src/runtime/guards.js +45 -45
- package/src/runtime/handoff.js +51 -51
- package/src/runtime/identity-cache.js +28 -28
- package/src/runtime/improvement-engine.js +109 -109
- package/src/runtime/improvement-harness.js +581 -581
- package/src/runtime/input-sanitiser.js +72 -72
- package/src/runtime/interaction-contract.js +347 -347
- package/src/runtime/lane-readiness.js +226 -226
- package/src/runtime/migration.js +323 -323
- package/src/runtime/model-resolution.js +78 -78
- package/src/runtime/network.js +64 -64
- package/src/runtime/notification-store.js +97 -97
- package/src/runtime/notifier.js +256 -256
- package/src/runtime/orchestrator.js +53 -53
- package/src/runtime/orphan-reaper.js +41 -41
- package/src/runtime/output-contract-schema.js +139 -139
- package/src/runtime/output-contract-validator.js +439 -439
- package/src/runtime/peer-readiness.js +69 -69
- package/src/runtime/peer-registry.js +133 -133
- package/src/runtime/pilot-status.js +108 -108
- package/src/runtime/prompt-builder.js +261 -261
- package/src/runtime/provider-attempt.js +582 -582
- package/src/runtime/report-fallback.js +71 -71
- package/src/runtime/result-normalizer.js +183 -183
- package/src/runtime/retention.js +74 -74
- package/src/runtime/review.js +244 -244
- package/src/runtime/route-job.js +15 -15
- package/src/runtime/run-store.js +38 -38
- package/src/runtime/schedule.js +88 -88
- package/src/runtime/scheduler-state.js +434 -434
- package/src/runtime/scheduler.js +656 -656
- package/src/runtime/session-compactor.js +182 -182
- package/src/runtime/session-search.js +155 -155
- package/src/runtime/slack-inbound.js +249 -249
- package/src/runtime/ssrf.js +102 -102
- package/src/runtime/status-aggregator.js +330 -330
- package/src/runtime/task-contract.js +140 -140
- package/src/runtime/task-packet.js +107 -107
- package/src/runtime/task-router.js +140 -140
- package/src/runtime/telegram-inbound.js +1565 -1565
- package/src/runtime/token-counter.js +134 -134
- package/src/runtime/token-estimator.js +59 -59
- package/src/runtime/tool-loop.js +200 -200
- package/src/runtime/transport-server.js +311 -311
- package/src/runtime/tui-server.js +411 -411
- package/src/runtime/ulid.js +44 -44
- package/src/security/ssrf-check.js +197 -197
- package/src/setup.js +369 -369
- package/src/shadow/bridge.js +303 -303
- package/src/skills/loader.js +84 -84
- package/src/tools/catalog.json +49 -49
- package/src/tools/cli-delegate.js +44 -44
- package/src/tools/mcp-client.js +106 -106
- package/src/tools/micro/cancel-task.js +6 -6
- package/src/tools/micro/complete-task.js +6 -6
- package/src/tools/micro/fail-task.js +6 -6
- package/src/tools/micro/http-fetch.js +74 -74
- package/src/tools/micro/index.js +36 -36
- package/src/tools/micro/lcm-recall.js +60 -60
- package/src/tools/micro/list-dir.js +17 -17
- package/src/tools/micro/list-skills.js +46 -46
- package/src/tools/micro/load-skill.js +38 -38
- package/src/tools/micro/memory-search.js +45 -45
- package/src/tools/micro/read-file.js +11 -11
- package/src/tools/micro/session-search.js +54 -54
- package/src/tools/micro/shell-exec.js +43 -43
- package/src/tools/micro/trigger-job.js +79 -79
- package/src/tools/micro/web-search.js +58 -58
- package/src/tools/micro/workspace-paths.js +39 -39
- package/src/tools/micro/write-file.js +14 -14
- package/src/tools/micro/write-memory.js +41 -41
- package/src/tools/registry.js +348 -348
- package/src/tools/tool-result-contract.js +36 -36
- package/src/tui/chat.js +835 -835
- package/src/tui/renderer.js +175 -175
- package/src/tui/socket-client.js +217 -217
- package/src/utils/canonical-json.js +29 -29
- package/src/utils/compaction.js +30 -30
- package/src/utils/env-loader.js +5 -5
- package/src/utils/errors.js +80 -80
- package/src/utils/fs.js +101 -101
- package/src/utils/ids.js +5 -5
- package/src/utils/model-context-limits.js +30 -30
- package/src/utils/token-budget.js +74 -74
- package/src/utils/usage-cost.js +25 -25
- package/src/utils/usage-metrics.js +14 -14
|
@@ -1,171 +1,171 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Active Recall — turn-aware memory scoring and tiered injection.
|
|
3
|
-
*
|
|
4
|
-
* Spec: docs/superpowers/specs/2026-03-16-interaction-layer-design.md § Pillar 1
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
// ── Scoring ─────────────────────────────────────────────────────
|
|
8
|
-
|
|
9
|
-
export function computeRecency(lastAccessedOrCreated, now = new Date()) {
|
|
10
|
-
const ts = new Date(lastAccessedOrCreated);
|
|
11
|
-
const daysSince = (now.getTime() - ts.getTime()) / (24 * 60 * 60 * 1000);
|
|
12
|
-
return 1 / (1 + daysSince * 0.1);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export function scoreCandidate({ similarity, topicRelevance, recency, confidence }) {
|
|
16
|
-
const hasTopicRelevance = topicRelevance > 0;
|
|
17
|
-
if (hasTopicRelevance) {
|
|
18
|
-
return (similarity * 0.5) + (topicRelevance * 0.3) + (recency * confidence * 0.2);
|
|
19
|
-
}
|
|
20
|
-
return (similarity * 0.7) + (recency * confidence * 0.3);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// ── Tier classification ─────────────────────────────────────────
|
|
24
|
-
|
|
25
|
-
const FACT_THRESHOLD = 0.85;
|
|
26
|
-
export const ASIDE_THRESHOLD = 0.70;
|
|
27
|
-
|
|
28
|
-
export function classifyTier(score) {
|
|
29
|
-
if (score > FACT_THRESHOLD) return "fact";
|
|
30
|
-
if (score >= ASIDE_THRESHOLD) return "aside";
|
|
31
|
-
return "silent";
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// ── Injection builder ───────────────────────────────────────────
|
|
35
|
-
|
|
36
|
-
const CHARS_PER_TOKEN = 4;
|
|
37
|
-
|
|
38
|
-
export function buildInjection(candidates, { tokenBudget = 2000 } = {}) {
|
|
39
|
-
let remainingChars = tokenBudget * CHARS_PER_TOKEN;
|
|
40
|
-
const facts = [];
|
|
41
|
-
const asides = [];
|
|
42
|
-
const silent = [];
|
|
43
|
-
|
|
44
|
-
const sorted = [...candidates].sort((a, b) => b.score - a.score);
|
|
45
|
-
|
|
46
|
-
for (const c of sorted) {
|
|
47
|
-
if (c.tier === "silent") { silent.push(c); continue; }
|
|
48
|
-
if (c.tier === "fact") {
|
|
49
|
-
const len = c.content.length;
|
|
50
|
-
if (len <= remainingChars) {
|
|
51
|
-
facts.push(c);
|
|
52
|
-
remainingChars -= len;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
for (const c of sorted) {
|
|
58
|
-
if (c.tier === "aside") {
|
|
59
|
-
const len = c.content.length;
|
|
60
|
-
if (len <= remainingChars) {
|
|
61
|
-
asides.push(c);
|
|
62
|
-
remainingChars -= len;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
return { facts, asides, silent };
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// ── Orchestrator ────────────────────────────────────────────────
|
|
71
|
-
|
|
72
|
-
export async function activeRecall({ inboundMessage, memoryStore, tokenBudget = 2000, topicRelevanceFn = null, contextWindow = 0 }) {
|
|
73
|
-
const budget = contextWindow > 0 ? Math.min(tokenBudget, Math.floor(contextWindow * 0.08)) : tokenBudget;
|
|
74
|
-
|
|
75
|
-
// Stage 1: Candidate retrieval (top 20)
|
|
76
|
-
// queryRetrievalCandidates(query, options) — positional args
|
|
77
|
-
const raw = memoryStore.queryRetrievalCandidates
|
|
78
|
-
? memoryStore.queryRetrievalCandidates(inboundMessage, { limit: 20 })
|
|
79
|
-
: [];
|
|
80
|
-
|
|
81
|
-
// Unwrap nested shape: real store returns { item: { entry_id, content, ... }, embeddingSimilarity }
|
|
82
|
-
const candidates = raw.map((r) => ({
|
|
83
|
-
entryId: r.item?.entry_id || r.entryId,
|
|
84
|
-
content: r.item?.content || r.content,
|
|
85
|
-
summary: r.item?.summary || r.summary,
|
|
86
|
-
salience: r.item?.salience ?? r.salience ?? 0.5,
|
|
87
|
-
embeddingSimilarity: r.embeddingSimilarity ?? 0,
|
|
88
|
-
last_accessed: r.item?.last_accessed || r.last_accessed || null,
|
|
89
|
-
created_at: r.item?.timestamp || r.created_at,
|
|
90
|
-
}));
|
|
91
|
-
|
|
92
|
-
// Stage 2: Turn-aware scoring
|
|
93
|
-
const now = new Date();
|
|
94
|
-
const scored = candidates.map((c) => {
|
|
95
|
-
const similarity = c.embeddingSimilarity || c.salience || 0.5;
|
|
96
|
-
const topicRelevance = topicRelevanceFn ? topicRelevanceFn(inboundMessage, c) : 0;
|
|
97
|
-
const recency = computeRecency(c.last_accessed || c.created_at || now.toISOString(), now);
|
|
98
|
-
const confidence = 0.75; // Default — declared memory (MEMORY.md) injected separately at 1.0
|
|
99
|
-
const score = scoreCandidate({ similarity, topicRelevance, recency, confidence });
|
|
100
|
-
const tier = classifyTier(score);
|
|
101
|
-
return { ...c, score, tier };
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
// Stage 3: Tiered injection
|
|
105
|
-
const injection = buildInjection(scored, { tokenBudget: budget });
|
|
106
|
-
|
|
107
|
-
// Stage 4: Access tracking (score >= 0.70)
|
|
108
|
-
const accessedIds = scored.filter((c) => c.score >= ASIDE_THRESHOLD).map((c) => c.entryId);
|
|
109
|
-
if (accessedIds.length && memoryStore.updateAccessTime) {
|
|
110
|
-
memoryStore.updateAccessTime(accessedIds);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
return injection;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// ── Write discipline ────────────────────────────────────────────
|
|
117
|
-
|
|
118
|
-
const DEDUP_THRESHOLD = 0.95;
|
|
119
|
-
const CONTRADICTION_LOW = 0.85;
|
|
120
|
-
|
|
121
|
-
export class WriteBuffer {
|
|
122
|
-
constructor({ commitFn, similarityFn, updateFn, existingEntries = [] }) {
|
|
123
|
-
this._pending = [];
|
|
124
|
-
this._commitFn = commitFn;
|
|
125
|
-
this._similarityFn = similarityFn;
|
|
126
|
-
this._updateFn = updateFn || (() => {});
|
|
127
|
-
this._existingEntries = existingEntries;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
add(entry) {
|
|
131
|
-
this._pending.push(entry);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
flush() {
|
|
135
|
-
let committed = 0;
|
|
136
|
-
let deduped = 0;
|
|
137
|
-
const contradictions = [];
|
|
138
|
-
|
|
139
|
-
for (const entry of this._pending) {
|
|
140
|
-
let dominated = false;
|
|
141
|
-
for (const existing of this._existingEntries) {
|
|
142
|
-
const sim = this._similarityFn(entry.content, existing.content);
|
|
143
|
-
if (sim > DEDUP_THRESHOLD) {
|
|
144
|
-
this._updateFn(existing.entryId, entry.content);
|
|
145
|
-
deduped++;
|
|
146
|
-
dominated = true;
|
|
147
|
-
break;
|
|
148
|
-
}
|
|
149
|
-
if (sim >= CONTRADICTION_LOW && sim <= DEDUP_THRESHOLD) {
|
|
150
|
-
contradictions.push({
|
|
151
|
-
existingId: existing.entryId,
|
|
152
|
-
existingContent: existing.content,
|
|
153
|
-
newContent: entry.content,
|
|
154
|
-
similarity: sim,
|
|
155
|
-
});
|
|
156
|
-
dominated = true;
|
|
157
|
-
break;
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
if (!dominated) {
|
|
161
|
-
this._commitFn(entry);
|
|
162
|
-
committed++;
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
this._pending = [];
|
|
167
|
-
return { committed, deduped, contradictions };
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
get pending() { return this._pending; }
|
|
171
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Active Recall — turn-aware memory scoring and tiered injection.
|
|
3
|
+
*
|
|
4
|
+
* Spec: docs/superpowers/specs/2026-03-16-interaction-layer-design.md § Pillar 1
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// ── Scoring ─────────────────────────────────────────────────────
|
|
8
|
+
|
|
9
|
+
export function computeRecency(lastAccessedOrCreated, now = new Date()) {
|
|
10
|
+
const ts = new Date(lastAccessedOrCreated);
|
|
11
|
+
const daysSince = (now.getTime() - ts.getTime()) / (24 * 60 * 60 * 1000);
|
|
12
|
+
return 1 / (1 + daysSince * 0.1);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function scoreCandidate({ similarity, topicRelevance, recency, confidence }) {
|
|
16
|
+
const hasTopicRelevance = topicRelevance > 0;
|
|
17
|
+
if (hasTopicRelevance) {
|
|
18
|
+
return (similarity * 0.5) + (topicRelevance * 0.3) + (recency * confidence * 0.2);
|
|
19
|
+
}
|
|
20
|
+
return (similarity * 0.7) + (recency * confidence * 0.3);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// ── Tier classification ─────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
const FACT_THRESHOLD = 0.85;
|
|
26
|
+
export const ASIDE_THRESHOLD = 0.70;
|
|
27
|
+
|
|
28
|
+
export function classifyTier(score) {
|
|
29
|
+
if (score > FACT_THRESHOLD) return "fact";
|
|
30
|
+
if (score >= ASIDE_THRESHOLD) return "aside";
|
|
31
|
+
return "silent";
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// ── Injection builder ───────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
const CHARS_PER_TOKEN = 4;
|
|
37
|
+
|
|
38
|
+
export function buildInjection(candidates, { tokenBudget = 2000 } = {}) {
|
|
39
|
+
let remainingChars = tokenBudget * CHARS_PER_TOKEN;
|
|
40
|
+
const facts = [];
|
|
41
|
+
const asides = [];
|
|
42
|
+
const silent = [];
|
|
43
|
+
|
|
44
|
+
const sorted = [...candidates].sort((a, b) => b.score - a.score);
|
|
45
|
+
|
|
46
|
+
for (const c of sorted) {
|
|
47
|
+
if (c.tier === "silent") { silent.push(c); continue; }
|
|
48
|
+
if (c.tier === "fact") {
|
|
49
|
+
const len = c.content.length;
|
|
50
|
+
if (len <= remainingChars) {
|
|
51
|
+
facts.push(c);
|
|
52
|
+
remainingChars -= len;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
for (const c of sorted) {
|
|
58
|
+
if (c.tier === "aside") {
|
|
59
|
+
const len = c.content.length;
|
|
60
|
+
if (len <= remainingChars) {
|
|
61
|
+
asides.push(c);
|
|
62
|
+
remainingChars -= len;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return { facts, asides, silent };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ── Orchestrator ────────────────────────────────────────────────
|
|
71
|
+
|
|
72
|
+
export async function activeRecall({ inboundMessage, memoryStore, tokenBudget = 2000, topicRelevanceFn = null, contextWindow = 0 }) {
|
|
73
|
+
const budget = contextWindow > 0 ? Math.min(tokenBudget, Math.floor(contextWindow * 0.08)) : tokenBudget;
|
|
74
|
+
|
|
75
|
+
// Stage 1: Candidate retrieval (top 20)
|
|
76
|
+
// queryRetrievalCandidates(query, options) — positional args
|
|
77
|
+
const raw = memoryStore.queryRetrievalCandidates
|
|
78
|
+
? memoryStore.queryRetrievalCandidates(inboundMessage, { limit: 20 })
|
|
79
|
+
: [];
|
|
80
|
+
|
|
81
|
+
// Unwrap nested shape: real store returns { item: { entry_id, content, ... }, embeddingSimilarity }
|
|
82
|
+
const candidates = raw.map((r) => ({
|
|
83
|
+
entryId: r.item?.entry_id || r.entryId,
|
|
84
|
+
content: r.item?.content || r.content,
|
|
85
|
+
summary: r.item?.summary || r.summary,
|
|
86
|
+
salience: r.item?.salience ?? r.salience ?? 0.5,
|
|
87
|
+
embeddingSimilarity: r.embeddingSimilarity ?? 0,
|
|
88
|
+
last_accessed: r.item?.last_accessed || r.last_accessed || null,
|
|
89
|
+
created_at: r.item?.timestamp || r.created_at,
|
|
90
|
+
}));
|
|
91
|
+
|
|
92
|
+
// Stage 2: Turn-aware scoring
|
|
93
|
+
const now = new Date();
|
|
94
|
+
const scored = candidates.map((c) => {
|
|
95
|
+
const similarity = c.embeddingSimilarity || c.salience || 0.5;
|
|
96
|
+
const topicRelevance = topicRelevanceFn ? topicRelevanceFn(inboundMessage, c) : 0;
|
|
97
|
+
const recency = computeRecency(c.last_accessed || c.created_at || now.toISOString(), now);
|
|
98
|
+
const confidence = 0.75; // Default — declared memory (MEMORY.md) injected separately at 1.0
|
|
99
|
+
const score = scoreCandidate({ similarity, topicRelevance, recency, confidence });
|
|
100
|
+
const tier = classifyTier(score);
|
|
101
|
+
return { ...c, score, tier };
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// Stage 3: Tiered injection
|
|
105
|
+
const injection = buildInjection(scored, { tokenBudget: budget });
|
|
106
|
+
|
|
107
|
+
// Stage 4: Access tracking (score >= 0.70)
|
|
108
|
+
const accessedIds = scored.filter((c) => c.score >= ASIDE_THRESHOLD).map((c) => c.entryId);
|
|
109
|
+
if (accessedIds.length && memoryStore.updateAccessTime) {
|
|
110
|
+
memoryStore.updateAccessTime(accessedIds);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return injection;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ── Write discipline ────────────────────────────────────────────
|
|
117
|
+
|
|
118
|
+
const DEDUP_THRESHOLD = 0.95;
|
|
119
|
+
const CONTRADICTION_LOW = 0.85;
|
|
120
|
+
|
|
121
|
+
export class WriteBuffer {
|
|
122
|
+
constructor({ commitFn, similarityFn, updateFn, existingEntries = [] }) {
|
|
123
|
+
this._pending = [];
|
|
124
|
+
this._commitFn = commitFn;
|
|
125
|
+
this._similarityFn = similarityFn;
|
|
126
|
+
this._updateFn = updateFn || (() => {});
|
|
127
|
+
this._existingEntries = existingEntries;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
add(entry) {
|
|
131
|
+
this._pending.push(entry);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
flush() {
|
|
135
|
+
let committed = 0;
|
|
136
|
+
let deduped = 0;
|
|
137
|
+
const contradictions = [];
|
|
138
|
+
|
|
139
|
+
for (const entry of this._pending) {
|
|
140
|
+
let dominated = false;
|
|
141
|
+
for (const existing of this._existingEntries) {
|
|
142
|
+
const sim = this._similarityFn(entry.content, existing.content);
|
|
143
|
+
if (sim > DEDUP_THRESHOLD) {
|
|
144
|
+
this._updateFn(existing.entryId, entry.content);
|
|
145
|
+
deduped++;
|
|
146
|
+
dominated = true;
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
if (sim >= CONTRADICTION_LOW && sim <= DEDUP_THRESHOLD) {
|
|
150
|
+
contradictions.push({
|
|
151
|
+
existingId: existing.entryId,
|
|
152
|
+
existingContent: existing.content,
|
|
153
|
+
newContent: entry.content,
|
|
154
|
+
similarity: sim,
|
|
155
|
+
});
|
|
156
|
+
dominated = true;
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (!dominated) {
|
|
161
|
+
this._commitFn(entry);
|
|
162
|
+
committed++;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
this._pending = [];
|
|
167
|
+
return { committed, deduped, contradictions };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
get pending() { return this._pending; }
|
|
171
|
+
}
|
|
@@ -1,97 +1,97 @@
|
|
|
1
|
-
import { FileMemoryBackend } from "./backends/file-backend.js";
|
|
2
|
-
import { QmdMemoryBackend } from "./backends/qmd-backend.js";
|
|
3
|
-
|
|
4
|
-
export class MemoryBackendManager {
|
|
5
|
-
constructor({ memoryStore, embeddingIndex, liveRoot }) {
|
|
6
|
-
this.fileBackend = new FileMemoryBackend({ memoryStore, embeddingIndex });
|
|
7
|
-
this.qmdBackend = new QmdMemoryBackend({ liveRoot });
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
defaultBackend() {
|
|
11
|
-
return this.fileBackend;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
async queryCombined(agentId, query, options = {}) {
|
|
15
|
-
const limit = options.limit ?? 8;
|
|
16
|
-
const workspaceRoot = options.workspaceRoot || null;
|
|
17
|
-
const backendOrder = options.backendOrder || ["file"];
|
|
18
|
-
const qmdSupplementLimit = options.qmdSupplementLimit ?? 2;
|
|
19
|
-
const fileQuery = options.fileQuery || query;
|
|
20
|
-
const qmdQuery = options.qmdQuery || query;
|
|
21
|
-
|
|
22
|
-
const fileResult = await this.fileBackend.query(agentId, fileQuery, {
|
|
23
|
-
limit,
|
|
24
|
-
now: options.now,
|
|
25
|
-
retrievalBlend: options.retrievalBlend
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
let merged = [...fileResult.items];
|
|
29
|
-
let qmdResult = {
|
|
30
|
-
backend: "qmd",
|
|
31
|
-
available: false,
|
|
32
|
-
items: []
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
if (backendOrder.includes("qmd")) {
|
|
36
|
-
qmdResult = await this.qmdBackend.query(agentId, qmdQuery, {
|
|
37
|
-
workspaceRoot,
|
|
38
|
-
limit: qmdSupplementLimit
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
const qmdItems = qmdResult.items.map((item, index) => ({
|
|
42
|
-
type: "external",
|
|
43
|
-
category: "qmd_match",
|
|
44
|
-
title: item.title,
|
|
45
|
-
content: item.snippet,
|
|
46
|
-
summary: item.filepath,
|
|
47
|
-
reason: "Matched through local QMD FTS retrieval.",
|
|
48
|
-
score: Number((0.35 - index * 0.03).toFixed(4)),
|
|
49
|
-
sourceBackend: "qmd",
|
|
50
|
-
candidateSource: "qmd",
|
|
51
|
-
lexicalScore: 0,
|
|
52
|
-
embeddingSimilarity: 0,
|
|
53
|
-
embeddingFreshness: "not_applicable",
|
|
54
|
-
retrievalSources: ["qmd"]
|
|
55
|
-
}));
|
|
56
|
-
|
|
57
|
-
merged = [...merged, ...qmdItems];
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return {
|
|
61
|
-
items: merged.slice(0, limit),
|
|
62
|
-
backends: {
|
|
63
|
-
file: {
|
|
64
|
-
available: fileResult.available,
|
|
65
|
-
totalCandidates: fileResult.totalCandidates ?? fileResult.items.length,
|
|
66
|
-
query: fileQuery
|
|
67
|
-
},
|
|
68
|
-
qmd: {
|
|
69
|
-
available: qmdResult.available,
|
|
70
|
-
totalCandidates: qmdResult.items.length,
|
|
71
|
-
stats: qmdResult.stats || null,
|
|
72
|
-
query: qmdQuery
|
|
73
|
-
}
|
|
74
|
-
},
|
|
75
|
-
retrieval: fileResult.retrieval || null
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
async inspect(agentId, workspaceRoot = null) {
|
|
80
|
-
const [file, qmd] = await Promise.all([
|
|
81
|
-
this.fileBackend.inspect(agentId),
|
|
82
|
-
this.qmdBackend.inspect(agentId, workspaceRoot)
|
|
83
|
-
]);
|
|
84
|
-
|
|
85
|
-
return {
|
|
86
|
-
defaultBackend: file.kind,
|
|
87
|
-
backends: [file, qmd]
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
async queryQmd(agentId, query, workspaceRoot = null, limit = 5) {
|
|
92
|
-
return this.qmdBackend.query(agentId, query, {
|
|
93
|
-
workspaceRoot,
|
|
94
|
-
limit
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
}
|
|
1
|
+
import { FileMemoryBackend } from "./backends/file-backend.js";
|
|
2
|
+
import { QmdMemoryBackend } from "./backends/qmd-backend.js";
|
|
3
|
+
|
|
4
|
+
export class MemoryBackendManager {
|
|
5
|
+
constructor({ memoryStore, embeddingIndex, liveRoot }) {
|
|
6
|
+
this.fileBackend = new FileMemoryBackend({ memoryStore, embeddingIndex });
|
|
7
|
+
this.qmdBackend = new QmdMemoryBackend({ liveRoot });
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
defaultBackend() {
|
|
11
|
+
return this.fileBackend;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async queryCombined(agentId, query, options = {}) {
|
|
15
|
+
const limit = options.limit ?? 8;
|
|
16
|
+
const workspaceRoot = options.workspaceRoot || null;
|
|
17
|
+
const backendOrder = options.backendOrder || ["file"];
|
|
18
|
+
const qmdSupplementLimit = options.qmdSupplementLimit ?? 2;
|
|
19
|
+
const fileQuery = options.fileQuery || query;
|
|
20
|
+
const qmdQuery = options.qmdQuery || query;
|
|
21
|
+
|
|
22
|
+
const fileResult = await this.fileBackend.query(agentId, fileQuery, {
|
|
23
|
+
limit,
|
|
24
|
+
now: options.now,
|
|
25
|
+
retrievalBlend: options.retrievalBlend
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
let merged = [...fileResult.items];
|
|
29
|
+
let qmdResult = {
|
|
30
|
+
backend: "qmd",
|
|
31
|
+
available: false,
|
|
32
|
+
items: []
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
if (backendOrder.includes("qmd")) {
|
|
36
|
+
qmdResult = await this.qmdBackend.query(agentId, qmdQuery, {
|
|
37
|
+
workspaceRoot,
|
|
38
|
+
limit: qmdSupplementLimit
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const qmdItems = qmdResult.items.map((item, index) => ({
|
|
42
|
+
type: "external",
|
|
43
|
+
category: "qmd_match",
|
|
44
|
+
title: item.title,
|
|
45
|
+
content: item.snippet,
|
|
46
|
+
summary: item.filepath,
|
|
47
|
+
reason: "Matched through local QMD FTS retrieval.",
|
|
48
|
+
score: Number((0.35 - index * 0.03).toFixed(4)),
|
|
49
|
+
sourceBackend: "qmd",
|
|
50
|
+
candidateSource: "qmd",
|
|
51
|
+
lexicalScore: 0,
|
|
52
|
+
embeddingSimilarity: 0,
|
|
53
|
+
embeddingFreshness: "not_applicable",
|
|
54
|
+
retrievalSources: ["qmd"]
|
|
55
|
+
}));
|
|
56
|
+
|
|
57
|
+
merged = [...merged, ...qmdItems];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
items: merged.slice(0, limit),
|
|
62
|
+
backends: {
|
|
63
|
+
file: {
|
|
64
|
+
available: fileResult.available,
|
|
65
|
+
totalCandidates: fileResult.totalCandidates ?? fileResult.items.length,
|
|
66
|
+
query: fileQuery
|
|
67
|
+
},
|
|
68
|
+
qmd: {
|
|
69
|
+
available: qmdResult.available,
|
|
70
|
+
totalCandidates: qmdResult.items.length,
|
|
71
|
+
stats: qmdResult.stats || null,
|
|
72
|
+
query: qmdQuery
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
retrieval: fileResult.retrieval || null
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async inspect(agentId, workspaceRoot = null) {
|
|
80
|
+
const [file, qmd] = await Promise.all([
|
|
81
|
+
this.fileBackend.inspect(agentId),
|
|
82
|
+
this.qmdBackend.inspect(agentId, workspaceRoot)
|
|
83
|
+
]);
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
defaultBackend: file.kind,
|
|
87
|
+
backends: [file, qmd]
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async queryQmd(agentId, query, workspaceRoot = null, limit = 5) {
|
|
92
|
+
return this.qmdBackend.query(agentId, query, {
|
|
93
|
+
workspaceRoot,
|
|
94
|
+
limit
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -1,38 +1,38 @@
|
|
|
1
|
-
export class FileMemoryBackend {
|
|
2
|
-
constructor({ memoryStore, embeddingIndex = null } = {}) {
|
|
3
|
-
this.kind = "file";
|
|
4
|
-
this.memoryStore = memoryStore;
|
|
5
|
-
this.embeddingIndex = embeddingIndex;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
async query(agentId, query, options = {}) {
|
|
9
|
-
const result = await this.memoryStore.query(agentId, query, {
|
|
10
|
-
...options,
|
|
11
|
-
embeddingIndex: this.embeddingIndex
|
|
12
|
-
});
|
|
13
|
-
return {
|
|
14
|
-
backend: this.kind,
|
|
15
|
-
available: true,
|
|
16
|
-
items: result.items.map((item) => ({
|
|
17
|
-
...item,
|
|
18
|
-
sourceBackend: this.kind
|
|
19
|
-
})),
|
|
20
|
-
totalCandidates: result.totalCandidates,
|
|
21
|
-
retrieval: result.retrieval || null
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
async listAll(agentId) {
|
|
26
|
-
return this.memoryStore.listAll(agentId);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
async inspect(agentId) {
|
|
30
|
-
const items = await this.memoryStore.listAll(agentId);
|
|
31
|
-
return {
|
|
32
|
-
kind: this.kind,
|
|
33
|
-
available: true,
|
|
34
|
-
itemCount: items.length,
|
|
35
|
-
embeddingIndexConfigured: Boolean(this.embeddingIndex)
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
}
|
|
1
|
+
export class FileMemoryBackend {
|
|
2
|
+
constructor({ memoryStore, embeddingIndex = null } = {}) {
|
|
3
|
+
this.kind = "file";
|
|
4
|
+
this.memoryStore = memoryStore;
|
|
5
|
+
this.embeddingIndex = embeddingIndex;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
async query(agentId, query, options = {}) {
|
|
9
|
+
const result = await this.memoryStore.query(agentId, query, {
|
|
10
|
+
...options,
|
|
11
|
+
embeddingIndex: this.embeddingIndex
|
|
12
|
+
});
|
|
13
|
+
return {
|
|
14
|
+
backend: this.kind,
|
|
15
|
+
available: true,
|
|
16
|
+
items: result.items.map((item) => ({
|
|
17
|
+
...item,
|
|
18
|
+
sourceBackend: this.kind
|
|
19
|
+
})),
|
|
20
|
+
totalCandidates: result.totalCandidates,
|
|
21
|
+
retrieval: result.retrieval || null
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async listAll(agentId) {
|
|
26
|
+
return this.memoryStore.listAll(agentId);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async inspect(agentId) {
|
|
30
|
+
const items = await this.memoryStore.listAll(agentId);
|
|
31
|
+
return {
|
|
32
|
+
kind: this.kind,
|
|
33
|
+
available: true,
|
|
34
|
+
itemCount: items.length,
|
|
35
|
+
embeddingIndexConfigured: Boolean(this.embeddingIndex)
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
}
|