claude-memory-layer 1.0.22 → 1.0.24

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 (51) hide show
  1. package/.claude/settings.local.json +11 -0
  2. package/README.md +2 -0
  3. package/dist/cli/index.js +87 -17
  4. package/dist/cli/index.js.map +2 -2
  5. package/dist/core/index.js +30 -5
  6. package/dist/core/index.js.map +2 -2
  7. package/dist/hooks/post-tool-use.js +117 -18
  8. package/dist/hooks/post-tool-use.js.map +2 -2
  9. package/dist/hooks/semantic-daemon.js +7337 -0
  10. package/dist/hooks/semantic-daemon.js.map +7 -0
  11. package/dist/hooks/session-end.js +71 -16
  12. package/dist/hooks/session-end.js.map +2 -2
  13. package/dist/hooks/session-start.js +156 -24
  14. package/dist/hooks/session-start.js.map +4 -4
  15. package/dist/hooks/stop.js +101 -18
  16. package/dist/hooks/stop.js.map +2 -2
  17. package/dist/hooks/user-prompt-submit.js +291 -102
  18. package/dist/hooks/user-prompt-submit.js.map +4 -4
  19. package/dist/server/api/index.js +71 -16
  20. package/dist/server/api/index.js.map +2 -2
  21. package/dist/server/index.js +71 -16
  22. package/dist/server/index.js.map +2 -2
  23. package/dist/services/memory-service.js +71 -16
  24. package/dist/services/memory-service.js.map +2 -2
  25. package/dist/ui/app.js +48 -1
  26. package/dist/ui/index.html +11 -3
  27. package/memory/_index.md +1 -0
  28. package/memory/agent_response/uncategorized/2026-03-04.md +1138 -1
  29. package/memory/session_summary/uncategorized/2026-03-04.md +31 -0
  30. package/memory/tool_observation/uncategorized/2026-03-04.md +785 -1
  31. package/memory/user_prompt/uncategorized/2026-03-04.md +438 -1
  32. package/package.json +1 -1
  33. package/scripts/build.ts +2 -1
  34. package/specs/selective-tool-observation/context.md +100 -0
  35. package/specs/selective-tool-observation/plan.md +158 -0
  36. package/specs/selective-tool-observation/spec.md +127 -0
  37. package/src/cli/index.ts +1 -0
  38. package/src/core/embedder.ts +15 -4
  39. package/src/core/sqlite-event-store.ts +16 -0
  40. package/src/core/turn-state.ts +48 -0
  41. package/src/core/types.ts +1 -0
  42. package/src/hooks/post-tool-use.ts +47 -2
  43. package/src/hooks/semantic-daemon-client.ts +208 -0
  44. package/src/hooks/semantic-daemon.ts +276 -0
  45. package/src/hooks/session-start.ts +7 -0
  46. package/src/hooks/stop.ts +19 -4
  47. package/src/hooks/user-prompt-submit.ts +48 -40
  48. package/src/services/memory-service.ts +59 -16
  49. package/src/services/session-history-importer.ts +18 -0
  50. package/src/ui/app.js +48 -1
  51. package/src/ui/index.html +11 -3
@@ -1838,6 +1838,21 @@ var SQLiteEventStore = class {
1838
1838
  [id, eventId, sessionId, score, query.slice(0, 100)]
1839
1839
  );
1840
1840
  }
1841
+ /**
1842
+ * Get session IDs that have unevaluated retrievals (measured_at IS NULL).
1843
+ * Excludes the current session. Used to backfill sessions that ended without Stop hook.
1844
+ */
1845
+ async getUnevaluatedSessions(currentSessionId, limit = 5) {
1846
+ await this.initialize();
1847
+ const rows = sqliteAll(
1848
+ this.db,
1849
+ `SELECT DISTINCT session_id FROM memory_helpfulness
1850
+ WHERE measured_at IS NULL AND session_id != ?
1851
+ ORDER BY created_at DESC LIMIT ?`,
1852
+ [currentSessionId, limit]
1853
+ );
1854
+ return rows.map((r) => r.session_id);
1855
+ }
1841
1856
  /**
1842
1857
  * Evaluate helpfulness for all retrievals in a session
1843
1858
  * Called at session end - uses behavioral signals to compute score
@@ -2625,7 +2640,7 @@ var VectorStore = class {
2625
2640
 
2626
2641
  // src/core/embedder.ts
2627
2642
  import { pipeline } from "@huggingface/transformers";
2628
- var Embedder = class {
2643
+ var Embedder = class _Embedder {
2629
2644
  pipeline = null;
2630
2645
  modelName;
2631
2646
  activeModelName;
@@ -2656,6 +2671,11 @@ var Embedder = class {
2656
2671
  this.initialized = true;
2657
2672
  }
2658
2673
  }
2674
+ // ~4 chars per token; 512 tokens * 4 = 2048, use 2000 to be safe
2675
+ static MAX_CHARS = 2e3;
2676
+ truncate(text) {
2677
+ return text.length > _Embedder.MAX_CHARS ? text.slice(0, _Embedder.MAX_CHARS) : text;
2678
+ }
2659
2679
  /**
2660
2680
  * Generate embedding for a single text
2661
2681
  */
