@knolo/core 3.2.1 → 3.2.4
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 +68 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.js +5 -0
- package/dist/indexer.d.ts +5 -4
- package/dist/indexer.js +6 -5
- package/dist/memory/consolidate.d.ts +15 -0
- package/dist/memory/consolidate.js +71 -0
- package/dist/memory/cortex.d.ts +43 -0
- package/dist/memory/cortex.js +93 -0
- package/dist/memory/engram.d.ts +48 -0
- package/dist/memory/engram.js +90 -0
- package/dist/memory/graph_adapter.d.ts +3 -0
- package/dist/memory/graph_adapter.js +65 -0
- package/dist/memory/index.d.ts +13 -0
- package/dist/memory/index.js +7 -0
- package/dist/memory/label.d.ts +4 -0
- package/dist/memory/label.js +53 -0
- package/dist/memory/log.d.ts +36 -0
- package/dist/memory/log.js +241 -0
- package/dist/memory/recall.d.ts +23 -0
- package/dist/memory/recall.js +167 -0
- package/dist/query.d.ts +10 -0
- package/dist/query.js +90 -7
- package/dist/semantic/cosine.d.ts +2 -0
- package/dist/semantic/cosine.js +20 -0
- package/dist/semantic/provider.d.ts +3 -0
- package/dist/semantic/provider.js +13 -0
- package/dist/semantic/rerank.d.ts +23 -0
- package/dist/semantic/rerank.js +42 -0
- package/dist/semantic/sidecar.d.ts +10 -0
- package/dist/semantic/sidecar.js +32 -0
- package/dist/semantic/types.d.ts +44 -0
- package/dist/semantic/types.js +1 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -309,6 +309,74 @@ Properties:
|
|
|
309
309
|
|
|
310
310
|
---
|
|
311
311
|
|
|
312
|
+
# 🧠 Knolo Cortex
|
|
313
|
+
|
|
314
|
+
Knolo Cortex is a local-first overlay memory layer for `.knolo` packs.
|
|
315
|
+
|
|
316
|
+
It gives you:
|
|
317
|
+
|
|
318
|
+
* Deterministic append-only memory writes
|
|
319
|
+
* Lexical-first recall with label and namespace filters
|
|
320
|
+
* Portable memory logs you can serialize and replay
|
|
321
|
+
* Consolidation back into pack docs without mutating the pack itself
|
|
322
|
+
* Deterministic graph export via `memoryToClaimOps()`
|
|
323
|
+
|
|
324
|
+
## Example
|
|
325
|
+
|
|
326
|
+
```ts
|
|
327
|
+
import {
|
|
328
|
+
buildPack,
|
|
329
|
+
consolidateMemories,
|
|
330
|
+
createCortex,
|
|
331
|
+
mountPack,
|
|
332
|
+
recall,
|
|
333
|
+
remember,
|
|
334
|
+
} from "@knolo/core";
|
|
335
|
+
|
|
336
|
+
const cortex = createCortex({ actor: "notes-app" });
|
|
337
|
+
const { cortex: next, memory } = remember(cortex, {
|
|
338
|
+
kind: "note",
|
|
339
|
+
text: "Project alpha uses a local-first memory overlay.",
|
|
340
|
+
labels: ["project.alpha"],
|
|
341
|
+
namespace: "project.alpha",
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
const hits = recall(next, "project alpha");
|
|
345
|
+
const docs = consolidateMemories(next, { namespacePrefix: "memory" });
|
|
346
|
+
const bytes = await buildPack(docs);
|
|
347
|
+
const pack = await mountPack({ src: bytes });
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
If you need to load a local file in Node, use `@knolo/core/node` or read the bytes first and pass a `Uint8Array` into `mountPack()`.
|
|
351
|
+
|
|
352
|
+
## Cortex API
|
|
353
|
+
|
|
354
|
+
```ts
|
|
355
|
+
import {
|
|
356
|
+
createCortex,
|
|
357
|
+
remember,
|
|
358
|
+
forget,
|
|
359
|
+
labelMemory,
|
|
360
|
+
linkMemories,
|
|
361
|
+
recall,
|
|
362
|
+
consolidateMemories,
|
|
363
|
+
memoryToClaimOps,
|
|
364
|
+
} from "@knolo/core";
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
* `createCortex({ actor?, now?, log? })` creates an immutable memory runtime
|
|
368
|
+
* `remember()` appends a new memory entry
|
|
369
|
+
* `forget()` tombstones a memory
|
|
370
|
+
* `labelMemory()` adds labels without mutating the original cortex
|
|
371
|
+
* `linkMemories()` records deterministic memory relationships
|
|
372
|
+
* `recall()` ranks memories with lexical-first scoring
|
|
373
|
+
* `consolidateMemories()` converts selected memories back into `BuildInputDoc[]`
|
|
374
|
+
* `memoryToClaimOps()` emits deterministic ClaimGraph ops for memory nodes, labels, and links
|
|
375
|
+
|
|
376
|
+
The full example lives in [`examples/memory-overlay/README.md`](../../examples/memory-overlay/README.md).
|
|
377
|
+
|
|
378
|
+
---
|
|
379
|
+
|
|
312
380
|
# 🗺 Roadmap
|
|
313
381
|
|
|
314
382
|
* Incremental pack updates
|
|
@@ -388,4 +456,3 @@ Runtimes that ignore unknown trailing bytes remain compatible.
|
|
|
388
456
|
# 📄 License
|
|
389
457
|
|
|
390
458
|
Apache-2.0
|
|
391
|
-
|
package/dist/index.d.ts
CHANGED
|
@@ -3,13 +3,19 @@ export { query, lexConfidence, validateQueryOptions, validateSemanticQueryOption
|
|
|
3
3
|
export { makeContextPatch } from './patch.js';
|
|
4
4
|
export { buildPack } from './builder.js';
|
|
5
5
|
export { quantizeEmbeddingInt8L2Norm, encodeScaleF16, decodeScaleF16, } from './semantic.js';
|
|
6
|
+
export { cosineSimilarity, normalizeVector } from './semantic/cosine.js';
|
|
7
|
+
export { createPackFingerprint, serializeSidecar, parseSidecar, validateSidecarForPack, } from './semantic/sidecar.js';
|
|
8
|
+
export { rerankCandidates } from './semantic/rerank.js';
|
|
9
|
+
export { assertProviderCompatible, ensureProviderModelId } from './semantic/provider.js';
|
|
6
10
|
export { listAgents, getAgent, resolveAgent, buildSystemPrompt, isToolAllowed, assertToolAllowed, validateAgentRegistry, validateAgentDefinition, } from './agent.js';
|
|
7
11
|
export { getClaimGraph, validateClaimGraph, } from './graph/claim_graph.js';
|
|
8
12
|
export { buildClaimGraph } from './graph/build_claim_graph.js';
|
|
9
13
|
export { createGraphLog, appendOp, applyClaimGraphLog, mergeClaimGraphLogs, serializeClaimGraphLog, deserializeClaimGraphLog, } from './graph/log.js';
|
|
10
14
|
export { expandQueryWithGraph } from './graph/query_expand.js';
|
|
15
|
+
export * from './memory/index.js';
|
|
11
16
|
export type { MountOptions, PackMeta, Pack } from './pack.runtime.js';
|
|
12
17
|
export type { QueryOptions, Hit } from './query.js';
|
|
18
|
+
export type { EmbeddingProvider, SemanticSidecar, SemanticQueryOptions, RetrievalEvidence } from './semantic/types.js';
|
|
13
19
|
export type { ContextPatch } from './patch.js';
|
|
14
20
|
export type { BuildInputDoc, BuildPackOptions } from './builder.js';
|
|
15
21
|
export type { AgentPromptTemplate, AgentToolPolicy, AgentRetrievalDefaults, AgentDefinitionV1, AgentRegistry, ResolveAgentInput, ResolvedAgent, } from './agent.js';
|
package/dist/index.js
CHANGED
|
@@ -4,11 +4,16 @@ export { query, lexConfidence, validateQueryOptions, validateSemanticQueryOption
|
|
|
4
4
|
export { makeContextPatch } from './patch.js';
|
|
5
5
|
export { buildPack } from './builder.js';
|
|
6
6
|
export { quantizeEmbeddingInt8L2Norm, encodeScaleF16, decodeScaleF16, } from './semantic.js';
|
|
7
|
+
export { cosineSimilarity, normalizeVector } from './semantic/cosine.js';
|
|
8
|
+
export { createPackFingerprint, serializeSidecar, parseSidecar, validateSidecarForPack, } from './semantic/sidecar.js';
|
|
9
|
+
export { rerankCandidates } from './semantic/rerank.js';
|
|
10
|
+
export { assertProviderCompatible, ensureProviderModelId } from './semantic/provider.js';
|
|
7
11
|
export { listAgents, getAgent, resolveAgent, buildSystemPrompt, isToolAllowed, assertToolAllowed, validateAgentRegistry, validateAgentDefinition, } from './agent.js';
|
|
8
12
|
export { getClaimGraph, validateClaimGraph, } from './graph/claim_graph.js';
|
|
9
13
|
export { buildClaimGraph } from './graph/build_claim_graph.js';
|
|
10
14
|
export { createGraphLog, appendOp, applyClaimGraphLog, mergeClaimGraphLogs, serializeClaimGraphLog, deserializeClaimGraphLog, } from './graph/log.js';
|
|
11
15
|
export { expandQueryWithGraph } from './graph/query_expand.js';
|
|
16
|
+
export * from './memory/index.js';
|
|
12
17
|
export { parseToolCallV1FromText } from './tool_parse.js';
|
|
13
18
|
export { nowIso, createTrace } from './trace.js';
|
|
14
19
|
export { assertToolCallAllowed } from './tool_gate.js';
|
package/dist/indexer.d.ts
CHANGED
|
@@ -13,11 +13,12 @@ export type IndexBuildResult = {
|
|
|
13
13
|
* sequences of blockId and positions for that term, with zeros as delimiters.
|
|
14
14
|
* The structure looks like:
|
|
15
15
|
*
|
|
16
|
-
* [termId, blockId+1, pos, pos, 0, blockId+1, pos, 0, 0, termId, ...]
|
|
16
|
+
* [termId, blockId+1, pos+1, pos+1, 0, blockId+1, pos+1, 0, 0, termId, ...]
|
|
17
17
|
*
|
|
18
18
|
* Block IDs are stored as bid+1 so that 0 can remain a sentinel delimiter.
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
19
|
+
* Positions are also stored as pos+1 for the same reason. Each block section
|
|
20
|
+
* ends with a 0, and each term section ends with a 0. The entire array can be
|
|
21
|
+
* streamed sequentially without needing to know the sizes of individual lists
|
|
22
|
+
* ahead of time.
|
|
22
23
|
*/
|
|
23
24
|
export declare function buildIndex(blocks: Block[]): IndexBuildResult;
|
package/dist/indexer.js
CHANGED
|
@@ -14,12 +14,13 @@ import { tokenize } from "./tokenize.js";
|
|
|
14
14
|
* sequences of blockId and positions for that term, with zeros as delimiters.
|
|
15
15
|
* The structure looks like:
|
|
16
16
|
*
|
|
17
|
-
* [termId, blockId+1, pos, pos, 0, blockId+1, pos, 0, 0, termId, ...]
|
|
17
|
+
* [termId, blockId+1, pos+1, pos+1, 0, blockId+1, pos+1, 0, 0, termId, ...]
|
|
18
18
|
*
|
|
19
19
|
* Block IDs are stored as bid+1 so that 0 can remain a sentinel delimiter.
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
20
|
+
* Positions are also stored as pos+1 for the same reason. Each block section
|
|
21
|
+
* ends with a 0, and each term section ends with a 0. The entire array can be
|
|
22
|
+
* streamed sequentially without needing to know the sizes of individual lists
|
|
23
|
+
* ahead of time.
|
|
23
24
|
*/
|
|
24
25
|
export function buildIndex(blocks) {
|
|
25
26
|
// Map term to termId and interim map of termId -> blockId -> positions
|
|
@@ -61,7 +62,7 @@ export function buildIndex(blocks) {
|
|
|
61
62
|
for (const [tid, blockMap] of termBlockPositions) {
|
|
62
63
|
postings.push(tid);
|
|
63
64
|
for (const [bid, positions] of blockMap) {
|
|
64
|
-
postings.push(bid + 1, ...positions, 0);
|
|
65
|
+
postings.push(bid + 1, ...positions.map((pos) => pos + 1), 0);
|
|
65
66
|
}
|
|
66
67
|
postings.push(0); // end of term
|
|
67
68
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { BuildInputDoc } from '../builder.js';
|
|
2
|
+
import type { CortexV1 } from './cortex.js';
|
|
3
|
+
import type { MemoryEngramV1 } from './engram.js';
|
|
4
|
+
export type ConsolidateMemoriesOptionsV1 = {
|
|
5
|
+
namespacePrefix?: string;
|
|
6
|
+
kind?: string | readonly string[];
|
|
7
|
+
labels?: string | readonly string[];
|
|
8
|
+
namespace?: string | readonly string[];
|
|
9
|
+
minImportance?: number;
|
|
10
|
+
minConfidence?: number;
|
|
11
|
+
minAgeMs?: number;
|
|
12
|
+
maxAgeMs?: number;
|
|
13
|
+
now?: number;
|
|
14
|
+
};
|
|
15
|
+
export declare function consolidateMemories(cortexOrMemories: CortexV1 | readonly MemoryEngramV1[], opts?: ConsolidateMemoriesOptionsV1): BuildInputDoc[];
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { matchesMemoryLabels, validateMemoryLabels } from './label.js';
|
|
2
|
+
import { normalizeMemoryLabel } from './label.js';
|
|
3
|
+
import { normalize } from '../tokenize.js';
|
|
4
|
+
export function consolidateMemories(cortexOrMemories, opts = {}) {
|
|
5
|
+
const memories = isCortex(cortexOrMemories)
|
|
6
|
+
? cortexOrMemories.memories
|
|
7
|
+
: cortexOrMemories;
|
|
8
|
+
const now = opts.now ?? (isCortex(cortexOrMemories) ? cortexOrMemories.now() : Date.now());
|
|
9
|
+
const namespacePrefix = normalizeNamespacePrefix(opts.namespacePrefix);
|
|
10
|
+
const kindFilters = normalizeKindFilters(opts.kind);
|
|
11
|
+
const labelsFilter = validateMemoryLabels(opts.labels);
|
|
12
|
+
const namespaceFilters = normalizeNamespaceFilters(opts.namespace);
|
|
13
|
+
return memories
|
|
14
|
+
.filter((memory) => {
|
|
15
|
+
if (kindFilters.length > 0 && !kindFilters.includes(normalizeKind(memory.kind)))
|
|
16
|
+
return false;
|
|
17
|
+
if (labelsFilter.length > 0 && !matchesMemoryLabels(memory.labels, labelsFilter))
|
|
18
|
+
return false;
|
|
19
|
+
if (namespaceFilters.length > 0 && !matchesNamespace(memory.namespace, namespaceFilters))
|
|
20
|
+
return false;
|
|
21
|
+
if (opts.minImportance !== undefined && valueOrDefault(memory.importance, 0.5) < opts.minImportance)
|
|
22
|
+
return false;
|
|
23
|
+
if (opts.minConfidence !== undefined && valueOrDefault(memory.confidence, 0.5) < opts.minConfidence)
|
|
24
|
+
return false;
|
|
25
|
+
const ageMs = now - memory.ts;
|
|
26
|
+
if (opts.minAgeMs !== undefined && ageMs < opts.minAgeMs)
|
|
27
|
+
return false;
|
|
28
|
+
if (opts.maxAgeMs !== undefined && ageMs > opts.maxAgeMs)
|
|
29
|
+
return false;
|
|
30
|
+
return true;
|
|
31
|
+
})
|
|
32
|
+
.slice()
|
|
33
|
+
.sort((a, b) => a.ts - b.ts || a.id.localeCompare(b.id))
|
|
34
|
+
.map((memory) => ({
|
|
35
|
+
id: memory.id,
|
|
36
|
+
heading: `${memory.kind}: ${memory.labels.join('/')}`,
|
|
37
|
+
namespace: `${namespacePrefix}.${memory.kind}`,
|
|
38
|
+
text: memory.text,
|
|
39
|
+
}));
|
|
40
|
+
}
|
|
41
|
+
function isCortex(input) {
|
|
42
|
+
return !Array.isArray(input) && typeof input === 'object' && input !== null && 'memories' in input;
|
|
43
|
+
}
|
|
44
|
+
function normalizeNamespacePrefix(value) {
|
|
45
|
+
const normalized = normalizeMemoryLabel(value ?? 'memory');
|
|
46
|
+
return normalized || 'memory';
|
|
47
|
+
}
|
|
48
|
+
function normalizeKindFilters(kind) {
|
|
49
|
+
if (kind === undefined)
|
|
50
|
+
return [];
|
|
51
|
+
const values = Array.isArray(kind) ? kind : [kind];
|
|
52
|
+
return [...new Set(values.map(normalizeKind).filter(Boolean))].sort((a, b) => a.localeCompare(b));
|
|
53
|
+
}
|
|
54
|
+
function normalizeNamespaceFilters(value) {
|
|
55
|
+
if (value === undefined)
|
|
56
|
+
return [];
|
|
57
|
+
const values = Array.isArray(value) ? value : [value];
|
|
58
|
+
return [...new Set(values.map((entry) => normalizeMemoryLabel(entry)).filter(Boolean))].sort((a, b) => a.localeCompare(b));
|
|
59
|
+
}
|
|
60
|
+
function normalizeKind(kind) {
|
|
61
|
+
return normalize(kind).replace(/\s+/g, ' ').trim();
|
|
62
|
+
}
|
|
63
|
+
function matchesNamespace(value, filters) {
|
|
64
|
+
if (!value)
|
|
65
|
+
return false;
|
|
66
|
+
const normalized = normalizeMemoryLabel(value);
|
|
67
|
+
return filters.some((filter) => normalized === filter || normalized.startsWith(`${filter}.`));
|
|
68
|
+
}
|
|
69
|
+
function valueOrDefault(value, fallback) {
|
|
70
|
+
return Number.isFinite(value) ? value : fallback;
|
|
71
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { MemoryEngramV1, MemoryInputV1, MemoryLinkV1 } from './engram.js';
|
|
2
|
+
import { type MemoryLogV1, type MemoryOpV1 } from './log.js';
|
|
3
|
+
export type CortexV1 = {
|
|
4
|
+
version: 1;
|
|
5
|
+
actor: string;
|
|
6
|
+
now: () => number;
|
|
7
|
+
log: MemoryLogV1;
|
|
8
|
+
memories: MemoryEngramV1[];
|
|
9
|
+
};
|
|
10
|
+
export type CortexWriteResult<T extends object> = T & {
|
|
11
|
+
cortex: CortexV1;
|
|
12
|
+
};
|
|
13
|
+
export declare function createCortex(opts?: {
|
|
14
|
+
actor?: string;
|
|
15
|
+
now?: () => number;
|
|
16
|
+
log?: MemoryLogV1;
|
|
17
|
+
}): CortexV1;
|
|
18
|
+
export declare function remember(cortex: CortexV1, input: MemoryInputV1): CortexWriteResult<{
|
|
19
|
+
memory: MemoryEngramV1;
|
|
20
|
+
op: MemoryOpV1;
|
|
21
|
+
}>;
|
|
22
|
+
export declare function forget(cortex: CortexV1, id: string, provenance?: {
|
|
23
|
+
ts?: number;
|
|
24
|
+
actor?: string;
|
|
25
|
+
}): CortexWriteResult<{
|
|
26
|
+
memoryId: string;
|
|
27
|
+
op: MemoryOpV1;
|
|
28
|
+
}>;
|
|
29
|
+
export declare function labelMemory(cortex: CortexV1, id: string, labels: string | readonly string[], provenance?: {
|
|
30
|
+
ts?: number;
|
|
31
|
+
actor?: string;
|
|
32
|
+
}): CortexWriteResult<{
|
|
33
|
+
memory?: MemoryEngramV1;
|
|
34
|
+
op: MemoryOpV1;
|
|
35
|
+
}>;
|
|
36
|
+
export declare function linkMemories(cortex: CortexV1, from: string, to: string, relation: string, provenance?: {
|
|
37
|
+
ts?: number;
|
|
38
|
+
actor?: string;
|
|
39
|
+
confidence?: number;
|
|
40
|
+
}): CortexWriteResult<{
|
|
41
|
+
link: MemoryLinkV1;
|
|
42
|
+
op: MemoryOpV1;
|
|
43
|
+
}>;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { createMemoryId, normalizeMemoryActor, normalizeMemoryInput, normalizeMemoryKind, normalizeMemoryTimestamp, } from './engram.js';
|
|
2
|
+
import { appendMemoryOp, applyMemoryLog, createMemoryLog, } from './log.js';
|
|
3
|
+
import { validateMemoryLabels } from './label.js';
|
|
4
|
+
export function createCortex(opts = {}) {
|
|
5
|
+
const log = opts.log ? { version: 1, ops: [...opts.log.ops] } : createMemoryLog();
|
|
6
|
+
return {
|
|
7
|
+
version: 1,
|
|
8
|
+
actor: normalizeMemoryActor(opts.actor ?? 'cortex'),
|
|
9
|
+
now: opts.now ?? (() => Date.now()),
|
|
10
|
+
log,
|
|
11
|
+
memories: applyMemoryLog(log),
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export function remember(cortex, input) {
|
|
15
|
+
const memory = normalizeMemoryInput(input, {
|
|
16
|
+
actor: cortex.actor,
|
|
17
|
+
ts: cortex.now(),
|
|
18
|
+
});
|
|
19
|
+
const op = {
|
|
20
|
+
op: 'remember',
|
|
21
|
+
ts: memory.ts,
|
|
22
|
+
actor: memory.actor,
|
|
23
|
+
memory,
|
|
24
|
+
};
|
|
25
|
+
return applyWrite(cortex, op, { memory });
|
|
26
|
+
}
|
|
27
|
+
export function forget(cortex, id, provenance = {}) {
|
|
28
|
+
const op = {
|
|
29
|
+
op: 'forget',
|
|
30
|
+
id,
|
|
31
|
+
ts: normalizeMemoryTimestamp(provenance.ts ?? cortex.now()),
|
|
32
|
+
actor: normalizeMemoryActor(provenance.actor ?? cortex.actor),
|
|
33
|
+
};
|
|
34
|
+
return applyWrite(cortex, op, { memoryId: id });
|
|
35
|
+
}
|
|
36
|
+
export function labelMemory(cortex, id, labels, provenance = {}) {
|
|
37
|
+
const normalizedLabels = validateMemoryLabels(labels);
|
|
38
|
+
const op = {
|
|
39
|
+
op: 'label',
|
|
40
|
+
id,
|
|
41
|
+
labels: normalizedLabels,
|
|
42
|
+
ts: normalizeMemoryTimestamp(provenance.ts ?? cortex.now()),
|
|
43
|
+
actor: normalizeMemoryActor(provenance.actor ?? cortex.actor),
|
|
44
|
+
};
|
|
45
|
+
return applyWrite(cortex, op, {
|
|
46
|
+
memory: cortex.memories.find((memory) => memory.id === id),
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
export function linkMemories(cortex, from, to, relation, provenance = {}) {
|
|
50
|
+
const normalizedRelation = normalizeMemoryKind(relation);
|
|
51
|
+
const ts = normalizeMemoryTimestamp(provenance.ts ?? cortex.now());
|
|
52
|
+
const actor = normalizeMemoryActor(provenance.actor ?? cortex.actor);
|
|
53
|
+
const link = {
|
|
54
|
+
version: 1,
|
|
55
|
+
id: createMemoryId({
|
|
56
|
+
kind: 'link',
|
|
57
|
+
text: [from, normalizedRelation, to, provenance.confidence ?? ''].join('\u0001'),
|
|
58
|
+
ts,
|
|
59
|
+
actor,
|
|
60
|
+
}),
|
|
61
|
+
from,
|
|
62
|
+
to,
|
|
63
|
+
relation: normalizedRelation,
|
|
64
|
+
ts,
|
|
65
|
+
actor,
|
|
66
|
+
confidence: provenance.confidence,
|
|
67
|
+
};
|
|
68
|
+
const op = {
|
|
69
|
+
op: 'link',
|
|
70
|
+
from,
|
|
71
|
+
to,
|
|
72
|
+
relation: normalizedRelation,
|
|
73
|
+
confidence: provenance.confidence,
|
|
74
|
+
ts,
|
|
75
|
+
actor,
|
|
76
|
+
};
|
|
77
|
+
return applyWrite(cortex, op, { link });
|
|
78
|
+
}
|
|
79
|
+
function applyWrite(cortex, op, extra) {
|
|
80
|
+
const log = appendMemoryOp(cortex.log, op);
|
|
81
|
+
const memories = applyMemoryLog(log);
|
|
82
|
+
return {
|
|
83
|
+
...extra,
|
|
84
|
+
op,
|
|
85
|
+
cortex: {
|
|
86
|
+
version: 1,
|
|
87
|
+
actor: cortex.actor,
|
|
88
|
+
now: cortex.now,
|
|
89
|
+
log,
|
|
90
|
+
memories,
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export type MemoryKind = string;
|
|
2
|
+
export type MemoryLinkV1 = {
|
|
3
|
+
version: 1;
|
|
4
|
+
id: string;
|
|
5
|
+
from: string;
|
|
6
|
+
to: string;
|
|
7
|
+
relation: string;
|
|
8
|
+
ts: number;
|
|
9
|
+
actor: string;
|
|
10
|
+
confidence?: number;
|
|
11
|
+
};
|
|
12
|
+
export type MemoryEngramV1 = {
|
|
13
|
+
version: 1;
|
|
14
|
+
id: string;
|
|
15
|
+
kind: MemoryKind;
|
|
16
|
+
text: string;
|
|
17
|
+
labels: string[];
|
|
18
|
+
namespace?: string;
|
|
19
|
+
source?: string;
|
|
20
|
+
importance?: number;
|
|
21
|
+
confidence?: number;
|
|
22
|
+
ts: number;
|
|
23
|
+
actor: string;
|
|
24
|
+
links: MemoryLinkV1[];
|
|
25
|
+
};
|
|
26
|
+
export type MemoryInputV1 = {
|
|
27
|
+
kind: MemoryKind;
|
|
28
|
+
text: string;
|
|
29
|
+
labels?: string | readonly string[];
|
|
30
|
+
namespace?: string;
|
|
31
|
+
source?: string;
|
|
32
|
+
importance?: number;
|
|
33
|
+
confidence?: number;
|
|
34
|
+
ts?: number;
|
|
35
|
+
actor?: string;
|
|
36
|
+
};
|
|
37
|
+
export type MemoryProvenanceV1 = {
|
|
38
|
+
ts?: number;
|
|
39
|
+
actor?: string;
|
|
40
|
+
};
|
|
41
|
+
export declare function createMemoryId(input: Pick<MemoryEngramV1, 'kind' | 'text' | 'ts' | 'actor'>): string;
|
|
42
|
+
export declare function normalizeMemoryInput(input: MemoryInputV1, provenance?: MemoryProvenanceV1): MemoryEngramV1;
|
|
43
|
+
export declare function normalizeMemoryKind(kind: string): string;
|
|
44
|
+
export declare function normalizeMemoryText(text: string): string;
|
|
45
|
+
export declare function normalizeMemoryNamespace(namespace?: string): string | undefined;
|
|
46
|
+
export declare function normalizeMemorySource(source?: string): string | undefined;
|
|
47
|
+
export declare function normalizeMemoryActor(actor: string): string;
|
|
48
|
+
export declare function normalizeMemoryTimestamp(ts: number): number;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { normalize } from '../tokenize.js';
|
|
2
|
+
import { normalizeMemoryLabel, validateMemoryLabels } from './label.js';
|
|
3
|
+
export function createMemoryId(input) {
|
|
4
|
+
const payload = [
|
|
5
|
+
normalizeMemoryKind(input.kind),
|
|
6
|
+
normalizeMemoryTextForId(input.text),
|
|
7
|
+
normalizeMemoryTimestamp(input.ts),
|
|
8
|
+
normalizeMemoryActor(input.actor),
|
|
9
|
+
].join('\u0001');
|
|
10
|
+
return `mem_${hash64Hex(payload)}`;
|
|
11
|
+
}
|
|
12
|
+
export function normalizeMemoryInput(input, provenance = {}) {
|
|
13
|
+
const kind = normalizeMemoryKind(input.kind);
|
|
14
|
+
if (!kind) {
|
|
15
|
+
throw new Error('Memory kind must be a non-empty string.');
|
|
16
|
+
}
|
|
17
|
+
const text = normalizeMemoryText(input.text);
|
|
18
|
+
if (!text) {
|
|
19
|
+
throw new Error('Memory text must be a non-empty string.');
|
|
20
|
+
}
|
|
21
|
+
const ts = normalizeMemoryTimestamp(input.ts ?? provenance.ts ?? 0);
|
|
22
|
+
const actor = normalizeMemoryActor(input.actor ?? provenance.actor ?? 'cortex');
|
|
23
|
+
const labels = validateMemoryLabels(input.labels);
|
|
24
|
+
const namespace = normalizeMemoryNamespace(input.namespace);
|
|
25
|
+
const source = normalizeMemorySource(input.source);
|
|
26
|
+
const importance = normalizeMemoryRatio(input.importance, 'importance');
|
|
27
|
+
const confidence = normalizeMemoryRatio(input.confidence, 'confidence');
|
|
28
|
+
return {
|
|
29
|
+
version: 1,
|
|
30
|
+
id: createMemoryId({ kind, text, ts, actor }),
|
|
31
|
+
kind,
|
|
32
|
+
text,
|
|
33
|
+
labels,
|
|
34
|
+
namespace,
|
|
35
|
+
source,
|
|
36
|
+
importance,
|
|
37
|
+
confidence,
|
|
38
|
+
ts,
|
|
39
|
+
actor,
|
|
40
|
+
links: [],
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export function normalizeMemoryKind(kind) {
|
|
44
|
+
return normalize(String(kind ?? '')).replace(/\s+/g, ' ').trim();
|
|
45
|
+
}
|
|
46
|
+
export function normalizeMemoryText(text) {
|
|
47
|
+
return String(text ?? '').trim();
|
|
48
|
+
}
|
|
49
|
+
export function normalizeMemoryNamespace(namespace) {
|
|
50
|
+
const normalized = normalizeMemoryLabel(namespace ?? '');
|
|
51
|
+
return normalized || undefined;
|
|
52
|
+
}
|
|
53
|
+
export function normalizeMemorySource(source) {
|
|
54
|
+
const normalized = normalize(String(source ?? '')).replace(/\s+/g, ' ').trim();
|
|
55
|
+
return normalized || undefined;
|
|
56
|
+
}
|
|
57
|
+
export function normalizeMemoryActor(actor) {
|
|
58
|
+
return String(actor ?? '').replace(/\s+/g, ' ').trim();
|
|
59
|
+
}
|
|
60
|
+
export function normalizeMemoryTimestamp(ts) {
|
|
61
|
+
const value = Number(ts);
|
|
62
|
+
if (!Number.isFinite(value)) {
|
|
63
|
+
throw new Error('Memory timestamp must be a finite number.');
|
|
64
|
+
}
|
|
65
|
+
return value;
|
|
66
|
+
}
|
|
67
|
+
function normalizeMemoryRatio(value, name) {
|
|
68
|
+
if (value === undefined || value === null)
|
|
69
|
+
return undefined;
|
|
70
|
+
if (!Number.isFinite(value)) {
|
|
71
|
+
throw new Error(`Memory ${name} must be a finite number.`);
|
|
72
|
+
}
|
|
73
|
+
if (value < 0)
|
|
74
|
+
return 0;
|
|
75
|
+
if (value > 1)
|
|
76
|
+
return 1;
|
|
77
|
+
return value;
|
|
78
|
+
}
|
|
79
|
+
function normalizeMemoryTextForId(text) {
|
|
80
|
+
return normalize(String(text ?? '')).replace(/\s+/g, ' ').trim();
|
|
81
|
+
}
|
|
82
|
+
function hash64Hex(input) {
|
|
83
|
+
let hash = 0xcbf29ce484222325n;
|
|
84
|
+
const prime = 0x100000001b3n;
|
|
85
|
+
for (const char of input) {
|
|
86
|
+
hash ^= BigInt(char.codePointAt(0) ?? 0);
|
|
87
|
+
hash = (hash * prime) & 0xffffffffffffffffn;
|
|
88
|
+
}
|
|
89
|
+
return hash.toString(16).padStart(16, '0');
|
|
90
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { computeEdgeId, computeNodeId, normalizeClaimLabel } from '../graph/claim_graph.js';
|
|
2
|
+
import { validateMemoryLabels } from './label.js';
|
|
3
|
+
export function memoryToClaimOps(memory) {
|
|
4
|
+
const nodeOps = new Map();
|
|
5
|
+
const edgeOps = new Map();
|
|
6
|
+
const memoryLabel = memoryNodeLabel(memory.id);
|
|
7
|
+
upsertNode(nodeOps, memoryLabel, memory.ts, memory.actor);
|
|
8
|
+
for (const label of validateMemoryLabels(memory.labels)) {
|
|
9
|
+
const entityLabel = normalizeClaimLabel(label);
|
|
10
|
+
if (!entityLabel)
|
|
11
|
+
continue;
|
|
12
|
+
upsertNode(nodeOps, entityLabel, memory.ts, memory.actor);
|
|
13
|
+
upsertEdge(edgeOps, memoryLabel, 'mentions', entityLabel, memory.ts, memory.actor);
|
|
14
|
+
}
|
|
15
|
+
for (const link of sortedLinks(memory.links)) {
|
|
16
|
+
const relation = normalizeClaimLabel(link.relation);
|
|
17
|
+
if (!relation)
|
|
18
|
+
continue;
|
|
19
|
+
const targetLabel = memoryNodeLabel(link.to);
|
|
20
|
+
upsertNode(nodeOps, targetLabel, link.ts, link.actor);
|
|
21
|
+
upsertEdge(edgeOps, memoryLabel, relation, targetLabel, link.ts, link.actor);
|
|
22
|
+
}
|
|
23
|
+
return [...nodeOps.values(), ...edgeOps.values()];
|
|
24
|
+
}
|
|
25
|
+
function memoryNodeLabel(id) {
|
|
26
|
+
return normalizeClaimLabel(`memory ${id}`);
|
|
27
|
+
}
|
|
28
|
+
function upsertNode(nodeOps, label, ts, actor) {
|
|
29
|
+
if (!label)
|
|
30
|
+
return;
|
|
31
|
+
const id = computeNodeId(label);
|
|
32
|
+
if (nodeOps.has(id))
|
|
33
|
+
return;
|
|
34
|
+
nodeOps.set(id, {
|
|
35
|
+
op: 'upsert_node',
|
|
36
|
+
id,
|
|
37
|
+
label,
|
|
38
|
+
ts,
|
|
39
|
+
actor,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
function upsertEdge(edgeOps, fromLabel, relation, toLabel, ts, actor) {
|
|
43
|
+
if (!fromLabel || !relation || !toLabel)
|
|
44
|
+
return;
|
|
45
|
+
const from = computeNodeId(fromLabel);
|
|
46
|
+
const to = computeNodeId(toLabel);
|
|
47
|
+
const id = computeEdgeId(from, relation, to);
|
|
48
|
+
if (edgeOps.has(id))
|
|
49
|
+
return;
|
|
50
|
+
edgeOps.set(id, {
|
|
51
|
+
op: 'add_edge',
|
|
52
|
+
from,
|
|
53
|
+
p: relation,
|
|
54
|
+
to,
|
|
55
|
+
ts,
|
|
56
|
+
actor,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
function sortedLinks(memoryLinks) {
|
|
60
|
+
return [...memoryLinks].sort((a, b) => a.ts - b.ts ||
|
|
61
|
+
a.actor.localeCompare(b.actor) ||
|
|
62
|
+
a.relation.localeCompare(b.relation) ||
|
|
63
|
+
a.to.localeCompare(b.to) ||
|
|
64
|
+
a.id.localeCompare(b.id));
|
|
65
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export { normalizeMemoryLabel, validateMemoryLabels, matchesMemoryLabels, normalizeMemoryLabelTokens, } from './label.js';
|
|
2
|
+
export type { MemoryKind, MemoryLinkV1, MemoryLinkV1 as MemoryLink, MemoryEngramV1, MemoryEngramV1 as MemoryEngram, MemoryInputV1, MemoryInputV1 as MemoryInput, MemoryProvenanceV1, MemoryProvenanceV1 as MemoryProvenance, } from './engram.js';
|
|
3
|
+
export { createMemoryId, normalizeMemoryInput, normalizeMemoryKind, normalizeMemoryText, normalizeMemoryNamespace, normalizeMemorySource, normalizeMemoryActor, normalizeMemoryTimestamp, } from './engram.js';
|
|
4
|
+
export type { MemoryLogV1, MemoryOpV1 } from './log.js';
|
|
5
|
+
export type { MemoryLogV1 as MemoryLog, MemoryOpV1 as MemoryOp, } from './log.js';
|
|
6
|
+
export { createMemoryLog, appendMemoryOp, mergeMemoryLogs, serializeMemoryLog, deserializeMemoryLog, applyMemoryLog, } from './log.js';
|
|
7
|
+
export type { CortexV1, CortexV1 as Cortex, CortexWriteResult, } from './cortex.js';
|
|
8
|
+
export { createCortex, remember, forget, labelMemory, linkMemories } from './cortex.js';
|
|
9
|
+
export type { RecallOptionsV1, RecallOptionsV1 as RecallOptions, MemoryRecallHitV1, MemoryRecallHitV1 as MemoryRecallHit, } from './recall.js';
|
|
10
|
+
export { recall } from './recall.js';
|
|
11
|
+
export type { ConsolidateMemoriesOptionsV1, ConsolidateMemoriesOptionsV1 as ConsolidateMemoriesOptions, } from './consolidate.js';
|
|
12
|
+
export { consolidateMemories } from './consolidate.js';
|
|
13
|
+
export { memoryToClaimOps } from './graph_adapter.js';
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { normalizeMemoryLabel, validateMemoryLabels, matchesMemoryLabels, normalizeMemoryLabelTokens, } from './label.js';
|
|
2
|
+
export { createMemoryId, normalizeMemoryInput, normalizeMemoryKind, normalizeMemoryText, normalizeMemoryNamespace, normalizeMemorySource, normalizeMemoryActor, normalizeMemoryTimestamp, } from './engram.js';
|
|
3
|
+
export { createMemoryLog, appendMemoryOp, mergeMemoryLogs, serializeMemoryLog, deserializeMemoryLog, applyMemoryLog, } from './log.js';
|
|
4
|
+
export { createCortex, remember, forget, labelMemory, linkMemories } from './cortex.js';
|
|
5
|
+
export { recall } from './recall.js';
|
|
6
|
+
export { consolidateMemories } from './consolidate.js';
|
|
7
|
+
export { memoryToClaimOps } from './graph_adapter.js';
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function normalizeMemoryLabel(label: string): string;
|
|
2
|
+
export declare function validateMemoryLabels(labels?: string | readonly string[] | null): string[];
|
|
3
|
+
export declare function matchesMemoryLabels(memoryLabels?: string[] | string | null, requestedLabels?: string | readonly string[] | null): boolean;
|
|
4
|
+
export declare function normalizeMemoryLabelTokens(label: string): string[];
|