@remind_ai/remind 0.1.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.
Files changed (79) hide show
  1. package/LICENSE +6 -0
  2. package/README.md +127 -0
  3. package/dist/src/brain/consolidation.d.ts +3 -0
  4. package/dist/src/brain/consolidation.js +153 -0
  5. package/dist/src/brain/constants.d.ts +18 -0
  6. package/dist/src/brain/constants.js +26 -0
  7. package/dist/src/brain/encoding.d.ts +10 -0
  8. package/dist/src/brain/encoding.js +45 -0
  9. package/dist/src/brain/forgetting.d.ts +3 -0
  10. package/dist/src/brain/forgetting.js +48 -0
  11. package/dist/src/brain/index.d.ts +6 -0
  12. package/dist/src/brain/index.js +13 -0
  13. package/dist/src/brain/llm-deps.d.ts +8 -0
  14. package/dist/src/brain/llm-deps.js +4 -0
  15. package/dist/src/brain/selftest.d.ts +1 -0
  16. package/dist/src/brain/selftest.js +229 -0
  17. package/dist/src/brain/similarity.d.ts +10 -0
  18. package/dist/src/brain/similarity.js +65 -0
  19. package/dist/src/brain/temporal.d.ts +9 -0
  20. package/dist/src/brain/temporal.js +65 -0
  21. package/dist/src/brain/tiering.d.ts +9 -0
  22. package/dist/src/brain/tiering.js +39 -0
  23. package/dist/src/config.d.ts +36 -0
  24. package/dist/src/config.js +61 -0
  25. package/dist/src/core/index.d.ts +12 -0
  26. package/dist/src/core/index.js +31 -0
  27. package/dist/src/core/ingest.d.ts +2 -0
  28. package/dist/src/core/ingest.js +117 -0
  29. package/dist/src/core/memory-types.d.ts +45 -0
  30. package/dist/src/core/memory-types.js +104 -0
  31. package/dist/src/core/retrieval.d.ts +2 -0
  32. package/dist/src/core/retrieval.js +55 -0
  33. package/dist/src/core/stores/doc.store.d.ts +2 -0
  34. package/dist/src/core/stores/doc.store.js +74 -0
  35. package/dist/src/core/stores/graph.store.d.ts +2 -0
  36. package/dist/src/core/stores/graph.store.js +61 -0
  37. package/dist/src/core/stores/vector.store.d.ts +2 -0
  38. package/dist/src/core/stores/vector.store.js +87 -0
  39. package/dist/src/engine.d.ts +33 -0
  40. package/dist/src/engine.js +63 -0
  41. package/dist/src/ids.d.ts +1 -0
  42. package/dist/src/ids.js +6 -0
  43. package/dist/src/index.d.ts +19 -0
  44. package/dist/src/index.js +25 -0
  45. package/dist/src/llm.d.ts +8 -0
  46. package/dist/src/llm.js +68 -0
  47. package/dist/src/memguard/auditor.d.ts +19 -0
  48. package/dist/src/memguard/auditor.js +48 -0
  49. package/dist/src/memguard/canary.d.ts +49 -0
  50. package/dist/src/memguard/canary.js +73 -0
  51. package/dist/src/memguard/detectors/exfiltration.d.ts +3 -0
  52. package/dist/src/memguard/detectors/exfiltration.js +18 -0
  53. package/dist/src/memguard/detectors/pii.d.ts +12 -0
  54. package/dist/src/memguard/detectors/pii.js +32 -0
  55. package/dist/src/memguard/detectors/secrets.d.ts +9 -0
  56. package/dist/src/memguard/detectors/secrets.js +25 -0
  57. package/dist/src/memguard/firewall.d.ts +15 -0
  58. package/dist/src/memguard/firewall.js +93 -0
  59. package/dist/src/memguard/index.d.ts +26 -0
  60. package/dist/src/memguard/index.js +24 -0
  61. package/dist/src/memguard/output-filter.d.ts +6 -0
  62. package/dist/src/memguard/output-filter.js +10 -0
  63. package/dist/src/memguard/provenance.d.ts +10 -0
  64. package/dist/src/memguard/provenance.js +36 -0
  65. package/dist/src/memguard/trust.d.ts +27 -0
  66. package/dist/src/memguard/trust.js +53 -0
  67. package/dist/src/queue/connection.d.ts +6 -0
  68. package/dist/src/queue/connection.js +23 -0
  69. package/dist/src/queue/queues.d.ts +29 -0
  70. package/dist/src/queue/queues.js +64 -0
  71. package/dist/src/types.d.ts +97 -0
  72. package/dist/src/types.js +3 -0
  73. package/dist/src/workers/consolidate.worker.d.ts +5 -0
  74. package/dist/src/workers/consolidate.worker.js +15 -0
  75. package/dist/src/workers/forget.worker.d.ts +4 -0
  76. package/dist/src/workers/forget.worker.js +8 -0
  77. package/dist/src/workers/ingest.worker.d.ts +6 -0
  78. package/dist/src/workers/ingest.worker.js +36 -0
  79. package/package.json +46 -0
