@smyslenny/agent-memory 2.1.0 → 3.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.
@@ -577,144 +577,6 @@ function exportMemories(db, dirPath, opts) {
577
577
  return { exported, files };
578
578
  }
579
579
 
580
- // src/search/intent.ts
581
- var INTENT_PATTERNS = {
582
- factual: [
583
- // English
584
- /^(what|who|where|which|how much|how many)\b/i,
585
- /\b(name|address|number|password|config|setting)\b/i,
586
- // Chinese - questions about facts
587
- /是(什么|谁|哪|啥)/,
588
- /叫(什么|啥)/,
589
- /(名字|地址|号码|密码|配置|设置|账号|邮箱|链接|版本)/,
590
- /(多少|几个|哪个|哪些|哪里)/,
591
- // Chinese - lookup patterns
592
- /(查一下|找一下|看看|搜一下)/,
593
- /(.+)是什么$/
594
- ],
595
- temporal: [
596
- // English
597
- /^(when|what time|how long)\b/i,
598
- /\b(yesterday|today|tomorrow|last week|recently|ago|before|after)\b/i,
599
- /\b(first|latest|newest|oldest|previous|next)\b/i,
600
- // Chinese - time expressions
601
- /什么时候/,
602
- /(昨天|今天|明天|上周|下周|最近|以前|之前|之后|刚才|刚刚)/,
603
- /(几月|几号|几点|多久|多长时间)/,
604
- /(上次|下次|第一次|最后一次|那天|那时)/,
605
- // Date patterns
606
- /\d{4}[-/.]\d{1,2}/,
607
- /\d{1,2}月\d{1,2}[日号]/,
608
- // Chinese - temporal context
609
- /(历史|记录|日志|以来|至今|期间)/
610
- ],
611
- causal: [
612
- // English
613
- /^(why|how come|what caused)\b/i,
614
- /\b(because|due to|reason|cause|result)\b/i,
615
- // Chinese - causal questions
616
- /为(什么|啥|何)/,
617
- /(原因|导致|造成|引起|因为|所以|结果)/,
618
- /(怎么回事|怎么了|咋回事|咋了)/,
619
- /(为啥|凭啥|凭什么)/,
620
- // Chinese - problem/diagnosis
621
- /(出(了|了什么)?问题|报错|失败|出错|bug)/
622
- ],
623
- exploratory: [
624
- // English
625
- /^(how|tell me about|explain|describe|show me)\b/i,
626
- /^(what do you think|what about|any)\b/i,
627
- /\b(overview|summary|list|compare)\b/i,
628
- // Chinese - exploratory
629
- /(怎么样|怎样|如何)/,
630
- /(介绍|说说|讲讲|聊聊|谈谈)/,
631
- /(有哪些|有什么|有没有)/,
632
- /(关于|对于|至于|关联)/,
633
- /(总结|概括|梳理|回顾|盘点)/,
634
- // Chinese - opinion/analysis
635
- /(看法|想法|意见|建议|评价|感觉|觉得)/,
636
- /(对比|比较|区别|差异|优缺点)/
637
- ]
638
- };
639
- var CN_STRUCTURE_BOOSTS = {
640
- factual: [/^.{1,6}(是什么|叫什么|在哪)/, /^(谁|哪)/],
641
- temporal: [/^(什么时候|上次|最近)/, /(时间|日期)$/],
642
- causal: [/^(为什么|为啥)/, /(为什么|怎么回事)$/],
643
- exploratory: [/^(怎么|如何|说说)/, /(哪些|什么样)$/]
644
- };
645
- function classifyIntent(query) {
646
- const scores = {
647
- factual: 0,
648
- exploratory: 0,
649
- temporal: 0,
650
- causal: 0
651
- };
652
- for (const [intent, patterns] of Object.entries(INTENT_PATTERNS)) {
653
- for (const pattern of patterns) {
654
- if (pattern.test(query)) {
655
- scores[intent] += 1;
656
- }
657
- }
658
- }
659
- for (const [intent, patterns] of Object.entries(CN_STRUCTURE_BOOSTS)) {
660
- for (const pattern of patterns) {
661
- if (pattern.test(query)) {
662
- scores[intent] += 0.5;
663
- }
664
- }
665
- }
666
- const tokens = tokenize(query);
667
- const totalPatternScore = Object.values(scores).reduce((a, b) => a + b, 0);
668
- if (totalPatternScore === 0 && tokens.length <= 3) {
669
- scores.factual += 1;
670
- }
671
- let maxIntent = "factual";
672
- let maxScore = 0;
673
- for (const [intent, score] of Object.entries(scores)) {
674
- if (score > maxScore) {
675
- maxScore = score;
676
- maxIntent = intent;
677
- }
678
- }
679
- const totalScore = Object.values(scores).reduce((a, b) => a + b, 0);
680
- const confidence = totalScore > 0 ? Math.min(0.95, maxScore / totalScore) : 0.5;
681
- return { intent: maxIntent, confidence };
682
- }
683
- function getStrategy(intent) {
684
- switch (intent) {
685
- case "factual":
686
- return { boostRecent: false, boostPriority: true, limit: 5 };
687
- case "temporal":
688
- return { boostRecent: true, boostPriority: false, limit: 10 };
689
- case "causal":
690
- return { boostRecent: false, boostPriority: false, limit: 10 };
691
- case "exploratory":
692
- return { boostRecent: false, boostPriority: false, limit: 15 };
693
- }
694
- }
695
-
696
- // src/search/rerank.ts
697
- function rerank(results, opts) {
698
- const now2 = Date.now();
699
- const scored = results.map((r) => {
700
- let finalScore = r.score;
701
- if (opts.boostPriority) {
702
- const priorityMultiplier = [4, 3, 2, 1][r.memory.priority] ?? 1;
703
- finalScore *= priorityMultiplier;
704
- }
705
- if (opts.boostRecent && r.memory.updated_at) {
706
- const age = now2 - new Date(r.memory.updated_at).getTime();
707
- const daysSinceUpdate = age / (1e3 * 60 * 60 * 24);
708
- const recencyBoost = Math.max(0.1, 1 / (1 + daysSinceUpdate * 0.1));
709
- finalScore *= recencyBoost;
710
- }
711
- finalScore *= Math.max(0.1, r.memory.vitality);
712
- return { ...r, score: finalScore };
713
- });
714
- scored.sort((a, b) => b.score - a.score);
715
- return scored.slice(0, opts.limit);
716
- }
717
-
718
580
  // src/search/bm25.ts
