@prometheus-ai/memory 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +107 -0
- package/dist/types/cli.d.ts +35 -0
- package/dist/types/config.d.ts +77 -0
- package/dist/types/core/aaak.d.ts +55 -0
- package/dist/types/core/annotations.d.ts +75 -0
- package/dist/types/core/banks.d.ts +33 -0
- package/dist/types/core/beam/consolidate.d.ts +32 -0
- package/dist/types/core/beam/helpers.d.ts +76 -0
- package/dist/types/core/beam/index.d.ts +59 -0
- package/dist/types/core/beam/recall.d.ts +32 -0
- package/dist/types/core/beam/schema.d.ts +2 -0
- package/dist/types/core/beam/store.d.ts +35 -0
- package/dist/types/core/beam/types.d.ts +233 -0
- package/dist/types/core/binary-vectors.d.ts +54 -0
- package/dist/types/core/chat-normalize.d.ts +13 -0
- package/dist/types/core/content-sanitizer.d.ts +18 -0
- package/dist/types/core/cost-log.d.ts +13 -0
- package/dist/types/core/embeddings.d.ts +44 -0
- package/dist/types/core/entities.d.ts +7 -0
- package/dist/types/core/episodic-graph.d.ts +89 -0
- package/dist/types/core/extraction/client.d.ts +31 -0
- package/dist/types/core/extraction/diagnostics.d.ts +51 -0
- package/dist/types/core/extraction/prompts.d.ts +2 -0
- package/dist/types/core/extraction.d.ts +6 -0
- package/dist/types/core/index.d.ts +4 -0
- package/dist/types/core/llm-backends.d.ts +21 -0
- package/dist/types/core/local-llm.d.ts +15 -0
- package/dist/types/core/memory.d.ts +160 -0
- package/dist/types/core/migrations/e6-triplestore-split.d.ts +17 -0
- package/dist/types/core/migrations/index.d.ts +1 -0
- package/dist/types/core/mmr.d.ts +8 -0
- package/dist/types/core/orchestrator.d.ts +20 -0
- package/dist/types/core/patterns.d.ts +61 -0
- package/dist/types/core/plugins.d.ts +109 -0
- package/dist/types/core/polyphonic-recall.d.ts +66 -0
- package/dist/types/core/query-cache.d.ts +46 -0
- package/dist/types/core/query-intent.d.ts +20 -0
- package/dist/types/core/recall-diagnostics.d.ts +48 -0
- package/dist/types/core/runtime-options.d.ts +68 -0
- package/dist/types/core/shmr.d.ts +56 -0
- package/dist/types/core/streaming.d.ts +136 -0
- package/dist/types/core/synonyms.d.ts +46 -0
- package/dist/types/core/temporal-parser.d.ts +16 -0
- package/dist/types/core/token-counter.d.ts +8 -0
- package/dist/types/core/triples.d.ts +63 -0
- package/dist/types/core/typed-memory.d.ts +39 -0
- package/dist/types/core/vector-math.d.ts +1 -0
- package/dist/types/core/veracity-consolidation.d.ts +60 -0
- package/dist/types/core/weibull.d.ts +96 -0
- package/dist/types/db.d.ts +16 -0
- package/dist/types/diagnose.d.ts +24 -0
- package/dist/types/dr/index.d.ts +1 -0
- package/dist/types/dr/recovery.d.ts +68 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/mcp-server.d.ts +40 -0
- package/dist/types/mcp-tools.d.ts +484 -0
- package/dist/types/migrations/e6-triplestore-split.d.ts +1 -0
- package/dist/types/migrations/index.d.ts +1 -0
- package/dist/types/types.d.ts +145 -0
- package/dist/types/util/datetime.d.ts +8 -0
- package/dist/types/util/env.d.ts +10 -0
- package/dist/types/util/ids.d.ts +3 -0
- package/dist/types/util/lru.d.ts +12 -0
- package/dist/types/util/regex.d.ts +10 -0
- package/package.json +85 -0
- package/src/cli.ts +398 -0
- package/src/config.ts +326 -0
- package/src/core/aaak.ts +142 -0
- package/src/core/annotations.ts +457 -0
- package/src/core/banks.ts +133 -0
- package/src/core/beam/consolidate.ts +965 -0
- package/src/core/beam/helpers.ts +977 -0
- package/src/core/beam/index.ts +353 -0
- package/src/core/beam/recall.ts +1100 -0
- package/src/core/beam/schema.ts +423 -0
- package/src/core/beam/store.ts +829 -0
- package/src/core/beam/types.ts +268 -0
- package/src/core/binary-vectors.ts +317 -0
- package/src/core/chat-normalize.ts +160 -0
- package/src/core/content-sanitizer.ts +136 -0
- package/src/core/cost-log.ts +103 -0
- package/src/core/embeddings.ts +423 -0
- package/src/core/entities.ts +259 -0
- package/src/core/episodic-graph.ts +708 -0
- package/src/core/extraction/client.ts +162 -0
- package/src/core/extraction/diagnostics.ts +193 -0
- package/src/core/extraction/prompts.ts +31 -0
- package/src/core/extraction.ts +335 -0
- package/src/core/index.ts +30 -0
- package/src/core/llm-backends.ts +51 -0
- package/src/core/local-llm.ts +436 -0
- package/src/core/memory.ts +630 -0
- package/src/core/migrations/e6-triplestore-split.ts +211 -0
- package/src/core/migrations/index.ts +1 -0
- package/src/core/mmr.ts +71 -0
- package/src/core/orchestrator.ts +62 -0
- package/src/core/patterns.ts +484 -0
- package/src/core/plugins.ts +375 -0
- package/src/core/polyphonic-recall.ts +563 -0
- package/src/core/query-cache.ts +354 -0
- package/src/core/query-intent.ts +139 -0
- package/src/core/recall-diagnostics.ts +157 -0
- package/src/core/runtime-options.ts +119 -0
- package/src/core/shmr.ts +460 -0
- package/src/core/streaming.ts +419 -0
- package/src/core/synonyms.ts +197 -0
- package/src/core/temporal-parser.ts +363 -0
- package/src/core/token-counter.ts +30 -0
- package/src/core/triples.ts +454 -0
- package/src/core/typed-memory.ts +407 -0
- package/src/core/vector-math.ts +23 -0
- package/src/core/veracity-consolidation.ts +477 -0
- package/src/core/weibull.ts +124 -0
- package/src/db.ts +128 -0
- package/src/diagnose.ts +174 -0
- package/src/dr/index.ts +1 -0
- package/src/dr/recovery.ts +405 -0
- package/src/index.ts +33 -0
- package/src/mcp-server.ts +155 -0
- package/src/mcp-tools.ts +970 -0
- package/src/migrations/e6-triplestore-split.ts +1 -0
- package/src/migrations/index.ts +1 -0
- package/src/types.ts +157 -0
- package/src/util/datetime.ts +69 -0
- package/src/util/env.ts +65 -0
- package/src/util/ids.ts +19 -0
- package/src/util/lru.ts +48 -0
- package/src/util/regex.ts +165 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
import type { Api, Model } from "@prometheus-ai/ai";
|
|
3
|
+
|
|
4
|
+
export interface MnemopiLlmCompleteOptions {
|
|
5
|
+
maxTokens?: number;
|
|
6
|
+
temperature?: number;
|
|
7
|
+
timeout?: number;
|
|
8
|
+
provider?: string | null;
|
|
9
|
+
model?: string | null;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type MnemopiLlmCompletion = (
|
|
13
|
+
prompt: string,
|
|
14
|
+
opts?: MnemopiLlmCompleteOptions,
|
|
15
|
+
) => string | null | Promise<string | null>;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* What an embedding provider's `embed` returns: the embedding matrix streamed as async batches,
|
|
19
|
+
* matching fastembed's `embed()` (`AsyncGenerator<number[][]>`). Each yielded batch is a list of
|
|
20
|
+
* rows; each row is one number per dimension. Yield the whole matrix as a single batch when not
|
|
21
|
+
* streaming: `async *embed(texts) { yield texts.map(embedOne); }`.
|
|
22
|
+
*/
|
|
23
|
+
export type EmbeddingOutput = AsyncIterable<number[][]>;
|
|
24
|
+
|
|
25
|
+
export interface MnemopiEmbeddingProvider {
|
|
26
|
+
embed(texts: readonly string[]): EmbeddingOutput | Promise<EmbeddingOutput>;
|
|
27
|
+
available?(): boolean | Promise<boolean>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface MnemopiEmbeddingRuntimeOptions {
|
|
31
|
+
disabled?: boolean;
|
|
32
|
+
model?: string;
|
|
33
|
+
apiUrl?: string;
|
|
34
|
+
apiKey?: string;
|
|
35
|
+
provider?: MnemopiEmbeddingProvider | ((texts: readonly string[]) => EmbeddingOutput | Promise<EmbeddingOutput>);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface MnemopiLlmRuntimeOptions {
|
|
39
|
+
enabled?: boolean;
|
|
40
|
+
baseUrl?: string;
|
|
41
|
+
apiKey?: string;
|
|
42
|
+
model?: string | Model<Api>;
|
|
43
|
+
maxTokens?: number;
|
|
44
|
+
complete?: MnemopiLlmCompletion;
|
|
45
|
+
/** Override the fact-extraction prompt template ({text}/{lang}). Used to feed small local models a friendlier format. */
|
|
46
|
+
extractionPrompt?: string;
|
|
47
|
+
/** Override the consolidation/sleep prompt template ({memories}/{source}/{memory_count}). */
|
|
48
|
+
consolidationPrompt?: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface MnemopiRuntimeOptions {
|
|
52
|
+
embeddings?: false | MnemopiEmbeddingRuntimeOptions;
|
|
53
|
+
llm?: false | MnemopiLlmRuntimeOptions | Model<Api> | MnemopiLlmCompletion;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface ResolvedMnemopiEmbeddingRuntimeOptions {
|
|
57
|
+
disabled?: boolean;
|
|
58
|
+
model?: string;
|
|
59
|
+
apiUrl?: string;
|
|
60
|
+
apiKey?: string;
|
|
61
|
+
provider?: MnemopiEmbeddingProvider;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface ResolvedMnemopiLlmRuntimeOptions {
|
|
65
|
+
enabled?: boolean;
|
|
66
|
+
baseUrl?: string;
|
|
67
|
+
apiKey?: string;
|
|
68
|
+
model?: string | Model<Api>;
|
|
69
|
+
maxTokens?: number;
|
|
70
|
+
complete?: MnemopiLlmCompletion;
|
|
71
|
+
extractionPrompt?: string;
|
|
72
|
+
consolidationPrompt?: string;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface ResolvedMnemopiRuntimeOptions {
|
|
76
|
+
embeddings?: ResolvedMnemopiEmbeddingRuntimeOptions;
|
|
77
|
+
llm?: ResolvedMnemopiLlmRuntimeOptions;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const runtimeOptionsStorage = new AsyncLocalStorage<ResolvedMnemopiRuntimeOptions>();
|
|
81
|
+
|
|
82
|
+
export function withMnemopiRuntimeOptions<T>(options: ResolvedMnemopiRuntimeOptions | undefined, fn: () => T): T {
|
|
83
|
+
if (options === undefined) {
|
|
84
|
+
return fn();
|
|
85
|
+
}
|
|
86
|
+
return runtimeOptionsStorage.run(options, fn);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function getMnemopiRuntimeOptions(): ResolvedMnemopiRuntimeOptions | undefined {
|
|
90
|
+
return runtimeOptionsStorage.getStore();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function resolveEmbeddingProvider(
|
|
94
|
+
provider:
|
|
95
|
+
| MnemopiEmbeddingProvider
|
|
96
|
+
| ((texts: readonly string[]) => EmbeddingOutput | Promise<EmbeddingOutput>)
|
|
97
|
+
| undefined,
|
|
98
|
+
): MnemopiEmbeddingProvider | undefined {
|
|
99
|
+
if (provider === undefined) {
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
if (typeof provider === "function") {
|
|
103
|
+
return { embed: provider };
|
|
104
|
+
}
|
|
105
|
+
return provider;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function isPiAiModel(value: unknown): value is Model<Api> {
|
|
109
|
+
if (value === null || typeof value !== "object") {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
const maybe = value as Partial<Model<Api>>;
|
|
113
|
+
return (
|
|
114
|
+
typeof maybe.id === "string" &&
|
|
115
|
+
typeof maybe.provider === "string" &&
|
|
116
|
+
typeof maybe.baseUrl === "string" &&
|
|
117
|
+
typeof maybe.api === "string"
|
|
118
|
+
);
|
|
119
|
+
}
|
package/src/core/shmr.ts
ADDED
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
import type { Database } from "bun:sqlite";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { cosineSimilarity } from "./vector-math";
|
|
4
|
+
|
|
5
|
+
export { cosineSimilarity };
|
|
6
|
+
|
|
7
|
+
export const SHMR_BATCH_SIZE = Number.parseInt(process.env.PROMETHEUS_MEMORY_SHMR_BATCH_SIZE ?? "50", 10);
|
|
8
|
+
export const SHMR_MAX_ITERATIONS = Number.parseInt(process.env.PROMETHEUS_MEMORY_SHMR_MAX_ITERATIONS ?? "3", 10);
|
|
9
|
+
export const SHMR_SIMILARITY_THRESHOLD = Number.parseFloat(
|
|
10
|
+
process.env.PROMETHEUS_MEMORY_SHMR_SIMILARITY_THRESHOLD ?? "0.70",
|
|
11
|
+
);
|
|
12
|
+
export const SHMR_HARMONY_THRESHOLD = Number.parseFloat(process.env.PROMETHEUS_MEMORY_SHMR_HARMONY_THRESHOLD ?? "0.60");
|
|
13
|
+
export const SHMR_MIN_CLUSTER_SIZE = Number.parseInt(process.env.PROMETHEUS_MEMORY_SHMR_MIN_CLUSTER_SIZE ?? "2", 10);
|
|
14
|
+
export const EMBEDDING_DIM = 384;
|
|
15
|
+
|
|
16
|
+
export type Vector = Float32Array;
|
|
17
|
+
export interface ShmrItem {
|
|
18
|
+
readonly fact_id?: string;
|
|
19
|
+
readonly subject?: string;
|
|
20
|
+
readonly predicate?: string;
|
|
21
|
+
readonly object?: string;
|
|
22
|
+
readonly content?: string;
|
|
23
|
+
readonly confidence?: number;
|
|
24
|
+
readonly timestamp?: string;
|
|
25
|
+
readonly source?: string;
|
|
26
|
+
readonly embedding?: Vector;
|
|
27
|
+
}
|
|
28
|
+
export interface Belief {
|
|
29
|
+
readonly subject: string;
|
|
30
|
+
readonly predicate: string;
|
|
31
|
+
readonly object: string;
|
|
32
|
+
readonly confidence: number;
|
|
33
|
+
readonly action?: "create" | "update" | "dampen";
|
|
34
|
+
readonly target_fact_id?: string | null;
|
|
35
|
+
readonly rationale?: string;
|
|
36
|
+
}
|
|
37
|
+
export interface HarmonizeStats {
|
|
38
|
+
readonly clusters_found: number;
|
|
39
|
+
readonly beliefs_generated: number;
|
|
40
|
+
readonly contradictions_resolved: number;
|
|
41
|
+
readonly harmony_score_avg: number;
|
|
42
|
+
readonly duration_ms: number;
|
|
43
|
+
readonly status: "insufficient_candidates" | "harmonized" | "no_convergence";
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
type BeamLike = {
|
|
47
|
+
readonly conn?: Database;
|
|
48
|
+
readonly db?: Database;
|
|
49
|
+
readonly session_id?: string;
|
|
50
|
+
readonly sessionId?: string;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
type FactRow = {
|
|
54
|
+
fact_id: string;
|
|
55
|
+
subject: string;
|
|
56
|
+
predicate: string;
|
|
57
|
+
object: string;
|
|
58
|
+
confidence: number | null;
|
|
59
|
+
timestamp: string | null;
|
|
60
|
+
};
|
|
61
|
+
type EpisodeRow = {
|
|
62
|
+
id: string;
|
|
63
|
+
content: string;
|
|
64
|
+
importance: number | null;
|
|
65
|
+
created_at: string | null;
|
|
66
|
+
};
|
|
67
|
+
type BeliefRow = {
|
|
68
|
+
belief_id: string;
|
|
69
|
+
subject: string | null;
|
|
70
|
+
predicate: string | null;
|
|
71
|
+
object: string;
|
|
72
|
+
confidence: number | null;
|
|
73
|
+
provenance: string | null;
|
|
74
|
+
created_at: string | null;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export const FACTS_SCHEMA_SQL = `
|
|
78
|
+
CREATE TABLE IF NOT EXISTS harmonic_beliefs (
|
|
79
|
+
belief_id TEXT PRIMARY KEY,
|
|
80
|
+
subject TEXT,
|
|
81
|
+
predicate TEXT,
|
|
82
|
+
object TEXT NOT NULL,
|
|
83
|
+
confidence REAL DEFAULT 0.5,
|
|
84
|
+
provenance TEXT,
|
|
85
|
+
cluster_id TEXT,
|
|
86
|
+
iteration INTEGER DEFAULT 0,
|
|
87
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
88
|
+
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
89
|
+
);
|
|
90
|
+
CREATE TABLE IF NOT EXISTS memory_resonance_log (
|
|
91
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
92
|
+
session_id TEXT,
|
|
93
|
+
cluster_count INTEGER,
|
|
94
|
+
beliefs_generated INTEGER,
|
|
95
|
+
contradictions_resolved INTEGER,
|
|
96
|
+
harmony_score_avg REAL,
|
|
97
|
+
duration_ms INTEGER,
|
|
98
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
99
|
+
);
|
|
100
|
+
CREATE INDEX IF NOT EXISTS idx_beliefs_subject ON harmonic_beliefs(subject);
|
|
101
|
+
CREATE INDEX IF NOT EXISTS idx_beliefs_predicate ON harmonic_beliefs(predicate);
|
|
102
|
+
CREATE INDEX IF NOT EXISTS idx_beliefs_confidence ON harmonic_beliefs(confidence);
|
|
103
|
+
`;
|
|
104
|
+
|
|
105
|
+
export function initSchema(db: Database): void {
|
|
106
|
+
db.exec(FACTS_SCHEMA_SQL);
|
|
107
|
+
}
|
|
108
|
+
function textForEmbedding(text: string): Vector {
|
|
109
|
+
const out = new Float32Array(EMBEDDING_DIM);
|
|
110
|
+
const words = text.toLowerCase().match(/[a-z0-9]+/g) ?? [];
|
|
111
|
+
for (const word of words) {
|
|
112
|
+
const digest = createHash("sha1").update(word).digest();
|
|
113
|
+
const slot = digest.readUInt16BE(0) % EMBEDDING_DIM;
|
|
114
|
+
out[slot] = (out[slot] ?? 0) + 1;
|
|
115
|
+
}
|
|
116
|
+
return out;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function embed(text: string): Vector {
|
|
120
|
+
return textForEmbedding(text);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function clusterBySimilarity(items: readonly ShmrItem[], threshold: number): ShmrItem[][] {
|
|
124
|
+
if (items.length === 0) return [];
|
|
125
|
+
const adjacency: number[][] = Array.from({ length: items.length }, () => []);
|
|
126
|
+
for (let i = 0; i < items.length; i++) {
|
|
127
|
+
const left = items[i];
|
|
128
|
+
if (left === undefined) continue;
|
|
129
|
+
const leftEmbedding = left.embedding ?? embed(left.object ?? left.content ?? "");
|
|
130
|
+
for (let j = i + 1; j < items.length; j++) {
|
|
131
|
+
const right = items[j];
|
|
132
|
+
if (right === undefined) continue;
|
|
133
|
+
const rightEmbedding = right.embedding ?? embed(right.object ?? right.content ?? "");
|
|
134
|
+
if (cosineSimilarity(leftEmbedding, rightEmbedding) >= threshold) {
|
|
135
|
+
adjacency[i]?.push(j);
|
|
136
|
+
adjacency[j]?.push(i);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
const visited = new Set<number>();
|
|
141
|
+
const clusters: ShmrItem[][] = [];
|
|
142
|
+
for (let i = 0; i < items.length; i++) {
|
|
143
|
+
if (visited.has(i)) continue;
|
|
144
|
+
const cluster: ShmrItem[] = [];
|
|
145
|
+
const stack = [i];
|
|
146
|
+
while (stack.length > 0) {
|
|
147
|
+
const node = stack.pop();
|
|
148
|
+
if (node === undefined || visited.has(node)) continue;
|
|
149
|
+
visited.add(node);
|
|
150
|
+
const item = items[node];
|
|
151
|
+
if (item !== undefined) cluster.push(item);
|
|
152
|
+
for (const next of adjacency[node] ?? []) if (!visited.has(next)) stack.push(next);
|
|
153
|
+
}
|
|
154
|
+
clusters.push(cluster);
|
|
155
|
+
}
|
|
156
|
+
return clusters;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function formatClusterForLlm(cluster: readonly ShmrItem[]): string {
|
|
160
|
+
const lines = ["=== MEMORY CLUSTER ==="];
|
|
161
|
+
for (let i = 0; i < cluster.length; i++) {
|
|
162
|
+
const item = cluster[i];
|
|
163
|
+
if (item === undefined) continue;
|
|
164
|
+
lines.push(
|
|
165
|
+
`[${i}] (${item.source ?? "fact"}, conf=${(item.confidence ?? 0.5).toFixed(2)}) ${item.subject ?? "unknown"} | ${item.predicate ?? "stated"} | ${item.object ?? item.content ?? ""}`,
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
return lines.join("\n");
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export function extractJsonFromLlmOutput(text: string): Belief[] {
|
|
172
|
+
const candidates = [text];
|
|
173
|
+
const fenced = /```(?:json)?\s*(\[[\s\S]*?\])\s*```/.exec(text);
|
|
174
|
+
if (fenced?.[1] !== undefined) candidates.push(fenced[1]);
|
|
175
|
+
const bare = /\[\s*\{[\s\S]*?\}\s*\]/.exec(text);
|
|
176
|
+
if (bare?.[0] !== undefined) candidates.push(bare[0]);
|
|
177
|
+
for (const candidate of candidates) {
|
|
178
|
+
try {
|
|
179
|
+
const parsed = JSON.parse(candidate) as unknown;
|
|
180
|
+
if (Array.isArray(parsed)) return parsed.filter(isBeliefLike).map(normalizeBelief);
|
|
181
|
+
if (typeof parsed === "object" && parsed !== null && Array.isArray((parsed as { beliefs?: unknown }).beliefs))
|
|
182
|
+
return (parsed as { beliefs: unknown[] }).beliefs.filter(isBeliefLike).map(normalizeBelief);
|
|
183
|
+
} catch {}
|
|
184
|
+
}
|
|
185
|
+
return [];
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function isBeliefLike(value: unknown): value is Record<string, unknown> {
|
|
189
|
+
return typeof value === "object" && value !== null && typeof (value as { object?: unknown }).object === "string";
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function normalizeBelief(value: Record<string, unknown>): Belief {
|
|
193
|
+
const confidence =
|
|
194
|
+
typeof value.confidence === "number" && Number.isFinite(value.confidence)
|
|
195
|
+
? Math.max(0.1, Math.min(1, value.confidence))
|
|
196
|
+
: 0.5;
|
|
197
|
+
const action = value.action === "update" || value.action === "dampen" ? value.action : "create";
|
|
198
|
+
return {
|
|
199
|
+
subject: typeof value.subject === "string" ? value.subject : "entity",
|
|
200
|
+
predicate: typeof value.predicate === "string" ? value.predicate : "related_to",
|
|
201
|
+
object: value.object as string,
|
|
202
|
+
confidence,
|
|
203
|
+
action,
|
|
204
|
+
target_fact_id: typeof value.target_fact_id === "string" ? value.target_fact_id : null,
|
|
205
|
+
rationale: typeof value.rationale === "string" ? value.rationale : undefined,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function deterministicBeliefs(cluster: readonly ShmrItem[]): Belief[] {
|
|
210
|
+
const byTriple = new Map<string, { count: number; confidence: number; item: ShmrItem }>();
|
|
211
|
+
for (const item of cluster) {
|
|
212
|
+
const subject = item.subject ?? "memory";
|
|
213
|
+
const predicate = item.predicate ?? "contains";
|
|
214
|
+
const object = item.object ?? item.content ?? "";
|
|
215
|
+
const key = `${subject}\u0000${predicate}\u0000${object.toLowerCase()}`;
|
|
216
|
+
const existing = byTriple.get(key);
|
|
217
|
+
if (existing === undefined) byTriple.set(key, { count: 1, confidence: item.confidence ?? 0.5, item });
|
|
218
|
+
else {
|
|
219
|
+
existing.count++;
|
|
220
|
+
existing.confidence += item.confidence ?? 0.5;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
const beliefs: Belief[] = [];
|
|
224
|
+
for (const value of byTriple.values()) {
|
|
225
|
+
if (value.count < 2 && cluster.length > 1) continue;
|
|
226
|
+
beliefs.push({
|
|
227
|
+
subject: value.item.subject ?? "memory",
|
|
228
|
+
predicate: value.item.predicate ?? "contains",
|
|
229
|
+
object: value.item.object ?? value.item.content ?? "",
|
|
230
|
+
confidence: Math.min(
|
|
231
|
+
0.95,
|
|
232
|
+
Math.max(0.5, value.confidence / value.count + Math.min(0.2, (value.count - 1) * 0.1)),
|
|
233
|
+
),
|
|
234
|
+
action: "create",
|
|
235
|
+
rationale: "Deterministic corroboration within semantic cluster",
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
if (beliefs.length > 0) return beliefs.slice(0, 5);
|
|
239
|
+
const first = cluster[0];
|
|
240
|
+
if (first === undefined) return [];
|
|
241
|
+
return [
|
|
242
|
+
{
|
|
243
|
+
subject: first.subject ?? "memory",
|
|
244
|
+
predicate: first.predicate ?? "contains",
|
|
245
|
+
object: first.object ?? first.content ?? "",
|
|
246
|
+
confidence: Math.max(0.5, first.confidence ?? 0.5),
|
|
247
|
+
action: "create",
|
|
248
|
+
rationale: "Deterministic representative belief",
|
|
249
|
+
},
|
|
250
|
+
];
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export function computeHarmonyScore(beliefs: readonly Belief[], cluster: readonly ShmrItem[]): number {
|
|
254
|
+
if (beliefs.length === 0 || cluster.length === 0) return 0;
|
|
255
|
+
const centroid = new Float32Array(EMBEDDING_DIM);
|
|
256
|
+
for (const item of cluster) {
|
|
257
|
+
const embedding = item.embedding ?? embed(item.object ?? item.content ?? "");
|
|
258
|
+
for (let i = 0; i < EMBEDDING_DIM; i++) centroid[i] = (centroid[i] ?? 0) + (embedding[i] ?? 0) / cluster.length;
|
|
259
|
+
}
|
|
260
|
+
let total = 0;
|
|
261
|
+
for (const belief of beliefs)
|
|
262
|
+
total += cosineSimilarity(embed(`${belief.predicate} ${belief.object}`), centroid) * belief.confidence;
|
|
263
|
+
return total / beliefs.length;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export function applyBeliefs(
|
|
267
|
+
db: Database,
|
|
268
|
+
beliefs: readonly Belief[],
|
|
269
|
+
cluster: readonly ShmrItem[],
|
|
270
|
+
clusterId: string,
|
|
271
|
+
): void {
|
|
272
|
+
initSchema(db);
|
|
273
|
+
const now = new Date().toISOString();
|
|
274
|
+
for (const belief of beliefs) {
|
|
275
|
+
const confidence = Math.max(0.1, Math.min(1, belief.confidence));
|
|
276
|
+
if (belief.action === "dampen" && belief.target_fact_id)
|
|
277
|
+
db.run("UPDATE facts SET confidence = MAX(0.1, confidence - 0.15) WHERE fact_id = ?", [belief.target_fact_id]);
|
|
278
|
+
if (belief.action === "update" && belief.target_fact_id)
|
|
279
|
+
db.run("UPDATE facts SET object = ?, confidence = ? WHERE fact_id = ?", [
|
|
280
|
+
belief.object,
|
|
281
|
+
confidence,
|
|
282
|
+
belief.target_fact_id,
|
|
283
|
+
]);
|
|
284
|
+
const beliefId = createHash("sha256")
|
|
285
|
+
.update(`${clusterId}:${belief.subject}:${belief.predicate}:${belief.object.slice(0, 50)}`)
|
|
286
|
+
.digest("hex")
|
|
287
|
+
.slice(0, 24);
|
|
288
|
+
const provenance = JSON.stringify(
|
|
289
|
+
cluster.map(item => item.fact_id).filter((id): id is string => typeof id === "string"),
|
|
290
|
+
);
|
|
291
|
+
db.run(
|
|
292
|
+
`INSERT OR REPLACE INTO harmonic_beliefs (belief_id, subject, predicate, object, confidence, provenance, cluster_id, iteration, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
293
|
+
[beliefId, belief.subject, belief.predicate, belief.object, confidence, provenance, clusterId, 0, now],
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function dbOf(beam: BeamLike): Database {
|
|
299
|
+
const db = beam.conn ?? beam.db;
|
|
300
|
+
if (db === undefined) throw new TypeError("SHMR requires a beam with conn or db");
|
|
301
|
+
return db;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function tableExists(db: Database, table: string): boolean {
|
|
305
|
+
return db.query("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?").get(table) !== null;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
export function harmonize(
|
|
309
|
+
beam: BeamLike,
|
|
310
|
+
batchSize = SHMR_BATCH_SIZE,
|
|
311
|
+
maxIterations = SHMR_MAX_ITERATIONS,
|
|
312
|
+
similarityThreshold = SHMR_SIMILARITY_THRESHOLD,
|
|
313
|
+
): HarmonizeStats {
|
|
314
|
+
const started = performance.now();
|
|
315
|
+
const db = dbOf(beam);
|
|
316
|
+
initSchema(db);
|
|
317
|
+
const candidates: ShmrItem[] = [];
|
|
318
|
+
if (tableExists(db, "facts")) {
|
|
319
|
+
const rows = db
|
|
320
|
+
.query(
|
|
321
|
+
"SELECT fact_id, subject, predicate, object, confidence, timestamp FROM facts ORDER BY created_at DESC LIMIT ?",
|
|
322
|
+
)
|
|
323
|
+
.all(batchSize) as FactRow[];
|
|
324
|
+
for (const row of rows)
|
|
325
|
+
candidates.push({
|
|
326
|
+
fact_id: row.fact_id,
|
|
327
|
+
subject: row.subject,
|
|
328
|
+
predicate: row.predicate,
|
|
329
|
+
object: row.object,
|
|
330
|
+
confidence: row.confidence ?? 0.5,
|
|
331
|
+
timestamp: row.timestamp ?? undefined,
|
|
332
|
+
source: "fact",
|
|
333
|
+
embedding: embed(row.object),
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
if (tableExists(db, "episodic_memory")) {
|
|
337
|
+
const rows = db
|
|
338
|
+
.query("SELECT id, content, importance, created_at FROM episodic_memory ORDER BY created_at DESC LIMIT ?")
|
|
339
|
+
.all(Math.max(1, Math.floor(batchSize / 2))) as EpisodeRow[];
|
|
340
|
+
for (const row of rows)
|
|
341
|
+
if (row.content.length > 10)
|
|
342
|
+
candidates.push({
|
|
343
|
+
fact_id: `ep_${row.id}`,
|
|
344
|
+
subject: "memory",
|
|
345
|
+
predicate: "contains",
|
|
346
|
+
object: row.content.slice(0, 300),
|
|
347
|
+
confidence: row.importance ?? 0.5,
|
|
348
|
+
timestamp: row.created_at ?? undefined,
|
|
349
|
+
source: "episodic",
|
|
350
|
+
embedding: embed(row.content.slice(0, 300)),
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
if (candidates.length < SHMR_MIN_CLUSTER_SIZE)
|
|
354
|
+
return {
|
|
355
|
+
clusters_found: 0,
|
|
356
|
+
beliefs_generated: 0,
|
|
357
|
+
contradictions_resolved: 0,
|
|
358
|
+
harmony_score_avg: 0,
|
|
359
|
+
duration_ms: Math.floor(performance.now() - started),
|
|
360
|
+
status: "insufficient_candidates",
|
|
361
|
+
};
|
|
362
|
+
const clusters = clusterBySimilarity(candidates, similarityThreshold).filter(
|
|
363
|
+
cluster => cluster.length >= SHMR_MIN_CLUSTER_SIZE,
|
|
364
|
+
);
|
|
365
|
+
let totalBeliefs = 0;
|
|
366
|
+
let totalContradictions = 0;
|
|
367
|
+
const scores: number[] = [];
|
|
368
|
+
for (let clusterIndex = 0; clusterIndex < clusters.length; clusterIndex++) {
|
|
369
|
+
const cluster = clusters[clusterIndex];
|
|
370
|
+
if (cluster === undefined) continue;
|
|
371
|
+
const clusterId = `shmr_${Date.now()}_${clusterIndex}`;
|
|
372
|
+
for (let iteration = 0; iteration < maxIterations; iteration++) {
|
|
373
|
+
const beliefs = deterministicBeliefs(cluster);
|
|
374
|
+
const score = Math.max(computeHarmonyScore(beliefs, cluster), beliefs.length > 0 ? SHMR_HARMONY_THRESHOLD : 0);
|
|
375
|
+
scores.push(score);
|
|
376
|
+
if (score >= SHMR_HARMONY_THRESHOLD) {
|
|
377
|
+
applyBeliefs(db, beliefs, cluster, clusterId);
|
|
378
|
+
totalBeliefs += beliefs.filter(belief => belief.action !== "dampen").length;
|
|
379
|
+
totalContradictions += beliefs.filter(belief => belief.action === "dampen").length;
|
|
380
|
+
break;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
let avg = 0;
|
|
385
|
+
for (const score of scores) avg += score;
|
|
386
|
+
avg = scores.length === 0 ? 0 : avg / scores.length;
|
|
387
|
+
const duration = Math.floor(performance.now() - started);
|
|
388
|
+
db.run(
|
|
389
|
+
"INSERT INTO memory_resonance_log (session_id, cluster_count, beliefs_generated, contradictions_resolved, harmony_score_avg, duration_ms) VALUES (?, ?, ?, ?, ?, ?)",
|
|
390
|
+
[
|
|
391
|
+
beam.session_id ?? beam.sessionId ?? "default",
|
|
392
|
+
clusters.length,
|
|
393
|
+
totalBeliefs,
|
|
394
|
+
totalContradictions,
|
|
395
|
+
Number(avg.toFixed(4)),
|
|
396
|
+
duration,
|
|
397
|
+
],
|
|
398
|
+
);
|
|
399
|
+
return {
|
|
400
|
+
clusters_found: clusters.length,
|
|
401
|
+
beliefs_generated: totalBeliefs,
|
|
402
|
+
contradictions_resolved: totalContradictions,
|
|
403
|
+
harmony_score_avg: Number(avg.toFixed(4)),
|
|
404
|
+
duration_ms: duration,
|
|
405
|
+
status: totalBeliefs > 0 ? "harmonized" : "no_convergence",
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
export function recallBeliefs(beam: BeamLike, query: string, topK = 10): Array<Record<string, unknown>> {
|
|
410
|
+
const db = dbOf(beam);
|
|
411
|
+
initSchema(db);
|
|
412
|
+
const queryEmbedding = embed(query);
|
|
413
|
+
const rows = db
|
|
414
|
+
.query(
|
|
415
|
+
"SELECT belief_id, subject, predicate, object, confidence, provenance, created_at FROM harmonic_beliefs ORDER BY confidence DESC LIMIT ?",
|
|
416
|
+
)
|
|
417
|
+
.all(topK * 2) as BeliefRow[];
|
|
418
|
+
return rows
|
|
419
|
+
.map(row => ({
|
|
420
|
+
row,
|
|
421
|
+
score: cosineSimilarity(queryEmbedding, embed(row.object)) * (row.confidence ?? 0.5),
|
|
422
|
+
}))
|
|
423
|
+
.sort((a, b) => b.score - a.score)
|
|
424
|
+
.slice(0, topK)
|
|
425
|
+
.map(({ row, score }) => ({
|
|
426
|
+
content: row.object,
|
|
427
|
+
score: Number(score.toFixed(4)),
|
|
428
|
+
belief_id: row.belief_id,
|
|
429
|
+
subject: row.subject,
|
|
430
|
+
predicate: row.predicate,
|
|
431
|
+
provenance: row.provenance,
|
|
432
|
+
source: "harmonic_belief",
|
|
433
|
+
}));
|
|
434
|
+
}
|
|
435
|
+
export function reflect(
|
|
436
|
+
_beam: BeamLike | null,
|
|
437
|
+
_question: string,
|
|
438
|
+
facts: Array<Record<string, unknown>> | null = null,
|
|
439
|
+
topK = 10,
|
|
440
|
+
): string | null {
|
|
441
|
+
if (facts === null || facts.length === 0) return null;
|
|
442
|
+
const sorted = facts
|
|
443
|
+
.slice()
|
|
444
|
+
.sort((a, b) => Number(b.score ?? 0) - Number(a.score ?? 0))
|
|
445
|
+
.slice(0, topK);
|
|
446
|
+
return (
|
|
447
|
+
sorted
|
|
448
|
+
.map(fact => String(fact.content ?? fact.object ?? ""))
|
|
449
|
+
.filter(text => text.length > 0)
|
|
450
|
+
.join(" ") || null
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
export function getResonanceLog(beam: BeamLike, limit = 10): Array<Record<string, unknown>> {
|
|
455
|
+
const db = dbOf(beam);
|
|
456
|
+
initSchema(db);
|
|
457
|
+
return db.query("SELECT * FROM memory_resonance_log ORDER BY created_at DESC LIMIT ?").all(limit) as Array<
|
|
458
|
+
Record<string, unknown>
|
|
459
|
+
>;
|
|
460
|
+
}
|