@cerefox/memory 1.0.0-rc.2 → 1.0.0-rc.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7184,7 +7184,7 @@ var exports_meta = {};
7184
7184
  __export(exports_meta, {
7185
7185
  PKG_VERSION: () => PKG_VERSION
7186
7186
  });
7187
- var PKG_VERSION = "1.0.0-rc.2";
7187
+ var PKG_VERSION = "1.0.0-rc.4";
7188
7188
  var init_meta = () => {};
7189
7189
 
7190
7190
  // ../../node_modules/.bun/tslib@2.8.1/node_modules/tslib/tslib.js
@@ -25008,10 +25008,11 @@ function getMaxResponseBytes() {
25008
25008
  }
25009
25009
  function getMinSearchScore() {
25010
25010
  const raw = globalThis.process?.env?.CEREFOX_MIN_SEARCH_SCORE;
25011
+ const fallback = globalThis.process?.env?.CEREFOX_EMBEDDER === "local" ? DEFAULT_MIN_SEARCH_SCORE_LOCAL : DEFAULT_MIN_SEARCH_SCORE;
25011
25012
  if (raw === undefined || raw === "")
25012
- return DEFAULT_MIN_SEARCH_SCORE;
25013
+ return fallback;
25013
25014
  const n = Number.parseFloat(raw);
25014
- return Number.isNaN(n) || n < 0 || n > 1 ? DEFAULT_MIN_SEARCH_SCORE : n;
25015
+ return Number.isNaN(n) || n < 0 || n > 1 ? fallback : n;
25015
25016
  }
25016
25017
  function applyByteBudget(rows, maxBytes) {
25017
25018
  const accepted = [];
@@ -25040,7 +25041,7 @@ function logUsage(supabase, params) {
25040
25041
  p_extra: params.extra ?? {}
25041
25042
  })).catch(() => {});
25042
25043
  }
25043
- var MAX_RESPONSE_BYTES = 200000, DEFAULT_MIN_SEARCH_SCORE = 0.5;
25044
+ var MAX_RESPONSE_BYTES = 200000, DEFAULT_MIN_SEARCH_SCORE = 0.5, DEFAULT_MIN_SEARCH_SCORE_LOCAL = 0.6;
25044
25045
 
25045
25046
  // ../../_shared/mcp-tools/_projects.ts