719
581
  function searchBM25(db, query, opts) {
720
582
  const limit = opts?.limit ?? 20;
@@ -766,246 +628,6 @@ function buildFtsQuery(text) {
766
628
  return tokens.map((w) => `"${w}"`).join(" OR ");
767
629
  }
768
630
 
769
- // src/search/embeddings.ts
770
- function encodeEmbedding(vector) {
771
- const arr = vector instanceof Float32Array ? vector : Float32Array.from(vector);
772
- return Buffer.from(arr.buffer, arr.byteOffset, arr.byteLength);
773
- }
774
- function decodeEmbedding(buf) {
775
- const copy = Buffer.from(buf);
776
- return new Float32Array(copy.buffer, copy.byteOffset, Math.floor(copy.byteLength / 4));
777
- }
778
- function upsertEmbedding(db, input) {
779
- const ts = now();
780
- const vec = input.vector instanceof Float32Array ? input.vector : Float32Array.from(input.vector);
781
- const blob = encodeEmbedding(vec);
782
- db.prepare(
783
- `INSERT INTO embeddings (agent_id, memory_id, model, dim, vector, created_at, updated_at)
784
- VALUES (?, ?, ?, ?, ?, ?, ?)
785
- ON CONFLICT(agent_id, memory_id, model) DO UPDATE SET
786
- dim = excluded.dim,
787
- vector = excluded.vector,
788
- updated_at = excluded.updated_at`
789
- ).run(input.agent_id, input.memory_id, input.model, vec.length, blob, ts, ts);
790
- }
791
- function listEmbeddings(db, agent_id, model) {
792
- const rows = db.prepare(
793
- "SELECT memory_id, vector FROM embeddings WHERE agent_id = ? AND model = ?"
794
- ).all(agent_id, model);
795
- return rows.map((r) => ({ memory_id: r.memory_id, vector: decodeEmbedding(r.vector) }));
796
- }
797
-
798
- // src/search/hybrid.ts
799
- function cosine(a, b) {
800
- const n = Math.min(a.length, b.length);
801
- let dot = 0;
802
- let na = 0;
803
- let nb = 0;
804
- for (let i = 0; i < n; i++) {
805
- const x = a[i];
806
- const y = b[i];
807
- dot += x * y;
808
- na += x * x;
809
- nb += y * y;
810
- }
811
- if (na === 0 || nb === 0) return 0;
812
- return dot / (Math.sqrt(na) * Math.sqrt(nb));
813
- }
814
- function rrfScore(rank, k) {
815
- return 1 / (k + rank);
816
- }
817
- function fuseRrf(lists, k) {
818
- const out = /* @__PURE__ */ new Map();
819
- for (const list of lists) {
820
- for (let i = 0; i < list.items.length; i++) {
821
- const it = list.items[i];
822
- const rank = i + 1;
823
- const add = rrfScore(rank, k);
824
- const prev = out.get(it.id);
825
- if (!prev) out.set(it.id, { score: add, sources: [list.name] });
826
- else {
827
- prev.score += add;
828
- if (!prev.sources.includes(list.name)) prev.sources.push(list.name);
829
- }
830
- }
831
- }
832
- return out;
833
- }
834
- function fetchMemories(db, ids, agentId) {
835
- if (ids.length === 0) return [];
836
- const placeholders = ids.map(() => "?").join(", ");
837
- const sql = agentId ? `SELECT * FROM memories WHERE id IN (${placeholders}) AND agent_id = ?` : `SELECT * FROM memories WHERE id IN (${placeholders})`;
838
- const rows = db.prepare(sql).all(...agentId ? [...ids, agentId] : ids);
839
- return rows;
840
- }
841
- async function searchHybrid(db, query, opts) {
842
- const agentId = opts?.agent_id ?? "default";
843
- const limit = opts?.limit ?? 10;
844
- const bm25Mult = opts?.bm25CandidateMultiplier ?? 3;
845
- const semanticCandidates = opts?.semanticCandidates ?? 50;
846
- const rrfK = opts?.rrfK ?? 60;
847
- const bm25 = searchBM25(db, query, {
848
- agent_id: agentId,
849
- limit: limit * bm25Mult
850
- });
851
- const provider = opts?.embeddingProvider ?? null;
852
- const model = opts?.embeddingModel ?? provider?.model;
853
- if (!provider || !model) {
854
- return bm25.slice(0, limit);
855
- }
856
- const qVec = Float32Array.from(await provider.embed(query));
857
- const embeddings = listEmbeddings(db, agentId, model);
858
- const scored = [];
859
- for (const e of embeddings) {
860
- scored.push({ id: e.memory_id, score: cosine(qVec, e.vector) });
861
- }
862
- scored.sort((a, b) => b.score - a.score);
863
- const semanticTop = scored.slice(0, semanticCandidates);
864
- const fused = fuseRrf(
865
- [
866
- { name: "bm25", items: bm25.map((r) => ({ id: r.memory.id, score: r.score })) },
867
- { name: "semantic", items: semanticTop }
868
- ],
869
- rrfK
870
- );
871
- const ids = [...fused.keys()];
872
- const memories = fetchMemories(db, ids, agentId);
873
- const byId = new Map(memories.map((m) => [m.id, m]));
874
- const out = [];
875
- for (const [id, meta] of fused) {
876
- const mem = byId.get(id);
877
- if (!mem) continue;
878
- out.push({
879
- memory: mem,
880
- score: meta.score,
881
- matchReason: meta.sources.sort().join("+")
882
- });
883
- }
884
- out.sort((a, b) => b.score - a.score);
885
- return out.slice(0, limit);
886
- }
887
-
888
- // src/search/providers.ts
889
- function getEmbeddingProviderFromEnv() {
890
- const provider = (process.env.AGENT_MEMORY_EMBEDDINGS_PROVIDER ?? "none").toLowerCase();
891
- if (provider === "none" || provider === "off" || provider === "false") return null;
892
- if (provider === "openai") {
893
- const apiKey = process.env.OPENAI_API_KEY;
894
- const model = process.env.AGENT_MEMORY_EMBEDDINGS_MODEL ?? "text-embedding-3-small";
895
- const baseUrl = process.env.OPENAI_BASE_URL ?? "https://api.openai.com/v1";
896
- if (!apiKey) return null;
897
- return createOpenAIProvider({ apiKey, model, baseUrl });
898
- }
899
- if (provider === "qwen" || provider === "dashscope" || provider === "tongyi") {
900
- const apiKey = process.env.DASHSCOPE_API_KEY;
901
- const model = process.env.AGENT_MEMORY_EMBEDDINGS_MODEL ?? "text-embedding-v3";
902
- const baseUrl = process.env.DASHSCOPE_BASE_URL ?? "https://dashscope.aliyuncs.com";
903
- if (!apiKey) return null;
904
- return createDashScopeProvider({ apiKey, model, baseUrl });
905
- }
906
- return null;
907
- }
908
- function authHeader(apiKey) {
909
- return apiKey.startsWith("Bearer ") ? apiKey : `Bearer ${apiKey}`;
910
- }
911
- function normalizeEmbedding(e) {
912
- if (!Array.isArray(e)) throw new Error("Invalid embedding: not an array");
913
- if (e.length === 0) throw new Error("Invalid embedding: empty");
914
- return e.map((x) => {
915
- if (typeof x !== "number" || !Number.isFinite(x)) throw new Error("Invalid embedding: non-numeric value");
916
- return x;
917
- });
918
- }
919
- function createOpenAIProvider(opts) {
920
- const baseUrl = opts.baseUrl ?? "https://api.openai.com/v1";
921
- return {
922
- id: "openai",
923
- model: opts.model,
924
- async embed(text) {
925
- const resp = await fetch(`${baseUrl.replace(/\/$/, "")}/embeddings`, {
926
- method: "POST",
927
- headers: {
928
- "content-type": "application/json",
929
- authorization: authHeader(opts.apiKey)
930
- },
931
- body: JSON.stringify({ model: opts.model, input: text })
932
- });
933
- if (!resp.ok) {
934
- const body = await resp.text().catch(() => "");
935
- throw new Error(`OpenAI embeddings failed: ${resp.status} ${resp.statusText} ${body}`.trim());
936
- }
937
- const data = await resp.json();
938
- const emb = data.data?.[0]?.embedding;
939
- return normalizeEmbedding(emb);
940
- }
941
- };
942
- }
943
- function createDashScopeProvider(opts) {
944
- const baseUrl = opts.baseUrl ?? "https://dashscope.aliyuncs.com";
945
- return {
946
- id: "dashscope",
947
- model: opts.model,
948
- async embed(text) {
949
- const resp = await fetch(`${baseUrl.replace(/\/$/, "")}/api/v1/services/embeddings/text-embedding/text-embedding`, {
950
- method: "POST",
951
- headers: {
952
- "content-type": "application/json",
953
- authorization: authHeader(opts.apiKey)
954
- },
955
- body: JSON.stringify({
956
- model: opts.model,
957
- input: { texts: [text] }
958
- })
959
- });
960
- if (!resp.ok) {
961
- const body = await resp.text().catch(() => "");
962
- throw new Error(`DashScope embeddings failed: ${resp.status} ${resp.statusText} ${body}`.trim());
963
- }
964
- const data = await resp.json();
965
- const emb = data?.output?.embeddings?.[0]?.embedding ?? data?.output?.embeddings?.[0]?.vector ?? data?.output?.embedding ?? data?.data?.[0]?.embedding;
966
- return normalizeEmbedding(emb);
967
- }
968
- };
969
- }
970
-
971
- // src/search/embed.ts
972
- async function embedMemory(db, memoryId, provider, opts) {
973
- const row = db.prepare("SELECT id, agent_id, content FROM memories WHERE id = ?").get(memoryId);
974
- if (!row) return false;
975
- if (opts?.agent_id && row.agent_id !== opts.agent_id) return false;
976
- const model = opts?.model ?? provider.model;
977
- const maxChars = opts?.maxChars ?? 2e3;
978
- const text = row.content.length > maxChars ? row.content.slice(0, maxChars) : row.content;
979
- const vector = await provider.embed(text);
980
- upsertEmbedding(db, {
981
- agent_id: row.agent_id,
982
- memory_id: row.id,
983
- model,
984
- vector
985
- });
986
- return true;
987
- }
988
- async function embedMissingForAgent(db, provider, opts) {
989
- const agentId = opts?.agent_id ?? "default";
990
- const model = opts?.model ?? provider.model;
991
- const limit = opts?.limit ?? 1e3;
992
- const rows = db.prepare(
993
- `SELECT m.id
994
- FROM memories m
995
- LEFT JOIN embeddings e
996
- ON e.memory_id = m.id AND e.agent_id = m.agent_id AND e.model = ?
997
- WHERE m.agent_id = ? AND e.memory_id IS NULL
998
- ORDER BY m.updated_at DESC
999
- LIMIT ?`
1000
- ).all(model, agentId, limit);
1001
- let embedded = 0;
1002
- for (const r of rows) {
1003
- const ok = await embedMemory(db, r.id, provider, { agent_id: agentId, model, maxChars: opts?.maxChars });
1004
- if (ok) embedded++;
1005
- }
1006
- return { embedded, scanned: rows.length };
1007
- }
1008
-
1009
631
  // src/core/path.ts
1010
632
  var DEFAULT_DOMAINS = /* @__PURE__ */ new Set(["core", "emotion", "knowledge", "event", "system"]);
1011
633
  function parseUri(uri) {
@@ -1154,33 +776,15 @@ function getDecayedMemories(db, threshold = 0.05, opts) {
1154
776
  ).all(...agentId ? [threshold, agentId] : [threshold]);
1155
777
  }
1156
778
 
1157
- // src/core/snapshot.ts
1158
- function createSnapshot(db, memoryId, action, changedBy) {
1159
- const memory = db.prepare("SELECT content FROM memories WHERE id = ?").get(memoryId);
1160
- if (!memory) throw new Error(`Memory not found: ${memoryId}`);
1161
- const id = newId();
1162
- db.prepare(
1163
- `INSERT INTO snapshots (id, memory_id, content, changed_by, action, created_at)
1164
- VALUES (?, ?, ?, ?, ?, ?)`
1165
- ).run(id, memoryId, memory.content, changedBy ?? null, action, now());
1166
- return { id, memory_id: memoryId, content: memory.content, changed_by: changedBy ?? null, action, created_at: now() };
1167
- }
1168
-
1169
779
  // src/sleep/tidy.ts
1170
780
  function runTidy(db, opts) {
1171
781
  const threshold = opts?.vitalityThreshold ?? 0.05;
1172
- const maxSnapshots = opts?.maxSnapshotsPerMemory ?? 10;
1173
782
  const agentId = opts?.agent_id;
1174
783
  let archived = 0;
1175
784
  let orphansCleaned = 0;
1176
- let snapshotsPruned = 0;
1177
785
  const transaction = db.transaction(() => {
1178
786
  const decayed = getDecayedMemories(db, threshold, agentId ? { agent_id: agentId } : void 0);
1179
787
  for (const mem of decayed) {
1180
- try {
1181
- createSnapshot(db, mem.id, "delete", "tidy");
1182
- } catch {
1183
- }
1184
788
  deleteMemory(db, mem.id);
1185
789
  archived++;
1186
790
  }
@@ -1192,35 +796,15 @@ function runTidy(db, opts) {
1192
796
  "DELETE FROM paths WHERE memory_id NOT IN (SELECT id FROM memories)"
1193
797
  ).run();
1194
798
  orphansCleaned = orphans.changes;
1195
- const memoriesWithSnapshots = agentId ? db.prepare(
1196
- `SELECT s.memory_id, COUNT(*) as cnt
1197
- FROM snapshots s
1198
- JOIN memories m ON m.id = s.memory_id
1199
- WHERE m.agent_id = ?
1200
- GROUP BY s.memory_id HAVING cnt > ?`
1201
- ).all(agentId, maxSnapshots) : db.prepare(
1202
- `SELECT memory_id, COUNT(*) as cnt FROM snapshots
1203
- GROUP BY memory_id HAVING cnt > ?`
1204
- ).all(maxSnapshots);
1205
- for (const { memory_id } of memoriesWithSnapshots) {
1206
- const pruned = db.prepare(
1207
- `DELETE FROM snapshots WHERE id NOT IN (
1208
- SELECT id FROM snapshots WHERE memory_id = ?
1209
- ORDER BY created_at DESC LIMIT ?
1210
- ) AND memory_id = ?`
1211
- ).run(memory_id, maxSnapshots, memory_id);
1212
- snapshotsPruned += pruned.changes;
1213
- }
1214
799
  });
1215
800
  transaction();
1216
- return { archived, orphansCleaned, snapshotsPruned };
801
+ return { archived, orphansCleaned };
1217
802
  }
1218
803
 
1219
804
  // src/sleep/govern.ts
1220
805
  function runGovern(db, opts) {
1221
806
  const agentId = opts?.agent_id;
1222
807
  let orphanPaths = 0;
1223
- let orphanLinks = 0;
1224
808
  let emptyMemories = 0;
1225
809
  const transaction = db.transaction(() => {
1226
810
  const pathResult = agentId ? db.prepare(
@@ -1229,23 +813,11 @@ function runGovern(db, opts) {
1229
813
  AND memory_id NOT IN (SELECT id FROM memories WHERE agent_id = ?)`
1230
814
  ).run(agentId, agentId) : db.prepare("DELETE FROM paths WHERE memory_id NOT IN (SELECT id FROM memories)").run();
1231
815
  orphanPaths = pathResult.changes;
1232
- const linkResult = agentId ? db.prepare(
1233
- `DELETE FROM links WHERE
1234
- agent_id = ? AND (
1235
- source_id NOT IN (SELECT id FROM memories WHERE agent_id = ?) OR
1236
- target_id NOT IN (SELECT id FROM memories WHERE agent_id = ?)
1237
- )`
1238
- ).run(agentId, agentId, agentId) : db.prepare(
1239
- `DELETE FROM links WHERE
1240
- source_id NOT IN (SELECT id FROM memories) OR
1241
- target_id NOT IN (SELECT id FROM memories)`
1242
- ).run();
1243
- orphanLinks = linkResult.changes;
1244
816
  const emptyResult = agentId ? db.prepare("DELETE FROM memories WHERE agent_id = ? AND TRIM(content) = ''").run(agentId) : db.prepare("DELETE FROM memories WHERE TRIM(content) = ''").run();
1245
817
  emptyMemories = emptyResult.changes;
1246
818
  });
1247
819
  transaction();
1248
- return { orphanPaths, orphanLinks, emptyMemories };
820
+ return { orphanPaths, emptyMemories };
1249
821
  }
1250
822
 
1251
823
  // src/core/guard.ts
@@ -1375,7 +947,6 @@ function syncOne(db, input) {
1375
947
  }
1376
948
  case "update": {
1377
949
  if (!guardResult.existingId) return { action: "skipped", reason: "No existing ID for update" };
1378
- createSnapshot(db, guardResult.existingId, "update", "sync");
1379
950
  updateMemory(db, guardResult.existingId, { content: input.content });
1380
951
  return { action: "updated", memoryId: guardResult.existingId, reason: guardResult.reason };
1381
952
  }
@@ -1383,7 +954,6 @@ function syncOne(db, input) {
1383
954
  if (!guardResult.existingId || !guardResult.mergedContent) {
1384
955
  return { action: "skipped", reason: "Missing merge data" };
1385
956
  }
1386
- createSnapshot(db, guardResult.existingId, "merge", "sync");
1387
957
  updateMemory(db, guardResult.existingId, { content: guardResult.mergedContent });
1388
958
  return { action: "merged", memoryId: guardResult.existingId, reason: guardResult.reason };
1389
959
  }
@@ -1403,27 +973,26 @@ function getAgentId() {
1403
973
  }
1404
974
  function printHelp() {
1405
975
  console.log(`
1406
- \u{1F9E0} AgentMemory v2 \u2014 Sleep-cycle memory for AI agents
976
+ \u{1F9E0} AgentMemory v3 \u2014 Sleep-cycle memory for AI agents
1407
977
 
1408
978
  Usage: agent-memory <command> [options]
1409
979
 
1410
980
  Commands:
1411
- init Create database
1412
- db:migrate Run schema migrations (no-op if up-to-date)
1413
- embed [--limit N] Embed missing memories (requires provider)
981
+ init Create database
982
+ db:migrate Run schema migrations (no-op if up-to-date)
1414
983
  remember <content> [--uri X] [--type T] Store a memory
1415
- recall <query> [--limit N] Search memories
1416
- boot Load identity memories
1417
- status Show statistics
1418
- reflect [decay|tidy|govern|all] Run sleep cycle
1419
- reindex Rebuild FTS index with jieba tokenizer
1420
- migrate <dir> Import from Markdown files
1421
- export <dir> Export memories to Markdown files
1422
- help Show this help
984
+ recall <query> [--limit N] Search memories (BM25 + priority\xD7vitality)
985
+ boot Load identity memories
986
+ status Show statistics
987
+ reflect [decay|tidy|govern|all] Run sleep cycle
988
+ reindex Rebuild FTS index with jieba tokenizer
989
+ migrate <dir> Import from Markdown files
990
+ export <dir> Export memories to Markdown files
991
+ help Show this help
1423
992
 
1424
993
  Environment:
1425
- AGENT_MEMORY_DB Database path (default: ./agent-memory.db)
1426
- AGENT_MEMORY_AGENT_ID Agent ID (default: "default")
994
+ AGENT_MEMORY_DB Database path (default: ./agent-memory.db)
995
+ AGENT_MEMORY_AGENT_ID Agent ID (default: "default")
1427
996
  `);
1428
997
  }
1429
998
  function getFlag(flag) {
@@ -1448,20 +1017,6 @@ async function main() {
1448
1017
  db.close();
1449
1018
  break;
1450
1019
  }
1451
- case "embed": {
1452
- const provider = getEmbeddingProviderFromEnv();
1453
- if (!provider) {
1454
- console.error("Embedding provider not configured. Set AGENT_MEMORY_EMBEDDINGS_PROVIDER=openai|qwen and the corresponding API key.");
1455
- process.exit(1);
1456
- }
1457
- const db = openDatabase({ path: getDbPath() });
1458
- const agentId = getAgentId();
1459
- const limit = parseInt(getFlag("--limit") ?? "200");
1460
- const r = await embedMissingForAgent(db, provider, { agent_id: agentId, limit });
1461
- console.log(`\u2705 Embedded: ${r.embedded}/${r.scanned} (agent_id=${agentId}, model=${provider.model})`);
1462
- db.close();
1463
- break;
1464
- }
1465
1020
  case "remember": {
1466
1021
  const content = args.slice(1).filter((a) => !a.startsWith("--")).join(" ");
1467
1022
  if (!content) {
@@ -1472,14 +1027,7 @@ async function main() {
1472
1027
  const uri = getFlag("--uri");
1473
1028
  const type = getFlag("--type") ?? "knowledge";
1474
1029
  const agentId = getAgentId();
1475
- const provider = getEmbeddingProviderFromEnv();
1476
- const result = syncOne(db, { content, type, uri, agent_id: agentId });
1477
- if (provider && result.memoryId && (result.action === "added" || result.action === "updated" || result.action === "merged")) {
1478
- try {
1479
- await embedMemory(db, result.memoryId, provider, { agent_id: agentId });
1480
- } catch {
1481
- }
1482
- }
1030
+ const result = syncOne(db, { content, type, uri, source: "manual", agent_id: agentId });
1483
1031
  console.log(`${result.action}: ${result.reason}${result.memoryId ? ` (${result.memoryId.slice(0, 8)})` : ""}`);
1484
1032
  db.close();
1485
1033
  break;
@@ -1491,16 +1039,17 @@ async function main() {
1491
1039
  process.exit(1);
1492
1040
  }
1493
1041
  const db = openDatabase({ path: getDbPath() });
1494
- const limit = parseInt(getFlag("--limit") ?? "10");
1495
- const { intent } = classifyIntent(query);
1496
- const strategy = getStrategy(intent);
1042
+ const limit = parseInt(getFlag("--limit") ?? "10", 10);
1497
1043
  const agentId = getAgentId();
1498
- const provider = getEmbeddingProviderFromEnv();
1499
- const raw = await searchHybrid(db, query, { agent_id: agentId, embeddingProvider: provider, limit: limit * 2 });
1500
- const results = rerank(raw, { ...strategy, limit });
1501
- console.log(`\u{1F50D} Intent: ${intent} | Results: ${results.length}
1044
+ const raw = searchBM25(db, query, { agent_id: agentId, limit: Math.max(limit * 2, limit) });
1045
+ const weighted = raw.map((r) => {
1046
+ const weight = [4, 3, 2, 1][r.memory.priority] ?? 1;
1047
+ const vitality = Math.max(0.1, r.memory.vitality);
1048
+ return { ...r, score: r.score * weight * vitality };
1049
+ }).sort((a, b) => b.score - a.score).slice(0, limit);
1050
+ console.log(`\u{1F50D} Results: ${weighted.length}
1502
1051
  `);
1503
- for (const r of results) {
1052
+ for (const r of weighted) {
1504
1053
  const p = ["\u{1F534}", "\u{1F7E0}", "\u{1F7E1}", "\u26AA"][r.memory.priority];
1505
1054
  const v = (r.memory.vitality * 100).toFixed(0);
1506
1055
  console.log(`${p} P${r.memory.priority} [${v}%] ${r.memory.content.slice(0, 80)}`);
@@ -1529,17 +1078,11 @@ async function main() {
1529
1078
  const stats = countMemories(db, agentId);
1530
1079
  const lowVit = db.prepare("SELECT COUNT(*) as c FROM memories WHERE vitality < 0.1 AND agent_id = ?").get(agentId).c;
1531
1080
  const paths = db.prepare("SELECT COUNT(*) as c FROM paths WHERE agent_id = ?").get(agentId).c;
1532
- const links = db.prepare("SELECT COUNT(*) as c FROM links WHERE agent_id = ?").get(agentId).c;
1533
- const snaps = db.prepare(
1534
- `SELECT COUNT(*) as c FROM snapshots s
1535
- JOIN memories m ON m.id = s.memory_id
1536
- WHERE m.agent_id = ?`
1537
- ).get(agentId).c;
1538
1081
  console.log("\u{1F9E0} AgentMemory Status\n");
1539
1082
  console.log(` Total memories: ${stats.total}`);
1540
1083
  console.log(` By type: ${Object.entries(stats.by_type).map(([k, v]) => `${k}=${v}`).join(", ")}`);
1541
1084
  console.log(` By priority: ${Object.entries(stats.by_priority).map(([k, v]) => `${k}=${v}`).join(", ")}`);
1542
- console.log(` Paths: ${paths} | Links: ${links} | Snapshots: ${snaps}`);
1085
+ console.log(` Paths: ${paths}`);
1543
1086
  console.log(` Low vitality (<10%): ${lowVit}`);
1544
1087
  db.close();
1545
1088
  break;
@@ -1556,11 +1099,11 @@ async function main() {
1556
1099
  }
1557
1100
  if (phase === "tidy" || phase === "all") {
1558
1101
  const r = runTidy(db, { agent_id: agentId });
1559
- console.log(` Tidy: ${r.archived} archived, ${r.orphansCleaned} orphans, ${r.snapshotsPruned} snapshots pruned`);
1102
+ console.log(` Tidy: ${r.archived} archived, ${r.orphansCleaned} orphans`);
1560
1103
  }
1561
1104
  if (phase === "govern" || phase === "all") {
1562
1105
  const r = runGovern(db, { agent_id: agentId });
1563
- console.log(` Govern: ${r.orphanPaths} paths, ${r.orphanLinks} links, ${r.emptyMemories} empty cleaned`);
1106
+ console.log(` Govern: ${r.orphanPaths} paths, ${r.emptyMemories} empty cleaned`);
1564
1107
  }
1565
1108
  db.close();
1566
1109
  break;