memorix 1.7.1 → 1.7.2

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/CHANGELOG.md CHANGED
@@ -4,6 +4,22 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [1.7.2] - 2026-08-19
8
+
9
+ ### Changed
10
+ - **Faster large-store cold retrieval** -- startup hydration now inserts
11
+ persisted observations into Orama in bounded batches. This avoids rebuilding
12
+ index balance once per row while continuing to yield to HTTP health traffic.
13
+ - **Fail-closed large-store evidence** -- the reproducible 40,000-record gate
14
+ now exercises and times HTTP readiness, MCP initialize/bind/context/search/
15
+ store, hook capture, and SDK reopen behavior with hard request and release
16
+ budgets instead of checking persistence alone.
17
+ - **Isolated index tests** -- startup hydration tests explicitly disable the
18
+ embedding lane and restore the caller environment instead of reading an
19
+ operator's provider configuration or local vector cache.
20
+ - **Current public guidance** -- README and canonical development/status docs
21
+ now describe preview-first Dashboard maintenance and the 1.7 product line.
22
+
7
23
  ## [1.7.1] - 2026-08-19
8
24
 
9
25
  ### Fixed
package/README.md CHANGED
@@ -80,7 +80,7 @@ Memorix is more than a memory store. It also installs agent integrations, keeps
80
80
  | Agent setup | One setup path for MCP, rules, hooks, skills, plugins, bundles, or extensions depending on the agent | `memorix setup --agent <agent>` |
81
81
  | Agent doctor | Checks whether agent MCP config and guidance are current, then repairs Memorix-owned entries when needed | `memorix doctor agents`, `memorix repair agents` |
82
82
  | Hooks and skills | Optional capture from supported agents, plus reusable project skills promoted from durable knowledge | `memorix hooks`, `memorix skills` |
83
- | Dashboard and HTTP | A local web UI and shared MCP endpoint for browsing memory, project state, teams, and diagnostics | `memorix dashboard`, `memorix background start` |
83
+ | Dashboard and HTTP | A local web UI and shared MCP endpoint for browsing memory, project state, teams, diagnostics, and preview-first cleanup, consolidation, deduplication, and retention actions | `memorix dashboard`, `memorix background start` |
84
84
  | Orchestration and team work | Task planning, worker handoffs, file locks, messages, verification gates, and review loops | `memorix orchestrate`, `memorix team`, `memorix lock` |
85
85
  | memcode | A bundled terminal coding agent that already reads and writes the same project memory | `memorix`, `memcode` |
86
86
  | CLI and SDK | Scriptable access for automation, imports/exports, diagnostics, and custom integrations | `memorix ...`, `createMemoryClient()` |
package/README.zh-CN.md CHANGED
@@ -79,7 +79,7 @@ Memorix 不只是一个记忆库。它还负责安装 Agent 接入、保留有
79
79
  | Agent setup | 按目标 Agent 写入 MCP、rules、hooks、skills、plugin、bundle 或 extension | `memorix setup --agent <agent>` |
80
80
  | Agent doctor | 检查 Agent 的 MCP 配置和规则是否是当前版本,并修复 Memorix 自己管理的条目 | `memorix doctor agents`、`memorix repair agents` |
81
81
  | Hooks 和 skills | 在支持的 Agent 中可选捕获工作事件,并把稳定知识提升成可复用项目技能 | `memorix hooks`、`memorix skills` |
82
- | Dashboard 和 HTTP | 本地 Web UI 与共享 MCP endpoint,用于浏览记忆、项目状态、团队和诊断信息 | `memorix dashboard`、`memorix background start` |
82
+ | Dashboard 和 HTTP | 本地 Web UI 与共享 MCP endpoint,用于浏览记忆、项目状态、团队和诊断,并在预览确认后执行清理、合并、去重和保留归档 | `memorix dashboard`、`memorix background start` |
83
83
  | Orchestration 和团队协作 | 任务规划、worker 交接、文件锁、消息、验证门和 review loop | `memorix orchestrate`、`memorix team`、`memorix lock` |
84
84
  | memcode | 内置终端 Coding Agent,默认读写同一套项目记忆 | `memorix`、`memcode` |
85
85
  | CLI 和 SDK | 给自动化、导入导出、诊断和自定义集成使用的本地接口 | `memorix ...`、`createMemoryClient()` |
package/dist/cli/index.js CHANGED
@@ -3232,7 +3232,7 @@ ${import_picocolors.default.gray(m3)} ${s4}
3232
3232
 
3233
3233
  // src/cli/version.ts
3234
3234
  function getCliVersion() {
3235
- return true ? "1.7.1" : pkg.version;
3235
+ return true ? "1.7.2" : pkg.version;
3236
3236
  }