25046
25047
  async function ensureDocumentInProject(supabase, documentId, projectName) {
@@ -25532,9 +25533,22 @@ async function ensurePipeline() {
25532
25533
  async function warmup() {
25533
25534
  await ensurePipeline();
25534
25535
  }
25536
+ function onnxBatchSize() {
25537
+ const env4 = globalThis.process?.env ?? {};
25538
+ const n = Number.parseInt(env4.CEREFOX_ONNX_BATCH ?? "", 10);
25539
+ return Number.isFinite(n) && n > 0 ? n : DEFAULT_ONNX_BATCH;
25540
+ }
25535
25541
  async function onnxEmbed(texts, role) {
25536
25542
  if (texts.length === 0)
25537
25543
  return [];
25544
+ const batch = onnxBatchSize();
25545
+ if (texts.length > batch) {
25546
+ const out2 = [];
25547
+ for (let i = 0;i < texts.length; i += batch) {
25548
+ out2.push(...await onnxEmbed(texts.slice(i, i + batch), role));
25549
+ }
25550
+ return out2;
25551
+ }
25538
25552
  const pipeline = await ensurePipeline();
25539
25553
  const inputs = buildPrefixedInputs(texts, role);
25540
25554
  const out = await pipeline(inputs, { pooling: "mean", normalize: true });
@@ -25549,7 +25563,7 @@ async function onnxEmbed(texts, role) {
25549
25563
  }
25550
25564
  return vectors;
25551
25565
  }
25552
- var ONNX_MODEL_ID = "nomic-ai/nomic-embed-text-v1.5", ONNX_MODEL_NAME = "nomic-embed-text-v1.5", ONNX_MODEL_DTYPE = "q8", ONNX_MODEL_DIM = 768, ONNX_MODEL_APPROX_MB = 130, transformersModule = null, pipelinePromise = null;
25566
+ var ONNX_MODEL_ID = "nomic-ai/nomic-embed-text-v1.5", ONNX_MODEL_NAME = "nomic-embed-text-v1.5", ONNX_MODEL_DTYPE = "q8", ONNX_MODEL_DIM = 768, ONNX_MODEL_APPROX_MB = 130, transformersModule = null, pipelinePromise = null, DEFAULT_ONNX_BATCH = 4;
25553
25567
  var init_onnx_embedder = () => {};
25554
25568
 
25555
25569
  // ../../_shared/embeddings/index.ts
@@ -74646,7 +74660,7 @@ import { homedir as homedir6 } from "node:os";
74646
74660
  import { join as join9 } from "node:path";
74647
74661
 
74648
74662
  // ../../_shared/ef-meta/index.ts
74649
- var EF_VERSION = "1.0.0-beta.4";
74663
+ var EF_VERSION = "1.0.0-rc.4";
74650
74664
 
74651
74665
  // src/cli/util/checks.ts
74652
74666
  init_config();
@@ -74704,6 +74718,14 @@ function checkConfig() {
74704
74718
  };
74705
74719
  }
74706
74720
  if (!existsSync10(envPath)) {
74721
+ const settings = loadSettings();
74722
+ if (settings.supabaseUrl && settings.supabaseKey) {
74723
+ return {
74724
+ name: "config",
74725
+ status: "ok",
74726
+ detail: "configured via environment variables (no .env file — normal for Cerefox Local)"
74727
+ };
74728
+ }
74707
74729
  return {
74708
74730
  name: "config",
74709
74731
  status: "error",
@@ -75023,9 +75045,9 @@ function checkMcpConfigs() {
75023
75045
  if (found.length === 0) {
75024
75046
  return {
75025
75047
  name: "mcp clients",
75026
- status: "warn",
75027
- detail: "No MCP client configs reference Cerefox.",
75028
- hint: "Run `cerefox configure-agent --tool claude-code` (or `--tool claude-desktop`) to wire up a client."
75048
+ status: isLocalBackend() ? "skipped" : "warn",
75049
+ detail: isLocalBackend() ? "checked from inside the container — host MCP configs are not visible here." : "No MCP client configs reference Cerefox.",
75050
+ hint: isLocalBackend() ? "Configure agents on the host with `cerefox-local configure-agent`." : "Run `cerefox configure-agent --tool claude-code` (or `--tool claude-desktop`) to wire up a client."
75029
75051
  };
75030
75052
  }
75031
75053
  return {
@@ -75097,7 +75119,17 @@ async function checkPostgres() {
75097
75119
  await sql.end({ timeout: 1 }).catch(() => {});
75098
75120
  }
75099
75121
  }
75122
+ function isLocalBackend() {
75123
+ return Boolean(process.env.CEREFOX_POSTGREST_UPSTREAM);
75124
+ }
75100
75125
  async function checkEdgeFunctionsCompat() {
75126
+ if (isLocalBackend()) {
75127
+ return {
75128
+ name: "edge functions",
75129
+ status: "skipped",
75130
+ detail: "local backend — Edge Functions are not used (cloud-only surface)."
75131
+ };
75132
+ }
75101
75133
  const settings = loadSettings();
75102
75134
  if (!settings.supabaseUrl) {
75103
75135
  return {
@@ -77234,7 +77266,7 @@ async function action29(query, options) {
77234
77266
  }
77235
77267
  }
77236
77268
  function registerSearch(program2) {
77237
- program2.command("search").description("Search the knowledge base (hybrid FTS + semantic).").argument("<query>", "Natural-language search query.").option("-c, --match-count <n>", "Maximum number of documents to return.", "5").option("-p, --project-name <name>", "Filter results to a specific project.").option("-f, --metadata-filter <json>", "JSON containment filter; only docs whose metadata contains ALL pairs are returned.").option("--mode <mode>", "Search mode: docs (default), hybrid, fts.", "docs").option("--alpha <float>", "Semantic weight 0..1 (default: 0.7).", "0.7").option("--min-score <float>", "Minimum cosine similarity threshold (default: CEREFOX_MIN_SEARCH_SCORE or 0.5).").option("--max-bytes <n>", "Response size budget in bytes (default: CEREFOX_MAX_RESPONSE_BYTES or 200000).").option("-r, --requestor <name>", "Agent / user name (recorded in usage log).").option("--json", "Emit machine-readable JSON instead of the default text.").option("--only-metadata", "List matching docs (id, score, chunks, chars, partial/full) WITHOUT their content — like the web UI's collapsed result list. Grab a [id:…] then `cerefox document get <id>`.").action(action29);
77269
+ program2.command("search").description("Search the knowledge base (hybrid FTS + semantic).").argument("<query>", "Natural-language search query.").option("-c, --match-count <n>", "Maximum number of documents to return.", "5").option("-p, --project-name <name>", "Filter results to a specific project.").option("-f, --metadata-filter <json>", "JSON containment filter; only docs whose metadata contains ALL pairs are returned.").option("--mode <mode>", "Search mode: docs (default), hybrid, fts.", "docs").option("--alpha <float>", "Semantic weight 0..1 (default: 0.7).", "0.7").option("--min-score <float>", "Minimum cosine similarity threshold (default: CEREFOX_MIN_SEARCH_SCORE; else 0.5, or 0.6 with the local embedder).").option("--max-bytes <n>", "Response size budget in bytes (default: CEREFOX_MAX_RESPONSE_BYTES or 200000).").option("-r, --requestor <name>", "Agent / user name (recorded in usage log).").option("--json", "Emit machine-readable JSON instead of the default text.").option("--only-metadata", "List matching docs (id, score, chunks, chars, partial/full) WITHOUT their content — like the web UI's collapsed result list. Grab a [id:…] then `cerefox document get <id>`.").action(action29);
77238
77270
  }
77239
77271
 
77240
77272
  // src/cli/commands/self-update.ts
@@ -18,7 +18,7 @@
18
18
  * doesn't touch `supabase/functions/` leaves it alone).
19
19
  */
20
20
 
21
- export const EF_VERSION = "1.0.0-beta.4";
21
+ export const EF_VERSION = "1.0.0-rc.4";
22
22
 
23
23
  /**
24
24
  * The 8 peer EFs the cerefox-mcp aggregator probes (excludes cerefox-mcp
@@ -208,8 +208,32 @@ export async function warmup(): Promise<void> {
208
208
  * Mean pooling + L2 normalisation (sentence-transformers convention; nomic
209
209
  * expects both). Returns plain `number[][]` to match the OpenAI path.
210
210
  */
211
+ /**
212
+ * Per-inference sub-batch. Peak tensor memory scales with the batch, and the
213
+ * container shares a (often small) Docker VM with Postgres + PostgREST + the
214
+ * web server — a 12-text single call was OOM-killed on Colima's default 2 GB
215
+ * VM (rc.2 dogfood, exit 137). 4 keeps peak memory flat at personal scale;
216
+ * override with CEREFOX_ONNX_BATCH.
217
+ */
218
+ const DEFAULT_ONNX_BATCH = 4;
219
+
220
+ function onnxBatchSize(): number {
221
+ const env = (globalThis as { process?: { env?: Record<string, string | undefined> } })
222
+ .process?.env ?? {};
223
+ const n = Number.parseInt(env.CEREFOX_ONNX_BATCH ?? "", 10);
224
+ return Number.isFinite(n) && n > 0 ? n : DEFAULT_ONNX_BATCH;
225
+ }
226
+
211
227
  export async function onnxEmbed(texts: string[], role: EmbedRole): Promise<number[][]> {
212
228
  if (texts.length === 0) return [];
229
+ const batch = onnxBatchSize();
230
+ if (texts.length > batch) {
231
+ const out: number[][] = [];
232
+ for (let i = 0; i < texts.length; i += batch) {
233
+ out.push(...(await onnxEmbed(texts.slice(i, i + batch), role)));
234
+ }
235
+ return out;
236
+ }
213
237
  const pipeline = await ensurePipeline();
214
238
  const inputs = buildPrefixedInputs(texts, role);
215
239
  const out = await pipeline(inputs, { pooling: "mean", normalize: true });
@@ -35,6 +35,15 @@ export function getMaxResponseBytes(): number {
35
35
  /** Built-in default cosine-similarity floor for hybrid/semantic search. */
36
36
  export const DEFAULT_MIN_SEARCH_SCORE = 0.5;
37
37
 
38
+ /**
39
+ * Nomic's cosine-score distribution sits higher than OpenAI's: unrelated text
40
+ * lands ~0.4–0.55 (vs ~0.1–0.3), so the 0.5 floor calibrated for
41
+ * text-embedding-3-small lets weak matches through on the local embedder
42
+ * (rc.3 dogfood: an unrelated doc passed at vec≈0.54). 0.6 restores the
43
+ * intended precision; relevant nomic matches score ~0.7+.
44
+ */
45
+ export const DEFAULT_MIN_SEARCH_SCORE_LOCAL = 0.6;
46
+
38
47
  /**
39
48
  * Resolve the minimum cosine-similarity floor for hybrid/semantic search
40
49
  * (vector-only matches below this are dropped; FTS matches always pass).
@@ -49,9 +58,14 @@ export const DEFAULT_MIN_SEARCH_SCORE = 0.5;
49
58
  export function getMinSearchScore(): number {
50
59
  const raw = (globalThis as { process?: { env?: Record<string, string | undefined> } })
51
60
  .process?.env?.CEREFOX_MIN_SEARCH_SCORE;
52
- if (raw === undefined || raw === "") return DEFAULT_MIN_SEARCH_SCORE;
61
+ const fallback =
62
+ (globalThis as { process?: { env?: Record<string, string | undefined> } })
63
+ .process?.env?.CEREFOX_EMBEDDER === "local"
64
+ ? DEFAULT_MIN_SEARCH_SCORE_LOCAL
65
+ : DEFAULT_MIN_SEARCH_SCORE;
66
+ if (raw === undefined || raw === "") return fallback;
53
67
  const n = Number.parseFloat(raw);
54
- return Number.isNaN(n) || n < 0 || n > 1 ? DEFAULT_MIN_SEARCH_SCORE : n;
68
+ return Number.isNaN(n) || n < 0 || n > 1 ? fallback : n;
55
69
  }
56
70
 
57
71
  export function applyByteBudget(
@@ -49,6 +49,10 @@ install; switching later requires a re-index (see below).
49
49
  The local model (~130 MB) downloads once — at install/init when selected — into the
50
50
  data volume, so it survives `cerefox-local upgrade`.
51
51
 
52
+ > **Memory**: give the Docker VM **≥ 4 GB** for comfortable local-embedder use
53
+ > (Colima defaults to 2 GB: `colima start --memory 4`). Inference is
54
+ > sub-batched to keep peak memory flat, so smaller VMs work — just slower.
55
+
52
56
  > **Switching embedders on existing data is breaking**: the two models produce
53
57
  > incompatible vector spaces, so documents embedded with one are invisible to
54
58
  > semantic search under the other. `cerefox-local init` warns and requires
@@ -56,6 +60,10 @@ data volume, so it survives `cerefox-local upgrade`.
56
60
  > `cerefox-local server reindex` to re-embed everything. `cerefox-local doctor`
57
61
  > flags any mismatch.
58
62
 
63
+ > Scores are calibrated per embedder: with the local model the default semantic
64
+ > threshold is **0.6** (vs 0.5 for OpenAI) because nomic scores unrelated text
65
+ > higher. Override per call with `--min-score` or via `CEREFOX_MIN_SEARCH_SCORE`.
66
+
59
67
  ---
60
68
 
61
69
  ## Step 1 — Install
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cerefox/memory",
3
- "version": "1.0.0-rc.2",
3
+ "version": "1.0.0-rc.4",
4
4
  "description": "Cerefox — user-owned shared memory for AI agents. The local TypeScript runtime: stdio MCP server in v0.4; CLI binary added in v0.5; in-process web server in v0.6; ingestion pipeline in v0.7.",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://github.com/fstamatelopoulos/cerefox",