@@ -0,0 +1,117 @@
1
+ // REMind · L0 ingest (write pipeline) · Owner: Bibhas
2
+ // draft(input): salience gate -> factual/episodic routing -> fact extraction OR (graph triples + denoised summary).
3
+ // Returns a draft MemoryRecord (does NOT persist). Ported from the POC addToMemory.
4
+ import { classify, complete } from '../llm.js';
5
+ import { idFor } from '../ids.js';
6
+ const now = () => new Date().toISOString();
7
+ export async function draft(input) {
8
+ const { user, assistant } = input.messages;
9
+ const source = input.source ?? 'conversation';
10
+ // 1. Salience / long-term gate.
11
+ const isLongTerm = await classify(`You decide whether a user/assistant exchange contains anything worth storing in LONG-TERM memory.
12
+ ` +
13
+ `Respond with a single word: 'yes' or 'no'.
14
+ ` +
15
+ `Examples:
16
+ ` +
17
+ `- { user: My birthday is on 27th June, assistant: excited for it } -> yes
18
+ ` +
19
+ `- { user: what's 2x3, assistant: 6 } -> no
20
+ ` +
21
+ `- { user: Pav Bhaji is my favorite food } -> yes
22
+ ` +
23
+ `Conversation: { user: ${user}, assistant: ${assistant} }
24
+ Answer only 'yes' or 'no'.`);
25
+ if (isLongTerm.startsWith('no'))
26
+ return null;
27
+ // 2. Factual vs episodic routing.
28
+ const isFactual = await classify(`Decide whether the exchange should be stored as FACTUAL memory (durable facts) vs EPISODIC (a time-stamped event).
29
+ ` +
30
+ `Respond 'yes' for factual, 'no' for episodic.
31
+ ` +
32
+ `Examples:
33
+ ` +
34
+ `- { user: My favorite color is blue } -> yes
35
+ ` +
36
+ `- { user: I went trekking in Manali last summer } -> no
37
+ ` +
38
+ `- { user: I work at Google as a backend developer } -> yes
39
+ ` +
40
+ `Conversation: { user: ${user}, assistant: ${assistant} }
41
+ Answer only 'yes' or 'no'.`);
42
+ if (isFactual.startsWith('yes')) {
43
+ // 3a. Fact extraction — denoise to a concise statement.
44
+ const fact = await complete(`Extract the durable fact as a short precise statement, no extra words.
45
+ ` +
46
+ `Examples:
47
+ ` +
48
+ `- { user: My favorite color is blue } -> User's favorite color is blue
49
+ ` +
50
+ `- { user: I work at Google as a backend developer } -> User is a backend developer at Google
51
+ ` +
52
+ `Conversation: { user: ${user}, assistant: ${assistant} }
53
+ Return only the fact string.`, { temperature: 0 });
54
+ return {
55
+ id: idFor(input.agentId, 'fact', fact),
56
+ agentId: input.agentId,
57
+ type: 'factual',
58
+ content: fact,
59
+ trust: 1,
60
+ confidence: 0.8,
61
+ provenance: { source },
62
+ tier: 'hot',
63
+ sourceId: input.sourceId,
64
+ createdAt: now(),
65
+ };
66
+ }
67
+ // 3b. Episodic — extract graph triples + a denoised vector summary.
68
+ const triples = await extractTriples(user, assistant);
69
+ const summary = await complete(`Summarize the episodic event into a single clean sentence for semantic storage, noise removed. Return only the sentence.
70
+ ` +
71
+ `Examples:
72
+ ` +
73
+ `- { user: I went trekking in Manali last summer } -> User traveled to Manali for trekking last summer.
74
+ ` +
75
+ `- { user: I watched Interstellar last weekend } -> User watched the movie Interstellar last weekend.
76
+ ` +
77
+ `Conversation: { user: ${user}, assistant: ${assistant} }`, { temperature: 0 });
78
+ return {
79
+ id: idFor(input.agentId, 'episodic', summary),
80
+ agentId: input.agentId,
81
+ type: 'episodic',
82
+ content: summary,
83
+ triples,
84
+ trust: 1,
85
+ confidence: 0.7,
86
+ provenance: { source },
87
+ tier: 'hot',
88
+ sourceId: input.sourceId,
89
+ createdAt: now(),
90
+ };
91
+ }
92
+ async function extractTriples(user, assistant) {
93
+ const raw = await complete(`Extract the episodic event as knowledge-graph triples.
94
+ ` +
95
+ `Return ONLY a JSON array of objects: [{"subject":"...","relation":"...","object":"..."}].
96
+ ` +
97
+ `Use "User" as the subject when the event is about the user. Keep names concise. No prose, no markdown.
98
+ ` +
99
+ `Examples:
100
+ ` +
101
+ `- { user: I went trekking in Manali last summer } -> [{"subject":"User","relation":"traveledTo","object":"Manali"}]
102
+ ` +
103
+ `- { user: I watched Interstellar last weekend } -> [{"subject":"User","relation":"watchedMovie","object":"Interstellar"}]
104
+ ` +
105
+ `Conversation: { user: ${user}, assistant: ${assistant} }`, { temperature: 0 });
106
+ try {
107
+ const cleaned = raw.replace(/^```(?:json)?/i, '').replace(/```$/, '').trim();
108
+ const parsed = JSON.parse(cleaned);
109
+ if (Array.isArray(parsed)) {
110
+ return parsed.filter((t) => t && t.subject && t.relation && t.object);
111
+ }
112
+ }
113
+ catch {
114
+ // ignore malformed JSON — episodic still stored as a vector summary.
115
+ }
116
+ return [];
117
+ }
@@ -0,0 +1,45 @@
1
+ import type { MemoryRecord, MemoryType, Tier } from '../types.js';
2
+ /** Behavioural defaults for one kind of memory. */
3
+ export interface MemoryTypeSpec {
4
+ /** Tier a brand-new record of this type lands in before consolidation. */
5
+ tier: Tier;
6
+ /** Default model confidence (0..1) when a writer does not supply one. */
7
+ confidence: number;
8
+ /** Durable kinds survive decay; ephemeral kinds are trimmed/expired fast. */
9
+ durable: boolean;
10
+ /** Soft retention budget in ms (Infinity = never auto-expire). decay() hint. */
11
+ ttlMs: number;
12
+ /** One-line human description for audits and debugging. */
13
+ description: string;
14
+ }
15
+ /** The taxonomy: every MemoryType in the frozen contract has exactly one spec. */
16
+ export declare const MEMORY_TYPES: Record<MemoryType, MemoryTypeSpec>;
17
+ /** Tier a fresh record of `type` should default to. */
18
+ export declare const defaultTier: (type: MemoryType) => Tier;
19
+ /** Default confidence for `type` when the writer has none. */
20
+ export declare const defaultConfidence: (type: MemoryType) => number;
21
+ /** Durable types survive decay; ephemeral ones (short/working) do not. */
22
+ export declare const isDurable: (type: MemoryType) => boolean;
23
+ /** Soft retention budget (ms) for `type`; Infinity means never auto-expire. */
24
+ export declare const retentionFor: (type: MemoryType) => number;
25
+ /** True once an ephemeral record has outlived its ttl (measured from last use). */
26
+ export declare function isExpired(record: MemoryRecord, at?: string): boolean;
27
+ /** Input for the lightweight (non-LLM) memory kinds. */
28
+ export interface ScratchInput {
29
+ agentId: string;
30
+ content: string;
31
+ source?: string;
32
+ sourceId?: string;
33
+ }
34
+ /** Recent-conversation memory (sliding window). Draft only — does not persist. */
35
+ export declare const draftShortTerm: (input: ScratchInput) => MemoryRecord;
36
+ /** Per-task scratchpad memory. Draft only — does not persist. */
37
+ export declare const draftWorking: (input: ScratchInput) => MemoryRecord;
38
+ /** Learned skill / routine memory. Draft only — does not persist. */
39
+ export declare const draftProcedural: (input: ScratchInput) => MemoryRecord;
40
+ /**
41
+ * Sliding short-term window: keep only the `limit` most recent short-term
42
+ * records, newest first. Older ones fall out of context (Brain.decay expires
43
+ * them once they pass their ttl).
44
+ */
45
+ export declare function windowShortTerm(records: MemoryRecord[], limit?: number): MemoryRecord[];
@@ -0,0 +1,104 @@
1
+ // REMind · L0 memory taxonomy · Owner: Bibhas (breadth)
2
+ // short-term window, working scratchpad, procedural skills + tier defaults.
3
+ //
4
+ // The frozen contract names five MemoryTypes; ingest.ts produces the two that
5
+ // need LLM extraction (factual, episodic). This module owns the other three
6
+ // lightweight kinds plus the per-type defaults the whole foundation reads from:
7
+ // it is the single source of truth for how a freshly drafted record of any type
8
+ // is tiered, trusted, and retained. The Brain layer's consolidate()/decay()
9
+ // consume these specs to promote durable memories and expire ephemeral ones.
10
+ import { idFor } from '../ids.js';
11
+ const now = () => new Date().toISOString();
12
+ const MINUTE = 60_000;
13
+ const HOUR = 60 * MINUTE;
14
+ const DAY = 24 * HOUR;
15
+ /** The taxonomy: every MemoryType in the frozen contract has exactly one spec. */
16
+ export const MEMORY_TYPES = {
17
+ factual: {
18
+ tier: 'hot',
19
+ confidence: 0.8,
20
+ durable: true,
21
+ ttlMs: Infinity,
22
+ description: 'Durable semantic fact about the user or the world.',
23
+ },
24
+ procedural: {
25
+ tier: 'warm',
26
+ confidence: 0.75,
27
+ durable: true,
28
+ ttlMs: Infinity,
29
+ description: 'A learned skill, routine, or how-to the agent can reuse.',
30
+ },
31
+ episodic: {
32
+ tier: 'hot',
33
+ confidence: 0.7,
34
+ durable: true,
35
+ ttlMs: 30 * DAY,
36
+ description: 'A time-stamped event; consolidates into facts/graph over time.',
37
+ },
38
+ short: {
39
+ tier: 'hot',
40
+ confidence: 0.5,
41
+ durable: false,
42
+ ttlMs: HOUR,
43
+ description: 'Recent conversation window; ephemeral context that decays fast.',
44
+ },
45
+ working: {
46
+ tier: 'hot',
47
+ confidence: 0.4,
48
+ durable: false,
49
+ ttlMs: 15 * MINUTE,
50
+ description: 'Active task scratchpad; discarded once the task completes.',
51
+ },
52
+ };
53
+ /** Tier a fresh record of `type` should default to. */
54
+ export const defaultTier = (type) => MEMORY_TYPES[type].tier;
55
+ /** Default confidence for `type` when the writer has none. */
56
+ export const defaultConfidence = (type) => MEMORY_TYPES[type].confidence;
57
+ /** Durable types survive decay; ephemeral ones (short/working) do not. */
58
+ export const isDurable = (type) => MEMORY_TYPES[type].durable;
59
+ /** Soft retention budget (ms) for `type`; Infinity means never auto-expire. */
60
+ export const retentionFor = (type) => MEMORY_TYPES[type].ttlMs;
61
+ /** True once an ephemeral record has outlived its ttl (measured from last use). */
62
+ export function isExpired(record, at = now()) {
63
+ const ttl = MEMORY_TYPES[record.type].ttlMs;
64
+ if (!Number.isFinite(ttl))
65
+ return false;
66
+ const since = Date.parse(record.lastUsedAt ?? record.createdAt);
67
+ const checkedAt = Date.parse(at);
68
+ if (Number.isNaN(since) || Number.isNaN(checkedAt))
69
+ return true;
70
+ return checkedAt - since > ttl;
71
+ }
72
+ /** Build a draft record of a lightweight kind. Deterministic id => idempotent persist. */
73
+ function build(type, input) {
74
+ const spec = MEMORY_TYPES[type];
75
+ return {
76
+ id: idFor(input.agentId, type, input.content),
77
+ agentId: input.agentId,
78
+ type,
79
+ content: input.content,
80
+ trust: 1,
81
+ confidence: spec.confidence,
82
+ provenance: { source: input.source ?? type },
83
+ tier: spec.tier,
84
+ sourceId: input.sourceId,
85
+ createdAt: now(),
86
+ };
87
+ }
88
+ /** Recent-conversation memory (sliding window). Draft only — does not persist. */
89
+ export const draftShortTerm = (input) => build('short', input);
90
+ /** Per-task scratchpad memory. Draft only — does not persist. */
91
+ export const draftWorking = (input) => build('working', input);
92
+ /** Learned skill / routine memory. Draft only — does not persist. */
93
+ export const draftProcedural = (input) => build('procedural', input);
94
+ /**
95
+ * Sliding short-term window: keep only the `limit` most recent short-term
96
+ * records, newest first. Older ones fall out of context (Brain.decay expires
97
+ * them once they pass their ttl).
98
+ */
99
+ export function windowShortTerm(records, limit = 10) {
100
+ return records
101
+ .filter((r) => r.type === 'short')
102
+ .sort((a, b) => Date.parse(b.createdAt) - Date.parse(a.createdAt))
103
+ .slice(0, limit);
104
+ }
@@ -0,0 +1,2 @@
1
+ import type { RetrievalResult, Stores } from '../types.js';
2
+ export declare function fetchMemory(stores: Stores, agentId: string, query: string, sourceId?: string): Promise<RetrievalResult>;
@@ -0,0 +1,55 @@
1
+ // REMind · L0 retrieval (read pipeline) · Owner: Bibhas
2
+ // query rewrite -> vector search -> better-chunks re-query -> frequency fusion -> graph filter -> context assembly.
3
+ // Ported from the POC fetchMemory + the retrieval half of createMessage.
4
+ import { complete } from '../llm.js';
5
+ export async function fetchMemory(stores, agentId, query, sourceId) {
6
+ // 1. Query rewriting — fix typos + enrich.
7
+ const refined = await complete(`Fix typos and add helpful context to this query so it retrieves better. Return only the rewritten query string.
8
+ Query: ${query}`);
9
+ // 2. First retrieval.
10
+ const first = await stores.vector.search(agentId, refined, 3, sourceId);
11
+ // 3. Better-chunks re-query.
12
+ const refined2 = await complete(`Given the user's query and the chunks retrieved, produce an improved retrieval query (same intent). Return only the query string.
13
+ ` +
14
+ `Original query: ${query}
15
+ Rewritten query: ${refined}
16
+ Chunks: ${JSON.stringify(first.map((r) => r.content))}`);
17
+ const second = await stores.vector.search(agentId, refined2, 3, sourceId);
18
+ // 4. Frequency fusion — chunks seen in both passes rank higher.
19
+ const priority = fuse([...first, ...second]).slice(0, 3);
20
+ // 5. Knowledge-graph relations, filtered to what's relevant.
21
+ const graphMap = await stores.graph.fetchGraph(agentId);
22
+ let relations = '';
23
+ if (graphMap.trim()) {
24
+ relations = await complete(`From the knowledge-graph relations below, keep only those relevant to the retrieved chunks. Do not add anything. Return plain text.
25
+ ` +
26
+ `Relations:
27
+ ${graphMap}
28
+ Relevant chunks: ${JSON.stringify(priority.map((r) => r.content))}`);
29
+ }
30
+ // 6. Durable facts + context assembly.
31
+ const facts = await stores.doc.getFacts(agentId);
32
+ const context = assemble(facts, relations, priority);
33
+ return { context, records: priority, facts };
34
+ }
35
+ function fuse(records) {
36
+ const freq = new Map();
37
+ records.forEach((r, i) => {
38
+ const key = r.id || r.content;
39
+ const e = freq.get(key);
40
+ if (e)
41
+ e.count++;
42
+ else
43
+ freq.set(key, { count: 1, first: i, rec: r });
44
+ });
45
+ return [...freq.values()]
46
+ .sort((a, b) => b.count - a.count || a.first - b.first)
47
+ .map((e) => e.rec);
48
+ }
49
+ function assemble(facts, relations, chunks) {
50
+ const factsText = facts.length ? facts.map((f) => `- ${f}`).join('\n') : 'None';
51
+ const chunkText = chunks.length ? chunks.map((c) => `- ${c.content}`).join('\n') : 'None';
52
+ return (`Factual context about the user:\n${factsText}\n\n` +
53
+ `Episodic relations:\n${relations.trim() || 'None'}\n\n` +
54
+ `Relevant memory chunks:\n${chunkText}`);
55
+ }
@@ -0,0 +1,2 @@
1
+ import type { DocStore } from '../../types.js';
2
+ export declare const docStore: DocStore;
@@ -0,0 +1,74 @@
1
+ // REMind · L0 document store · Owner: Bibhas
2
+ // Azure Cosmos DB for MongoDB (Mongoose drop-in). Holds facts + MemoryRecord metadata.
3
+ import mongoose, { Schema } from 'mongoose';
4
+ import { config } from '../../config.js';
5
+ let conn = null;
6
+ function connect() {
7
+ // `dbName` is the only option we set. mongoose's ConnectOptions extends the mongodb
8
+ // driver's TLS-derived options (Pick<tls.ConnectionOptions, ...>), which some TypeScript
9
+ // versions mis-evaluate as having required members; cast through unknown so the editor
10
+ // and the workspace compiler agree.
11
+ const options = { dbName: config.mongo.db };
12
+ if (!conn)
13
+ conn = mongoose.connect(config.mongo.uri, options);
14
+ return conn;
15
+ }
16
+ const RecordSchema = new Schema({
17
+ id: { type: String, unique: true, index: true },
18
+ agentId: { type: String, index: true },
19
+ type: String,
20
+ content: String,
21
+ trust: Number,
22
+ confidence: Number,
23
+ provenance: Schema.Types.Mixed,
24
+ validFrom: String,
25
+ validTo: String,
26
+ tier: String,
27
+ quarantined: Boolean,
28
+ sourceId: String,
29
+ createdAt: String,
30
+ lastUsedAt: String,
31
+ triples: Schema.Types.Mixed,
32
+ }, { versionKey: false });
33
+ const AgentSchema = new Schema({ agentId: { type: String, unique: true, index: true }, facts: [String] }, { versionKey: false });
34
+ const RecordModel = mongoose.models.MemoryRecord ?? mongoose.model('MemoryRecord', RecordSchema);
35
+ const AgentModel = mongoose.models.Agent ?? mongoose.model('Agent', AgentSchema);
36
+ function toRecord(d) {
37
+ return {
38
+ id: d.id,
39
+ agentId: d.agentId,
40
+ type: d.type,
41
+ content: d.content,
42
+ trust: d.trust ?? 1,
43
+ confidence: d.confidence ?? 0.5,
44
+ provenance: d.provenance ?? { source: 'doc' },
45
+ tier: d.tier ?? 'warm',
46
+ validFrom: d.validFrom,
47
+ validTo: d.validTo,
48
+ quarantined: d.quarantined,
49
+ sourceId: d.sourceId,
50
+ createdAt: d.createdAt ?? new Date().toISOString(),
51
+ lastUsedAt: d.lastUsedAt,
52
+ triples: d.triples,
53
+ };
54
+ }
55
+ export const docStore = {
56
+ async pushFact(agentId, fact) {
57
+ await connect();
58
+ await AgentModel.updateOne({ agentId }, { $addToSet: { facts: fact } }, { upsert: true });
59
+ },
60
+ async getFacts(agentId) {
61
+ await connect();
62
+ const a = await AgentModel.findOne({ agentId }).lean();
63
+ return a?.facts ?? [];
64
+ },
65
+ async saveRecord(record) {
66
+ await connect();
67
+ await RecordModel.updateOne({ id: record.id }, { $set: record }, { upsert: true });
68
+ },
69
+ async getRecords(agentId, filter = {}) {
70
+ await connect();
71
+ const docs = await RecordModel.find({ agentId, ...filter }).lean();
72
+ return docs.map(toRecord);
73
+ },
74
+ };
@@ -0,0 +1,2 @@
1
+ import type { GraphStore } from '../../types.js';
2
+ export declare const graphStore: GraphStore;
@@ -0,0 +1,61 @@
1
+ // REMind · L0 knowledge graph · Owner: Bibhas
2
+ // Azure Cosmos DB for Apache Gremlin (replaces Neo4j). Deterministic, idempotent triple upserts.
3
+ import gremlin from 'gremlin';
4
+ import { config } from '../../config.js';
5
+ // gremlin ships partial types; treat the driver namespace loosely.
6
+ const G = gremlin;
7
+ let client;
8
+ function getClient() {
9
+ if (client)
10
+ return client;
11
+ const authenticator = new G.driver.auth.PlainTextSaslAuthenticator(`/dbs/${config.gremlin.database}/colls/${config.gremlin.graph}`, config.gremlin.key);
12
+ client = new G.driver.Client(config.gremlin.endpoint, {
13
+ authenticator,
14
+ traversalsource: 'g',
15
+ rejectUnauthorized: true,
16
+ mimeType: 'application/vnd.gremlin-v2.0+json',
17
+ });
18
+ return client;
19
+ }
20
+ /** Gremlin string-literal escaping. */
21
+ const esc = (s) => s.replace(/'/g, "\\'");
22
+ async function ensureVertex(name, agentId) {
23
+ const n = esc(name);
24
+ const a = esc(agentId);
25
+ // partitionKey = agentId so every vertex is tenant-scoped (Cosmos requirement).
26
+ await getClient().submit(`g.V().has('Entity','name','${n}').has('agentId','${a}').fold()` +
27
+ `.coalesce(unfold(),addV('Entity').property('name','${n}').property('agentId','${a}').property('partitionKey','${a}'))`);
28
+ }
29
+ export const graphStore = {
30
+ async writeTriples(agentId, triples) {
31
+ for (const t of triples) {
32
+ if (!t.subject || !t.relation || !t.object)
33
+ continue;
34
+ await ensureVertex(t.subject, agentId);
35
+ await ensureVertex(t.object, agentId);
36
+ const s = esc(t.subject);
37
+ const o = esc(t.object);
38
+ const rel = esc(t.relation);
39
+ const a = esc(agentId);
40
+ await getClient().submit(`g.V().has('Entity','name','${s}').has('agentId','${a}').as('s')` +
41
+ `.V().has('Entity','name','${o}').has('agentId','${a}')` +
42
+ `.coalesce(__.inE('${rel}').where(outV().as('s')), addE('${rel}').from('s'))`);
43
+ }
44
+ },
45
+ async fetchGraph(agentId) {
46
+ const a = esc(agentId);
47
+ // outE/inV (not bothE/otherV) so each directed edge is emitted exactly once,
48
+ // oriented subject -> object — matching the `-[e]->` rendering below.
49
+ const res = await getClient().submit(`g.V().has('agentId','${a}').as('v').outE().as('e').inV().as('o').select('v','e','o')`);
50
+ const rows = res?._items ?? (typeof res?.toArray === 'function' ? res.toArray() : res) ?? [];
51
+ let map = '';
52
+ for (const row of rows) {
53
+ const v = row?.v?.properties?.name?.[0]?.value ?? row?.v?.label;
54
+ const e = row?.e?.label;
55
+ const o = row?.o?.properties?.name?.[0]?.value ?? row?.o?.label;
56
+ if (v && e && o)
57
+ map += `(${v}) -[${e}]-> (${o})\n`;
58
+ }
59
+ return map;
60
+ },
61
+ };
@@ -0,0 +1,2 @@
1
+ import type { VectorStore } from '../../types.js';
2
+ export declare const vectorStore: VectorStore;
@@ -0,0 +1,87 @@
1
+ // REMind · L0 vector store · Owner: Bibhas
2
+ // Azure AI Search (native vector search, replaces Qdrant). Per-agent filtering + idempotent upsert.
3
+ import { AzureKeyCredential, SearchClient, SearchIndexClient, } from '@azure/search-documents';
4
+ import { config } from '../../config.js';
5
+ import { embed } from '../../llm.js';
6
+ const credential = new AzureKeyCredential(config.search.apiKey);
7
+ const searchClient = new SearchClient(config.search.endpoint, config.search.index, credential);
8
+ const indexClient = new SearchIndexClient(config.search.endpoint, credential);
9
+ /** OData string-literal escaping. */
10
+ const esc = (s) => s.replace(/'/g, "''");
11
+ let ensured = false;
12
+ async function ensureIndex() {
13
+ if (ensured)
14
+ return;
15
+ const index = {
16
+ name: config.search.index,
17
+ fields: [
18
+ { name: 'id', type: 'Edm.String', key: true, filterable: true },
19
+ { name: 'agentId', type: 'Edm.String', filterable: true },
20
+ { name: 'type', type: 'Edm.String', filterable: true },
21
+ { name: 'sourceId', type: 'Edm.String', filterable: true },
22
+ { name: 'content', type: 'Edm.String', searchable: true },
23
+ { name: 'createdAt', type: 'Edm.String', filterable: true, sortable: true },
24
+ {
25
+ name: 'embedding',
26
+ type: 'Collection(Edm.Single)',
27
+ searchable: true,
28
+ vectorSearchDimensions: config.azureOpenAI.embedDimensions,
29
+ vectorSearchProfileName: 'remind-hnsw-profile',
30
+ },
31
+ ],
32
+ vectorSearch: {
33
+ algorithms: [{ name: 'remind-hnsw', kind: 'hnsw' }],
34
+ profiles: [{ name: 'remind-hnsw-profile', algorithmConfigurationName: 'remind-hnsw' }],
35
+ },
36
+ };
37
+ await indexClient.createOrUpdateIndex(index);
38
+ ensured = true;
39
+ }
40
+ export const vectorStore = {
41
+ async upsert(record) {
42
+ await ensureIndex();
43
+ const embedding = record.embedding ?? (await embed(record.content));
44
+ await searchClient.mergeOrUploadDocuments([
45
+ {
46
+ id: record.id,
47
+ agentId: record.agentId,
48
+ type: record.type,
49
+ content: record.content,
50
+ sourceId: record.sourceId,
51
+ createdAt: record.createdAt,
52
+ embedding,
53
+ },
54
+ ]);
55
+ },
56
+ async search(agentId, query, k = 3, sourceId) {
57
+ await ensureIndex();
58
+ const vector = await embed(query);
59
+ const filters = [`agentId eq '${esc(agentId)}'`];
60
+ if (sourceId)
61
+ filters.push(`sourceId eq '${esc(sourceId)}'`);
62
+ const results = await searchClient.search('*', {
63
+ vectorSearchOptions: {
64
+ queries: [{ kind: 'vector', vector, kNearestNeighborsCount: k, fields: ['embedding'] }],
65
+ },
66
+ filter: filters.join(' and '),
67
+ top: k,
68
+ });
69
+ const out = [];
70
+ for await (const r of results.results) {
71
+ const d = r.document;
72
+ out.push({
73
+ id: d.id,
74
+ agentId: d.agentId,
75
+ type: d.type,
76
+ content: d.content,
77
+ sourceId: d.sourceId,
78
+ createdAt: d.createdAt,
79
+ trust: 1,
80
+ confidence: 0.7,
81
+ provenance: { source: 'vector' },
82
+ tier: 'warm',
83
+ });
84
+ }
85
+ return out;
86
+ },
87
+ };
@@ -0,0 +1,33 @@
1
+ import type { Brain, ConsolidationReport, IngestInput, MemGuardHooks, MemoryEngineCore, RetrievalResult, Stores } from './types.js';
2
+ import { type Queues } from './queue/queues.js';
3
+ /** The three injected layers plus their shared stores — the composition root's payload. */
4
+ export interface Layers {
5
+ core: MemoryEngineCore;
6
+ brain: Brain;
7
+ guard: MemGuardHooks;
8
+ stores: Stores;
9
+ }
10
+ export interface MemoryAPI {
11
+ addToMemory(input: IngestInput): Promise<void>;
12
+ fetchMemory(agentId: string, query: string, sourceId?: string): Promise<RetrievalResult>;
13
+ sleep(agentId: string): Promise<ConsolidationReport>;
14
+ forget(agentId: string): Promise<void>;
15
+ close(): Promise<void>;
16
+ }
17
+ /** A no-op MemGuard: allow everything, never quarantine, never report drift ("MemGuard OFF"). */
18
+ export declare const passthroughGuard: MemGuardHooks;
19
+ export declare class MemoryEngine implements MemoryAPI {
20
+ private readonly layers;
21
+ private readonly queues;
22
+ constructor(layers: Layers, queues?: Queues);
23
+ /** Write path: enqueue ingest (inline in SYNC mode, async + retried via Redis otherwise). */
24
+ addToMemory(input: IngestInput): Promise<void>;
25
+ /** Read path (direct): retrieve, then run the output firewall (guard.onOutput). */
26
+ fetchMemory(agentId: string, query: string, sourceId?: string): Promise<RetrievalResult>;
27
+ /** Sleep cycle: run inline and return the report in SYNC mode, else enqueue. */
28
+ sleep(agentId: string): Promise<ConsolidationReport>;
29
+ /** Decay pass: inline in SYNC mode, else enqueue. */
30
+ forget(agentId: string): Promise<void>;
31
+ /** Release queue / worker / Redis resources. */
32
+ close(): Promise<void>;
33
+ }
@@ -0,0 +1,63 @@
1
+ // REMind · Orchestrator (producer) · Owner: Yasho
2
+ // MemoryEngine composes Foundation + Brain + MemGuard via dependency injection and owns the
3
+ // queues. Writes enqueue (crash-safe + async via Redis; inline in SYNC mode); reads are direct
4
+ // (core.fetchMemory + guard.onOutput); sleep() runs or enqueues a consolidation cycle.
5
+ import { config } from './config.js';
6
+ import { createQueues } from './queue/queues.js';
7
+ import { closeConnection } from './queue/connection.js';
8
+ import { registerIngestWorker } from './workers/ingest.worker.js';
9
+ import { registerConsolidateWorker, runConsolidate } from './workers/consolidate.worker.js';
10
+ import { registerForgetWorker, runForget } from './workers/forget.worker.js';
11
+ /** A no-op MemGuard: allow everything, never quarantine, never report drift ("MemGuard OFF"). */
12
+ export const passthroughGuard = {
13
+ onIngest: async (_input, record) => ({ allow: true, record }),
14
+ onConsolidate: async () => ({ promote: true }),
15
+ onOutput: async (records) => records,
16
+ checkCanaries: async () => ({ drift: false, details: 'MemGuard disabled' }),
17
+ };
18
+ export class MemoryEngine {
19
+ layers;
20
+ queues;
21
+ constructor(layers, queues) {
22
+ this.layers = layers;
23
+ this.queues = queues ?? createQueues();
24
+ registerIngestWorker(this.queues, this.layers);
25
+ registerConsolidateWorker(this.queues, this.layers);
26
+ registerForgetWorker(this.queues, this.layers);
27
+ }
28
+ /** Write path: enqueue ingest (inline in SYNC mode, async + retried via Redis otherwise). */
29
+ async addToMemory(input) {
30
+ await this.queues.ingest.add({ input });
31
+ }
32
+ /** Read path (direct): retrieve, then run the output firewall (guard.onOutput). */
33
+ async fetchMemory(agentId, query, sourceId) {
34
+ const result = await this.layers.core.fetchMemory(agentId, query, sourceId);
35
+ const records = await this.layers.guard.onOutput(result.records);
36
+ return { ...result, records };
37
+ }
38
+ /** Sleep cycle: run inline and return the report in SYNC mode, else enqueue. */
39
+ async sleep(agentId) {
40
+ if (config.sync)
41
+ return runConsolidate(this.layers, agentId);
42
+ await this.queues.consolidate.add({ agentId });
43
+ return { promoted: [], blocked: [], merged: 0 };
44
+ }
45
+ /** Decay pass: inline in SYNC mode, else enqueue. */
46
+ async forget(agentId) {
47
+ if (config.sync) {
48
+ await runForget(this.layers, agentId);
49
+ return;
50
+ }
51
+ await this.queues.forget.add({ agentId });
52
+ }
53
+ /** Release queue / worker / Redis resources. */
54
+ async close() {
55
+ await Promise.all([
56
+ this.queues.ingest.close(),
57
+ this.queues.consolidate.close(),
58
+ this.queues.forget.close(),
59
+ this.queues.quarantine.close(),
60
+ ]);
61
+ await closeConnection(); // no-op in inline/SYNC mode (no connection was opened)
62
+ }
63
+ }
@@ -0,0 +1 @@
1
+ export declare function idFor(agentId: string, kind: string, content: string): string;