@@ -2664,9 +2684,11 @@ var Embedder = class {
2664
2684
  if (!this.pipeline) {
2665
2685
  throw new Error("Embedding pipeline not initialized");
2666
2686
  }
2667
- const output = await this.pipeline(text, {
2687
+ const output = await this.pipeline(this.truncate(text), {
2668
2688
  pooling: "mean",
2669
- normalize: true
2689
+ normalize: true,
2690
+ truncation: true,
2691
+ max_length: 512
2670
2692
  });
2671
2693
  const vector = Array.from(output.data);
2672
2694
  return {
@@ -2688,9 +2710,11 @@ var Embedder = class {
2688
2710
  for (let i = 0; i < texts.length; i += batchSize) {
2689
2711
  const batch = texts.slice(i, i + batchSize);
2690
2712
  for (const text of batch) {
2691
- const output = await this.pipeline(text, {
2713
+ const output = await this.pipeline(this.truncate(text), {
2692
2714
  pooling: "mean",
2693
- normalize: true
2715
+ normalize: true,
2716
+ truncation: true,
2717
+ max_length: 512
2694
2718
  });
2695
2719
  const vector = Array.from(output.data);
2696
2720
  results.push({
@@ -5960,6 +5984,7 @@ var MemoryService = class {
5960
5984
  projectPath = null;
5961
5985
  readOnly;
5962
5986
  lightweightMode;
5987
+ embeddingOnly;
5963
5988
  mdMirror;
5964
5989
  storagePath;
5965
5990
  constructor(config) {
@@ -5967,6 +5992,7 @@ var MemoryService = class {
5967
5992
  this.storagePath = storagePath;
5968
5993
  this.readOnly = config.readOnly ?? false;
5969
5994
  this.lightweightMode = config.lightweightMode ?? false;
5995
+ this.embeddingOnly = config.embeddingOnly ?? false;
5970
5996
  this.mdMirror = new MarkdownMirror2(process.cwd());
5971
5997
  if (!this.readOnly && !fs4.existsSync(storagePath)) {
5972
5998
  fs4.mkdirSync(storagePath, { recursive: true });
@@ -6040,19 +6066,21 @@ var MemoryService = class {
6040
6066
  this.embedder
6041
6067
  );
6042
6068
  this.vectorWorker.start();
6043
- this.retriever.setGraduationPipeline(this.graduation);
6044
- this.graduationWorker = createGraduationWorker(
6045
- this.sqliteStore,
6046
- this.graduation
6047
- );
6048
- this.graduationWorker.start();
6049
- if (this.analyticsStore) {
6050
- this.syncWorker = new SyncWorker(
6069
+ if (!this.embeddingOnly) {
6070
+ this.retriever.setGraduationPipeline(this.graduation);
6071
+ this.graduationWorker = createGraduationWorker(
6051
6072
  this.sqliteStore,
6052
- this.analyticsStore,
6053
- { intervalMs: 3e4, batchSize: 500 }
6073
+ this.graduation
6054
6074
  );
6055
- this.syncWorker.start();
6075
+ this.graduationWorker.start();
6076
+ if (this.analyticsStore) {
6077
+ this.syncWorker = new SyncWorker(
6078
+ this.sqliteStore,
6079
+ this.analyticsStore,
6080
+ { intervalMs: 3e4, batchSize: 500 }
6081
+ );
6082
+ this.syncWorker.start();
6083
+ }
6056
6084
  }
6057
6085
  const savedMode = await this.sqliteStore.getEndlessConfig("mode");
6058
6086
  if (savedMode === "endless") {
@@ -6776,6 +6804,19 @@ var MemoryService = class {
6776
6804
  await this.initialize();
6777
6805
  await this.sqliteStore.recordRetrieval(eventId, sessionId, score, query);
6778
6806
  }
6807
+ /**
6808
+ * Record a query-level retrieval trace (used by user-prompt-submit hook).
6809
+ * Feeds the retrieval_traces table that powers dashboard stats.
6810
+ */
6811
+ async recordQueryTrace(input) {
6812
+ await this.initialize();
6813
+ await this.sqliteStore.recordRetrievalTrace({
6814
+ ...input,
6815
+ candidateDetails: [],
6816
+ selectedDetails: [],
6817
+ fallbackTrace: []
6818
+ });
6819
+ }
6779
6820
  /**
6780
6821
  * Evaluate helpfulness of retrievals in a session (called at session end)
6781
6822
  */
@@ -6783,6 +6824,20 @@ var MemoryService = class {
6783
6824
  await this.initialize();
6784
6825
  await this.sqliteStore.evaluateSessionHelpfulness(sessionId);
6785
6826
  }
6827
+ /**
6828
+ * Backfill helpfulness evaluation for sessions that ended without Stop hook.
6829
+ * Call on first turn of a new session to catch missed evaluations.
6830
+ */
6831
+ async evaluatePendingSessions(currentSessionId) {
6832
+ await this.initialize();
6833
+ const sessions = await this.sqliteStore.getUnevaluatedSessions(currentSessionId, 5);
6834
+ for (const sid of sessions) {
6835
+ try {
6836
+ await this.sqliteStore.evaluateSessionHelpfulness(sid);
6837
+ } catch {
6838
+ }
6839
+ }
6840
+ }
6786
6841
  /**
6787
6842
  * Get most helpful memories ranked by helpfulness score
6788
6843
  */
@@ -7277,6 +7332,24 @@ function clearTurnState(sessionId) {
7277
7332
  }
7278
7333
  }
7279
7334
  }
7335
+ var LAST_RESPONSE_SNIPPET_CHARS = 500;
7336
+ function getLastResponsePath(sessionId) {
7337
+ return path4.join(TURN_STATE_DIR, `.last-response-${sessionId}.json`);
7338
+ }
7339
+ function writeLastAssistantSnippet(sessionId, text) {
7340
+ try {
7341
+ if (!fs5.existsSync(TURN_STATE_DIR)) {
7342
+ fs5.mkdirSync(TURN_STATE_DIR, { recursive: true });
7343
+ }
7344
+ const snippet = text.slice(0, LAST_RESPONSE_SNIPPET_CHARS);
7345
+ const state = { sessionId, snippet, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
7346
+ const filePath = getLastResponsePath(sessionId);
7347
+ const tempPath = filePath + ".tmp";
7348
+ fs5.writeFileSync(tempPath, JSON.stringify(state));
7349
+ fs5.renameSync(tempPath, filePath);
7350
+ } catch {
7351
+ }
7352
+ }
7280
7353
 
7281
7354
  // src/hooks/stop.ts
7282
7355
  var DEFAULT_PRIVACY_CONFIG = {
@@ -7324,13 +7397,19 @@ async function main() {
7324
7397
  try {
7325
7398
  const turnId = readTurnState(input.session_id);
7326
7399
  const assistantMessages = await extractAssistantMessages(input.transcript_path);
7327
- for (const text of assistantMessages) {
7400
+ const MIN_AGENT_RESPONSE_LEN = parseInt(
7401
+ process.env.CLAUDE_MEMORY_AGENT_RESPONSE_MIN_LEN || "150"
7402
+ );
7403
+ const lastIdx = assistantMessages.length - 1;
7404
+ for (let i = 0; i < assistantMessages.length; i++) {
7405
+ const text = assistantMessages[i];
7406
+ const isLast = i === lastIdx;
7328
7407
  const filterResult = applyPrivacyFilter(text, DEFAULT_PRIVACY_CONFIG);
7329
7408
  let content = filterResult.content;
7330
7409
  if (content.length > 5e3) {
7331
7410
  content = content.slice(0, 5e3) + "...[truncated]";
7332
7411
  }
7333
- if (content.trim().length < 10)
7412
+ if (!isLast && content.trim().length < MIN_AGENT_RESPONSE_LEN)
7334
7413
  continue;
7335
7414
  await memoryService.storeAgentResponse(
7336
7415
  input.session_id,
@@ -7341,6 +7420,10 @@ async function main() {
7341
7420
  }
7342
7421
  );
7343
7422
  }
7423
+ if (assistantMessages.length > 0) {
7424
+ const lastMessage = assistantMessages[assistantMessages.length - 1];
7425
+ writeLastAssistantSnippet(input.session_id, lastMessage);
7426
+ }
7344
7427
  clearTurnState(input.session_id);
7345
7428
  await memoryService.processPendingEmbeddings();
7346
7429
  console.log(JSON.stringify({}));