3237
3237
  var init_version = __esm({
3238
3238
  "src/cli/version.ts"() {
@@ -185335,7 +185335,7 @@ async function hydrateIndex(observations2, options = {}) {
185335
185335
  }
185336
185336
  const deferCachedVectors = options.deferCachedVectors === true && Boolean(indexEmbeddingProvider?.getCachedEmbeddings);
185337
185337
  const cachedVectors = deferCachedVectors ? candidates.map(() => null) : await getCachedEmbeddings(candidates.map(({ observation }) => observationEmbeddingText(observation)));
185338
- let inserted = 0;
185338
+ const documents = [];
185339
185339
  for (let index = 0; index < candidates.length; index++) {
185340
185340
  const { observation: obs, id } = candidates[index];
185341
185341
  try {
@@ -185369,17 +185369,34 @@ async function hydrateIndex(observations2, options = {}) {
185369
185369
  knowledgeLayer: resolveKnowledgeLayer("observation", obs.sourceDetail, obs.source),
185370
185370
  ...compatibleVector ? { embedding: compatibleVector } : {}
185371
185371
  };
185372
- await insert3(database, doc);
185373
- rememberObservationDoc(doc);
185374
- inserted++;
185375
- if (inserted % 50 === 0) {
185376
- await new Promise((resolve6) => setImmediate(resolve6));
185377
- }
185378
- if (inserted % 200 === 0) {
185379
- await new Promise((resolve6) => setTimeout(resolve6, 0));
185380
- }
185372
+ documents.push(doc);
185373
+ } catch {
185374
+ }
185375
+ }
185376
+ let inserted = 0;
185377
+ const hydrationBatchSize = 200;
185378
+ for (let start = 0; start < documents.length; start += hydrationBatchSize) {
185379
+ const batch = documents.slice(start, start + hydrationBatchSize);
185380
+ try {
185381
+ await insertMultiple(database, batch, batch.length);
185382
+ for (const doc of batch) rememberObservationDoc(doc);
185383
+ inserted += batch.length;
185381
185384
  } catch {
185385
+ for (const doc of batch) {
185386
+ if (getByID(database, doc.id)) {
185387
+ rememberObservationDoc(doc);
185388
+ inserted++;
185389
+ continue;
185390
+ }
185391
+ try {
185392
+ await insert3(database, doc);
185393
+ rememberObservationDoc(doc);
185394
+ inserted++;
185395
+ } catch {
185396
+ }
185397
+ }
185382
185398
  }
185399
+ await new Promise((resolve6) => setImmediate(resolve6));
185383
185400
  }
185384
185401
  deferredCachedVectorHydration = options.skipCachedVectors ? null : deferCachedVectors ? attachCachedVectors(database, candidates).catch(() => {
185385
185402
  }) : null;
@@ -260203,7 +260220,7 @@ The path should point to a directory containing a .git folder.`
260203
260220
  };
260204
260221
  const server = existingServer ?? new McpServer({
260205
260222
  name: "memorix",
260206
- version: true ? "1.7.1" : "1.0.1"
260223
+ version: true ? "1.7.2" : "1.0.1"
260207
260224
  });
260208
260225
  const originalRegisterTool = server.registerTool.bind(server);
260209
260226
  server.registerTool = ((name, ...args) => {
package/dist/index.js CHANGED
@@ -6888,7 +6888,7 @@ __export(orama_store_exports, {
6888
6888
  searchObservations: () => searchObservations,
6889
6889
  updateObservationMetadata: () => updateObservationMetadata
6890
6890
  });
6891
- import { create, insert as insert2, search, remove as remove2, update, count, getByID } from "@orama/orama";
6891
+ import { create, insert as insert2, insertMultiple, search, remove as remove2, update, count, getByID } from "@orama/orama";
6892
6892
  function getLastSearchMode(projectId) {
6893
6893
  return lastSearchModeByProject.get(projectId ?? SEARCH_MODE_DEFAULT_KEY) ?? "fulltext";
6894
6894
  }
@@ -7100,7 +7100,7 @@ async function hydrateIndex(observations2, options = {}) {
7100
7100
  }
7101
7101
  const deferCachedVectors = options.deferCachedVectors === true && Boolean(indexEmbeddingProvider?.getCachedEmbeddings);
7102
7102
  const cachedVectors = deferCachedVectors ? candidates.map(() => null) : await getCachedEmbeddings(candidates.map(({ observation }) => observationEmbeddingText(observation)));
7103
- let inserted = 0;
7103
+ const documents = [];
7104
7104
  for (let index = 0; index < candidates.length; index++) {
7105
7105
  const { observation: obs, id } = candidates[index];
7106
7106
  try {
@@ -7134,17 +7134,34 @@ async function hydrateIndex(observations2, options = {}) {
7134
7134
  knowledgeLayer: resolveKnowledgeLayer("observation", obs.sourceDetail, obs.source),
7135
7135
  ...compatibleVector ? { embedding: compatibleVector } : {}
7136
7136
  };
7137
- await insert2(database, doc);
7138
- rememberObservationDoc(doc);
7139
- inserted++;
7140
- if (inserted % 50 === 0) {
7141
- await new Promise((resolve2) => setImmediate(resolve2));
7142
- }
7143
- if (inserted % 200 === 0) {
7144
- await new Promise((resolve2) => setTimeout(resolve2, 0));
7145
- }
7137
+ documents.push(doc);
7138
+ } catch {
7139
+ }
7140
+ }
7141
+ let inserted = 0;
7142
+ const hydrationBatchSize = 200;
7143
+ for (let start = 0; start < documents.length; start += hydrationBatchSize) {
7144
+ const batch = documents.slice(start, start + hydrationBatchSize);
7145
+ try {
7146
+ await insertMultiple(database, batch, batch.length);
7147
+ for (const doc of batch) rememberObservationDoc(doc);
7148
+ inserted += batch.length;
7146
7149
  } catch {
7150
+ for (const doc of batch) {
7151
+ if (getByID(database, doc.id)) {
7152
+ rememberObservationDoc(doc);
7153
+ inserted++;
7154
+ continue;
7155
+ }
7156
+ try {
7157
+ await insert2(database, doc);
7158
+ rememberObservationDoc(doc);
7159
+ inserted++;
7160
+ } catch {
7161
+ }
7162
+ }
7147
7163
  }
7164
+ await new Promise((resolve2) => setImmediate(resolve2));
7148
7165
  }
7149
7166
  deferredCachedVectorHydration = options.skipCachedVectors ? null : deferCachedVectors ? attachCachedVectors(database, candidates).catch(() => {
7150
7167
  }) : null;
@@ -167285,7 +167302,7 @@ The path should point to a directory containing a .git folder.`
167285
167302
  };
167286
167303
  const server = existingServer ?? new McpServer({
167287
167304
  name: "memorix",
167288
- version: true ? "1.7.1" : "1.0.1"
167305
+ version: true ? "1.7.2" : "1.0.1"
167289
167306
  });
167290
167307
  const originalRegisterTool = server.registerTool.bind(server);
167291
167308
  server.registerTool = ((name, ...args) => {
@@ -171725,7 +171742,7 @@ var init_serve_shared = __esm({
171725
171742
 
171726
171743
  // src/cli/version.ts
171727
171744
  function getCliVersion() {
171728
- return true ? "1.7.1" : pkg.version;
171745
+ return true ? "1.7.2" : pkg.version;
171729
171746
  }
171730
171747
  var init_version3 = __esm({
171731
171748
  "src/cli/version.ts"() {
@@ -6094,7 +6094,7 @@ __export(orama_store_exports, {
6094
6094
  searchObservations: () => searchObservations,
6095
6095
  updateObservationMetadata: () => updateObservationMetadata
6096
6096
  });
6097
- import { create, insert as insert2, search, remove as remove2, update, count, getByID } from "@orama/orama";
6097
+ import { create, insert as insert2, insertMultiple, search, remove as remove2, update, count, getByID } from "@orama/orama";
6098
6098
  function getLastSearchMode(projectId) {
6099
6099
  return lastSearchModeByProject.get(projectId ?? SEARCH_MODE_DEFAULT_KEY) ?? "fulltext";
6100
6100
  }
@@ -6306,7 +6306,7 @@ async function hydrateIndex(observations2, options = {}) {
6306
6306
  }
6307
6307
  const deferCachedVectors = options.deferCachedVectors === true && Boolean(indexEmbeddingProvider?.getCachedEmbeddings);
6308
6308
  const cachedVectors = deferCachedVectors ? candidates.map(() => null) : await getCachedEmbeddings(candidates.map(({ observation }) => observationEmbeddingText(observation)));
6309
- let inserted = 0;
6309
+ const documents = [];
6310
6310
  for (let index = 0; index < candidates.length; index++) {
6311
6311
  const { observation: obs, id } = candidates[index];
6312
6312
  try {
@@ -6340,17 +6340,34 @@ async function hydrateIndex(observations2, options = {}) {
6340
6340
  knowledgeLayer: resolveKnowledgeLayer("observation", obs.sourceDetail, obs.source),
6341
6341
  ...compatibleVector ? { embedding: compatibleVector } : {}
6342
6342
  };
6343
- await insert2(database, doc);
6344
- rememberObservationDoc(doc);
6345
- inserted++;
6346
- if (inserted % 50 === 0) {
6347
- await new Promise((resolve) => setImmediate(resolve));
6348
- }
6349
- if (inserted % 200 === 0) {
6350
- await new Promise((resolve) => setTimeout(resolve, 0));
6351
- }
6343
+ documents.push(doc);
6344
+ } catch {
6345
+ }
6346
+ }
6347
+ let inserted = 0;
6348
+ const hydrationBatchSize = 200;
6349
+ for (let start = 0; start < documents.length; start += hydrationBatchSize) {
6350
+ const batch = documents.slice(start, start + hydrationBatchSize);
6351
+ try {
6352
+ await insertMultiple(database, batch, batch.length);
6353
+ for (const doc of batch) rememberObservationDoc(doc);
6354
+ inserted += batch.length;
6352
6355
  } catch {
6356
+ for (const doc of batch) {
6357
+ if (getByID(database, doc.id)) {
6358
+ rememberObservationDoc(doc);
6359
+ inserted++;
6360
+ continue;
6361
+ }
6362
+ try {
6363
+ await insert2(database, doc);
6364
+ rememberObservationDoc(doc);
6365
+ inserted++;
6366
+ } catch {
6367
+ }
6368
+ }
6353
6369
  }
6370
+ await new Promise((resolve) => setImmediate(resolve));
6354
6371
  }
6355
6372
  deferredCachedVectorHydration = options.skipCachedVectors ? null : deferCachedVectors ? attachCachedVectors(database, candidates).catch(() => {
6356
6373
  }) : null;
@@ -6580,7 +6580,7 @@ __export(orama_store_exports, {
6580
6580
  searchObservations: () => searchObservations,
6581
6581
  updateObservationMetadata: () => updateObservationMetadata
6582
6582
  });
6583
- import { create, insert as insert2, search, remove as remove2, update, count, getByID } from "@orama/orama";
6583
+ import { create, insert as insert2, insertMultiple, search, remove as remove2, update, count, getByID } from "@orama/orama";
6584
6584
  function getLastSearchMode(projectId) {
6585
6585
  return lastSearchModeByProject.get(projectId ?? SEARCH_MODE_DEFAULT_KEY) ?? "fulltext";
6586
6586
  }
@@ -6792,7 +6792,7 @@ async function hydrateIndex(observations2, options = {}) {
6792
6792
  }
6793
6793
  const deferCachedVectors = options.deferCachedVectors === true && Boolean(indexEmbeddingProvider?.getCachedEmbeddings);
6794
6794
  const cachedVectors = deferCachedVectors ? candidates.map(() => null) : await getCachedEmbeddings(candidates.map(({ observation }) => observationEmbeddingText(observation)));
6795
- let inserted = 0;
6795
+ const documents = [];
6796
6796
  for (let index = 0; index < candidates.length; index++) {
6797
6797
  const { observation: obs, id } = candidates[index];
6798
6798
  try {
@@ -6826,17 +6826,34 @@ async function hydrateIndex(observations2, options = {}) {
6826
6826
  knowledgeLayer: resolveKnowledgeLayer("observation", obs.sourceDetail, obs.source),
6827
6827
  ...compatibleVector ? { embedding: compatibleVector } : {}
6828
6828
  };
6829
- await insert2(database, doc);
6830
- rememberObservationDoc(doc);
6831
- inserted++;
6832
- if (inserted % 50 === 0) {
6833
- await new Promise((resolve) => setImmediate(resolve));
6834
- }
6835
- if (inserted % 200 === 0) {
6836
- await new Promise((resolve) => setTimeout(resolve, 0));
6837
- }
6829
+ documents.push(doc);
6830
+ } catch {
6831
+ }
6832
+ }
6833
+ let inserted = 0;
6834
+ const hydrationBatchSize = 200;
6835
+ for (let start = 0; start < documents.length; start += hydrationBatchSize) {
6836
+ const batch = documents.slice(start, start + hydrationBatchSize);
6837
+ try {
6838
+ await insertMultiple(database, batch, batch.length);
6839
+ for (const doc of batch) rememberObservationDoc(doc);
6840
+ inserted += batch.length;
6838
6841
  } catch {
6842
+ for (const doc of batch) {
6843
+ if (getByID(database, doc.id)) {
6844
+ rememberObservationDoc(doc);
6845
+ inserted++;
6846
+ continue;
6847
+ }
6848
+ try {
6849
+ await insert2(database, doc);
6850
+ rememberObservationDoc(doc);
6851
+ inserted++;
6852
+ } catch {
6853
+ }
6854
+ }
6839
6855
  }
6856
+ await new Promise((resolve) => setImmediate(resolve));
6840
6857
  }
6841
6858
  deferredCachedVectorHydration = options.skipCachedVectors ? null : deferCachedVectors ? attachCachedVectors(database, candidates).catch(() => {
6842
6859
  }) : null;
@@ -5597,7 +5597,7 @@ __export(orama_store_exports, {
5597
5597
  searchObservations: () => searchObservations,
5598
5598
  updateObservationMetadata: () => updateObservationMetadata
5599
5599
  });
5600
- import { create, insert as insert2, search, remove as remove2, update, count, getByID } from "@orama/orama";
5600
+ import { create, insert as insert2, insertMultiple, search, remove as remove2, update, count, getByID } from "@orama/orama";
5601
5601
  function getLastSearchMode(projectId) {
5602
5602
  return lastSearchModeByProject.get(projectId ?? SEARCH_MODE_DEFAULT_KEY) ?? "fulltext";
5603
5603
  }
@@ -5809,7 +5809,7 @@ async function hydrateIndex(observations2, options = {}) {
5809
5809
  }
5810
5810
  const deferCachedVectors = options.deferCachedVectors === true && Boolean(indexEmbeddingProvider?.getCachedEmbeddings);
5811
5811
  const cachedVectors = deferCachedVectors ? candidates.map(() => null) : await getCachedEmbeddings(candidates.map(({ observation }) => observationEmbeddingText(observation)));
5812
- let inserted = 0;
5812
+ const documents = [];
5813
5813
  for (let index = 0; index < candidates.length; index++) {
5814
5814
  const { observation: obs, id } = candidates[index];
5815
5815
  try {
@@ -5843,17 +5843,34 @@ async function hydrateIndex(observations2, options = {}) {
5843
5843
  knowledgeLayer: resolveKnowledgeLayer("observation", obs.sourceDetail, obs.source),
5844
5844
  ...compatibleVector ? { embedding: compatibleVector } : {}
5845
5845
  };
5846
- await insert2(database, doc);
5847
- rememberObservationDoc(doc);
5848
- inserted++;
5849
- if (inserted % 50 === 0) {
5850
- await new Promise((resolve) => setImmediate(resolve));
5851
- }
5852
- if (inserted % 200 === 0) {
5853
- await new Promise((resolve) => setTimeout(resolve, 0));
5854
- }
5846
+ documents.push(doc);
5847
+ } catch {
5848
+ }
5849
+ }
5850
+ let inserted = 0;
5851
+ const hydrationBatchSize = 200;
5852
+ for (let start = 0; start < documents.length; start += hydrationBatchSize) {
5853
+ const batch = documents.slice(start, start + hydrationBatchSize);
5854
+ try {
5855
+ await insertMultiple(database, batch, batch.length);
5856
+ for (const doc of batch) rememberObservationDoc(doc);
5857
+ inserted += batch.length;
5855
5858
  } catch {
5859
+ for (const doc of batch) {
5860
+ if (getByID(database, doc.id)) {
5861
+ rememberObservationDoc(doc);
5862
+ inserted++;
5863
+ continue;
5864
+ }
5865
+ try {
5866
+ await insert2(database, doc);
5867
+ rememberObservationDoc(doc);
5868
+ inserted++;
5869
+ } catch {
5870
+ }
5871
+ }
5856
5872
  }
5873
+ await new Promise((resolve) => setImmediate(resolve));
5857
5874
  }
5858
5875
  deferredCachedVectorHydration = options.skipCachedVectors ? null : deferCachedVectors ? attachCachedVectors(database, candidates).catch(() => {
5859
5876
  }) : null;
@@ -4,6 +4,22 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [1.7.2] - 2026-08-19
8
+
9
+ ### Changed
10
+ - **Faster large-store cold retrieval** -- startup hydration now inserts
11
+ persisted observations into Orama in bounded batches. This avoids rebuilding
12
+ index balance once per row while continuing to yield to HTTP health traffic.
13
+ - **Fail-closed large-store evidence** -- the reproducible 40,000-record gate
14
+ now exercises and times HTTP readiness, MCP initialize/bind/context/search/
15
+ store, hook capture, and SDK reopen behavior with hard request and release
16
+ budgets instead of checking persistence alone.
17
+ - **Isolated index tests** -- startup hydration tests explicitly disable the
18
+ embedding lane and restore the caller environment instead of reading an
19
+ operator's provider configuration or local vector cache.
20
+ - **Current public guidance** -- README and canonical development/status docs
21
+ now describe preview-first Dashboard maintenance and the 1.7 product line.
22
+
7
23
  ## [1.7.1] - 2026-08-19
8
24
 
9
25
  ### Fixed
package/dist/sdk.js CHANGED
@@ -6876,7 +6876,7 @@ __export(orama_store_exports, {
6876
6876
  searchObservations: () => searchObservations,
6877
6877
  updateObservationMetadata: () => updateObservationMetadata
6878
6878
  });
6879
- import { create, insert as insert2, search, remove as remove2, update, count, getByID } from "@orama/orama";
6879
+ import { create, insert as insert2, insertMultiple, search, remove as remove2, update, count, getByID } from "@orama/orama";
6880
6880
  function getLastSearchMode(projectId) {
6881
6881
  return lastSearchModeByProject.get(projectId ?? SEARCH_MODE_DEFAULT_KEY) ?? "fulltext";
6882
6882
  }
@@ -7088,7 +7088,7 @@ async function hydrateIndex(observations2, options = {}) {
7088
7088
  }
7089
7089
  const deferCachedVectors = options.deferCachedVectors === true && Boolean(indexEmbeddingProvider?.getCachedEmbeddings);
7090
7090
  const cachedVectors = deferCachedVectors ? candidates.map(() => null) : await getCachedEmbeddings(candidates.map(({ observation }) => observationEmbeddingText(observation)));
7091
- let inserted = 0;
7091
+ const documents = [];
7092
7092
  for (let index = 0; index < candidates.length; index++) {
7093
7093
  const { observation: obs, id } = candidates[index];
7094
7094
  try {
@@ -7122,17 +7122,34 @@ async function hydrateIndex(observations2, options = {}) {
7122
7122
  knowledgeLayer: resolveKnowledgeLayer("observation", obs.sourceDetail, obs.source),
7123
7123
  ...compatibleVector ? { embedding: compatibleVector } : {}
7124
7124
  };
7125
- await insert2(database, doc);
7126
- rememberObservationDoc(doc);
7127
- inserted++;
7128
- if (inserted % 50 === 0) {
7129
- await new Promise((resolve2) => setImmediate(resolve2));
7130
- }
7131
- if (inserted % 200 === 0) {
7132
- await new Promise((resolve2) => setTimeout(resolve2, 0));
7133
- }
7125
+ documents.push(doc);
7126
+ } catch {
7127
+ }
7128
+ }
7129
+ let inserted = 0;
7130
+ const hydrationBatchSize = 200;
7131
+ for (let start = 0; start < documents.length; start += hydrationBatchSize) {
7132
+ const batch = documents.slice(start, start + hydrationBatchSize);
7133
+ try {
7134
+ await insertMultiple(database, batch, batch.length);
7135
+ for (const doc of batch) rememberObservationDoc(doc);
7136
+ inserted += batch.length;
7134
7137
  } catch {
7138
+ for (const doc of batch) {
7139
+ if (getByID(database, doc.id)) {
7140
+ rememberObservationDoc(doc);
7141
+ inserted++;
7142
+ continue;
7143
+ }
7144
+ try {
7145
+ await insert2(database, doc);
7146
+ rememberObservationDoc(doc);
7147
+ inserted++;
7148
+ } catch {
7149
+ }
7150
+ }
7135
7151
  }
7152
+ await new Promise((resolve2) => setImmediate(resolve2));
7136
7153
  }
7137
7154
  deferredCachedVectorHydration = options.skipCachedVectors ? null : deferCachedVectors ? attachCachedVectors(database, candidates).catch(() => {
7138
7155
  }) : null;
@@ -167080,7 +167097,7 @@ The path should point to a directory containing a .git folder.`
167080
167097
  };
167081
167098
  const server = existingServer ?? new McpServer({
167082
167099
  name: "memorix",
167083
- version: true ? "1.7.1" : "1.0.1"
167100
+ version: true ? "1.7.2" : "1.0.1"
167084
167101
  });
167085
167102
  const originalRegisterTool = server.registerTool.bind(server);
167086
167103
  server.registerTool = ((name, ...args) => {
@@ -5597,7 +5597,7 @@ __export(orama_store_exports, {
5597
5597
  searchObservations: () => searchObservations,
5598
5598
  updateObservationMetadata: () => updateObservationMetadata
5599
5599
  });
5600
- import { create, insert as insert2, search, remove as remove2, update, count, getByID } from "@orama/orama";
5600
+ import { create, insert as insert2, insertMultiple, search, remove as remove2, update, count, getByID } from "@orama/orama";
5601
5601
  function getLastSearchMode(projectId) {
5602
5602
  return lastSearchModeByProject.get(projectId ?? SEARCH_MODE_DEFAULT_KEY) ?? "fulltext";
5603
5603
  }
@@ -5809,7 +5809,7 @@ async function hydrateIndex(observations2, options = {}) {
5809
5809
  }
5810
5810
  const deferCachedVectors = options.deferCachedVectors === true && Boolean(indexEmbeddingProvider?.getCachedEmbeddings);
5811
5811
  const cachedVectors = deferCachedVectors ? candidates.map(() => null) : await getCachedEmbeddings(candidates.map(({ observation }) => observationEmbeddingText(observation)));
5812
- let inserted = 0;
5812
+ const documents = [];
5813
5813
  for (let index = 0; index < candidates.length; index++) {
5814
5814
  const { observation: obs, id } = candidates[index];
5815
5815
  try {
@@ -5843,17 +5843,34 @@ async function hydrateIndex(observations2, options = {}) {
5843
5843
  knowledgeLayer: resolveKnowledgeLayer("observation", obs.sourceDetail, obs.source),
5844
5844
  ...compatibleVector ? { embedding: compatibleVector } : {}
5845
5845
  };
5846
- await insert2(database, doc);
5847
- rememberObservationDoc(doc);
5848
- inserted++;
5849
- if (inserted % 50 === 0) {
5850
- await new Promise((resolve) => setImmediate(resolve));
5851
- }
5852
- if (inserted % 200 === 0) {
5853
- await new Promise((resolve) => setTimeout(resolve, 0));
5854
- }
5846
+ documents.push(doc);
5847
+ } catch {
5848
+ }
5849
+ }
5850
+ let inserted = 0;
5851
+ const hydrationBatchSize = 200;
5852
+ for (let start = 0; start < documents.length; start += hydrationBatchSize) {
5853
+ const batch = documents.slice(start, start + hydrationBatchSize);
5854
+ try {
5855
+ await insertMultiple(database, batch, batch.length);
5856
+ for (const doc of batch) rememberObservationDoc(doc);
5857
+ inserted += batch.length;
5855
5858
  } catch {
5859
+ for (const doc of batch) {
5860
+ if (getByID(database, doc.id)) {
5861
+ rememberObservationDoc(doc);
5862
+ inserted++;
5863
+ continue;
5864
+ }
5865
+ try {
5866
+ await insert2(database, doc);
5867
+ rememberObservationDoc(doc);
5868
+ inserted++;
5869
+ } catch {
5870
+ }
5871
+ }
5856
5872
  }
5873
+ await new Promise((resolve) => setImmediate(resolve));
5857
5874
  }
5858
5875
  deferredCachedVectorHydration = options.skipCachedVectors ? null : deferCachedVectors ? attachCachedVectors(database, candidates).catch(() => {
5859
5876
  }) : null;
@@ -13,9 +13,10 @@ Memorix is a TypeScript project built around:
13
13
 
14
14
  ## Current Development Baseline
15
15
 
16
- The current release work targets the **1.4 line** while package metadata may still show the last published version until the release commit is cut.
16
+ The current release work targets the **1.7 line** while package metadata may
17
+ still show the last published version until the release commit is cut.
17
18
 
18
- Contributors should assume the following areas are part of the 1.4 release line:
19
+ Contributors should assume the following areas are part of the current release line:
19
20
 
20
21
  - shared memory across MCP clients, CLI, SDK, dashboard, hooks, and memcode
21
22
  - memcode uses Memorix project memory, hooks, `/memory` commands, resumable sessions, and model switching as the bundled terminal agent
@@ -27,6 +28,9 @@ Contributors should assume the following areas are part of the 1.4 release line:
27
28
  - dashboard config loading aligned with CLI/TUI status surfaces
28
29
  - the existing layered retrieval, retention, attribution, and compact output model
29
30
  - source-backed long-term memory with candidate, qualification, approval, archival, supersession, and explicit portable user scope
31
+ - preview-first project maintenance shared across Dashboard, CLI, and MCP
32
+ - the reproducible large-store gate for SDK, HTTP MCP, hooks, persistence, and
33
+ cold-start retrieval budgets
30
34
 
31
35
  ---
32
36
 
@@ -103,9 +103,16 @@ On the release development machine used for this check, the healthy HTTP service
103
103
  temporary Git project with an isolated data directory. It runs with embeddings
104
104
  disabled, so it does not use provider credentials or make paid network calls.
105
105
  The gate measures steady-state writes, first lexical search, process memory,
106
- HTTP MCP and hook persistence, and an SDK close/reopen cycle. It also verifies
107
- that the personal hook candidate remains durable on disk without becoming
108
- visible to an unbound SDK reader.
106
+ HTTP readiness, MCP initialize/bind/context/search/store, hook capture, and an
107
+ SDK close/reopen cycle. Every network request has a hard timeout, and the
108
+ report fails when an interactive path exceeds its release budget. It also
109
+ verifies that the personal hook candidate remains durable on disk without
110
+ becoming visible to an unbound SDK reader.
111
+
112
+ Startup hydration uses bounded Orama batches. This keeps HTTP health responsive
113
+ while avoiding a full index rebalance for every persisted observation. The MCP
114
+ search number is intentionally a cold control-plane measurement: it includes
115
+ any lexical hydration the fresh service still owes before its first search.
109
116
 
110
117
  Results are machine-specific and should be compared on the same OS and Node
111
118
  version. The release gate is an integrity and regression check, not a public
package/docs/README.md CHANGED
@@ -119,11 +119,13 @@ Historical/deep-reference documents may describe older designs. If they conflict
119
119
 
120
120
  ## Current Product Line
121
121
 
122
- The released product is on the **1.6 line**. The current baseline has:
122
+ The released product is on the **1.7 line**. The current baseline has:
123
123
 
124
124
  - `memorix setup --agent <agent> --global` is the default agent integration command
125
125
  - `memorix serve` is the manual stdio MCP server for external agents
126
126
  - `memorix background start` runs the shared HTTP MCP service and dashboard
127
+ - Dashboard maintenance is preview-first, project-scoped, and uses the same
128
+ cleanup and deduplication planners as CLI and MCP
127
129
  - context and CodeGraph commands return bounded task receipts before detailed diagnostics
128
130
  - HTTP MCP sessions have a bounded, explicit lifecycle and fail with a reinitialize hint after expiry
129
131
  - `memorix integrate --agent <agent>` and `memorix hooks install --agent <agent>` remain manual/fallback generation commands
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "memorix",
3
3
  "mcpName": "io.github.AVIDS2/memorix",
4
- "version": "1.7.1",
4
+ "version": "1.7.2",
5
5
  "description": "Local-first shared memory layer for AI coding agents across MCP clients, Git history, reasoning context, and project sessions.",
6
6
  "type": "module",
7
7
  "sideEffects": false,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://anthropic.com/claude-code/plugin.schema.json",
3
3
  "name": "memorix",
4
- "version": "1.7.1",
4
+ "version": "1.7.2",
5
5
  "description": "Shared workspace memory for Claude Code and other AI coding agents.",
6
6
  "author": {
7
7
  "name": "AVIDS2",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memorix",
3
- "version": "1.7.1",
3
+ "version": "1.7.2",
4
4
  "description": "Local-first project memory for CodeBuddy Code and other coding agents.",
5
5
  "author": {
6
6
  "name": "AVIDS2",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memorix",
3
- "version": "1.7.1",
3
+ "version": "1.7.2",
4
4
  "description": "Shared workspace memory for Codex and other AI coding agents.",
5
5
  "author": {
6
6
  "name": "AVIDS2",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memorix",
3
- "version": "1.7.1",
3
+ "version": "1.7.2",
4
4
  "description": "Shared local workspace memory for GitHub Copilot CLI and other AI coding agents.",
5
5
  "author": {
6
6
  "name": "AVIDS2",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memorix",
3
- "version": "1.7.1",
3
+ "version": "1.7.2",
4
4
  "mcpServers": {
5
5
  "memorix": {
6
6
  "command": "memorix",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memorix-omp-package",
3
- "version": "1.7.1",
3
+ "version": "1.7.2",
4
4
  "description": "Memorix shared workspace memory package for Oh-my-Pi.",
5
5
  "license": "Apache-2.0",
6
6
  "keywords": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memorix",
3
- "version": "1.7.1",
3
+ "version": "1.7.2",
4
4
  "description": "Shared workspace memory for OpenClaw and other AI coding agents.",
5
5
  "author": {
6
6
  "name": "AVIDS2",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memorix-pi-package",
3
- "version": "1.7.1",
3
+ "version": "1.7.2",
4
4
  "description": "Memorix shared workspace memory package for Pi coding agent.",
5
5
  "license": "Apache-2.0",
6
6
  "keywords": [
package/server.json CHANGED
@@ -7,7 +7,7 @@
7
7
  "url": "https://github.com/AVIDS2/memorix",
8
8
  "source": "github"
9
9
  },
10
- "version": "1.7.1",
10
+ "version": "1.7.2",
11
11
  "websiteUrl": "https://github.com/AVIDS2/memorix#readme",
12
12
  "icons": [
13
13
  {
@@ -22,7 +22,7 @@
22
22
  {
23
23
  "registryType": "npm",
24
24
  "identifier": "memorix",
25
- "version": "1.7.1",
25
+ "version": "1.7.2",
26
26
  "runtimeHint": "npx",
27
27
  "packageArguments": [
28
28
  {
@@ -8,7 +8,7 @@
8
8
  * Vector search (embeddings) will be added in P1 phase.
9
9
  */
10
10
 
11
- import { create, insert, search, remove, update, count, getByID, type AnyOrama } from '@orama/orama';
11
+ import { create, insert, insertMultiple, search, remove, update, count, getByID, type AnyOrama } from '@orama/orama';
12
12
  import type { MemorixDocument, SearchOptions, IndexEntry, KnowledgeLayer, ObservationReader } from '../types.js';
13
13
  import { OBSERVATION_ICONS, type ObservationType } from '../types.js';
14
14
  import { resolveKnowledgeLayer } from '../skills/mini-skills.js';
@@ -405,7 +405,7 @@ export async function hydrateIndex(
405
405
  ? candidates.map(() => null)
406
406
  : await getCachedEmbeddings(candidates.map(({ observation }) => observationEmbeddingText(observation)));
407
407
 
408
- let inserted = 0;
408
+ const documents: MemorixDocument[] = [];
409
409
  for (let index = 0; index < candidates.length; index++) {
410
410
  const { observation: obs, id } = candidates[index];
411
411
  try {
@@ -439,19 +439,39 @@ export async function hydrateIndex(
439
439
  knowledgeLayer: resolveKnowledgeLayer('observation', obs.sourceDetail, obs.source),
440
440
  ...(compatibleVector ? { embedding: compatibleVector } : {}),
441
441
  };
442
- await insert(database, doc);
443
- rememberObservationDoc(doc);
444
- inserted++;
445
- // Keep the HTTP event loop alive on large corpora so /health still answers.
446
- if (inserted % 50 === 0) {
447
- await new Promise<void>((resolve) => setImmediate(resolve));
448
- }
449
- if (inserted % 200 === 0) {
450
- await new Promise<void>((resolve) => setTimeout(resolve, 0));
451
- }
442
+ documents.push(doc);
452
443
  } catch { /* skip malformed entries */ }
453
444
  }
454
445
 
446
+ let inserted = 0;
447
+ const hydrationBatchSize = 200;
448
+ for (let start = 0; start < documents.length; start += hydrationBatchSize) {
449
+ const batch = documents.slice(start, start + hydrationBatchSize);
450
+ try {
451
+ // Orama can rebalance its indexes once per batch instead of once per
452
+ // document. Keep batches bounded so HTTP health remains responsive.
453
+ await insertMultiple(database, batch, batch.length);
454
+ for (const doc of batch) rememberObservationDoc(doc);
455
+ inserted += batch.length;
456
+ } catch {
457
+ // A concurrent write or malformed document can partially complete a
458
+ // batch. Preserve the old best-effort behavior without double-inserting.
459
+ for (const doc of batch) {
460
+ if (getByID(database, doc.id)) {
461
+ rememberObservationDoc(doc);
462
+ inserted++;
463
+ continue;
464
+ }
465
+ try {
466
+ await insert(database, doc);
467
+ rememberObservationDoc(doc);
468
+ inserted++;
469
+ } catch { /* skip malformed or concurrently inserted entries */ }
470
+ }
471
+ }
472
+ await new Promise<void>((resolve) => setImmediate(resolve));
473
+ }
474
+
455
475
  deferredCachedVectorHydration = options.skipCachedVectors
456
476
  ? null
457
477
  : deferCachedVectors