@cerefox/memory 1.0.5 → 1.0.7

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.
@@ -15,7 +15,7 @@
15
15
  href="https://fonts.googleapis.com/css2?family=Geist:wght@300;400;500;600;700&display=swap"
16
16
  />
17
17
  <title>Cerefox</title>
18
- <script type="module" crossorigin src="/app/assets/index-Csj-6UHY.js"></script>
18
+ <script type="module" crossorigin src="/app/assets/index-VeqA60-v.js"></script>
19
19
  <link rel="stylesheet" crossorigin href="/app/assets/index-Asx5wD7g.css">
20
20
  </head>
21
21
  <body>
@@ -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.5";
21
+ export const EF_VERSION = "1.0.7";
22
22
 
23
23
  /**
24
24
  * The most recent version whose EF-side SOURCE actually changed (#127).
@@ -28,7 +28,7 @@ export const EF_VERSION = "1.0.5";
28
28
  * `cut_release.ts` ONLY when EF source changed since the last tag; doctor
29
29
  * uses it to stay silent on label-only drift.
30
30
  */
31
- export const EF_LAST_CHANGED = "1.0.4";
31
+ export const EF_LAST_CHANGED = "1.0.6";
32
32
 
33
33
  /**
34
34
  * The 8 peer EFs the cerefox-mcp aggregator probes (excludes cerefox-mcp
@@ -44,6 +44,51 @@ export const DEFAULT_MIN_SEARCH_SCORE = 0.5;
44
44
  */
45
45
  export const DEFAULT_MIN_SEARCH_SCORE_LOCAL = 0.6;
46
46
 
47
+ /**
48
+ * Read an env var in any of Cerefox's three runtimes.
49
+ *
50
+ * Node/Bun expose `process.env` (populated from the user's `.env` by
51
+ * `_shared/config`). Supabase Edge Functions run Deno, where `process` may be
52
+ * absent but **Function secrets are readable via `Deno.env`** — so reading
53
+ * both means a secret set on the project configures the remote MCP / EF path
54
+ * the same way `.env` configures the local one. (Before this, the retrieval
55
+ * tunables silently fell back to built-in defaults on the remote path.)
56
+ */
57
+ function readEnv(name: string): string | undefined {
58
+ const g = globalThis as {
59
+ process?: { env?: Record<string, string | undefined> };
60
+ Deno?: { env?: { get(k: string): string | undefined } };
61
+ };
62
+ const fromProcess = g.process?.env?.[name];
63
+ if (fromProcess !== undefined && fromProcess !== "") return fromProcess;
64
+ try {
65
+ const fromDeno = g.Deno?.env?.get(name);
66
+ return fromDeno === "" ? undefined : fromDeno;
67
+ } catch {
68
+ // Deno without --allow-env: treat as unset.
69
+ return undefined;
70
+ }
71
+ }
72
+
73
+ /** Parse a 0–1 env value; undefined when unset or out of range. */
74
+ function readUnitInterval(name: string): number | undefined {
75
+ const raw = readEnv(name);
76
+ if (raw === undefined) return undefined;
77
+ const n = Number.parseFloat(raw);
78
+ return Number.isNaN(n) || n < 0 || n > 1 ? undefined : n;
79
+ }
80
+
81
+ /**
82
+ * Default hybrid fusion weight: 1.0 = pure semantic, 0.0 = pure keyword.
83
+ * Overridable via `CEREFOX_SEARCH_ALPHA` (parity with the other retrieval
84
+ * tunables; previously alpha was per-call only).
85
+ */
86
+ export const DEFAULT_SEARCH_ALPHA = 0.7;
87
+
88
+ export function getSearchAlpha(): number {
89
+ return readUnitInterval("CEREFOX_SEARCH_ALPHA") ?? DEFAULT_SEARCH_ALPHA;
90
+ }
91
+
47
92
  /**
48
93
  * Resolve the minimum cosine-similarity floor for hybrid/semantic search
49
94
  * (vector-only matches below this are dropped; FTS matches always pass).
@@ -56,16 +101,11 @@ export const DEFAULT_MIN_SEARCH_SCORE_LOCAL = 0.6;
56
101
  * EF path doesn't use the host `.env` anyway).
57
102
  */
58
103
  export function getMinSearchScore(): number {
59
- const raw = (globalThis as { process?: { env?: Record<string, string | undefined> } })
60
- .process?.env?.CEREFOX_MIN_SEARCH_SCORE;
61
104
  const fallback =
62
- (globalThis as { process?: { env?: Record<string, string | undefined> } })
63
- .process?.env?.CEREFOX_EMBEDDER === "local"
105
+ readEnv("CEREFOX_EMBEDDER") === "local"
64
106
  ? DEFAULT_MIN_SEARCH_SCORE_LOCAL
65
107
  : DEFAULT_MIN_SEARCH_SCORE;
66
- if (raw === undefined || raw === "") return fallback;
67
- const n = Number.parseFloat(raw);
68
- return Number.isNaN(n) || n < 0 || n > 1 ? fallback : n;
108
+ return readUnitInterval("CEREFOX_MIN_SEARCH_SCORE") ?? fallback;
69
109
  }
70
110
 
71
111
  /**
@@ -76,11 +116,7 @@ export function getMinSearchScore(): number {
76
116
  * (an unknown named argument fails the PostgREST function match).
77
117
  */
78
118
  export function getMinTermCoverage(): number | undefined {
79
- const raw = (globalThis as { process?: { env?: Record<string, string | undefined> } })
80
- .process?.env?.CEREFOX_MIN_TERM_COVERAGE;
81
- if (raw === undefined || raw === "") return undefined;
82
- const n = Number.parseFloat(raw);
83
- return Number.isNaN(n) || n < 0 || n > 1 ? undefined : n;
119
+ return readUnitInterval("CEREFOX_MIN_TERM_COVERAGE");
84
120
  }
85
121
 
86
122
  export function applyByteBudget(
@@ -19,7 +19,7 @@ import type { MCPSupabaseClient } from "./types.ts";
19
19
 
20
20
  import { getEmbedding, resolveEmbedderKind } from "../embeddings/index.ts";
21
21
  import { applyByteBudget, getMaxResponseBytes, getMinSearchScore,
22
- getMinTermCoverage, logUsage } from "./_utils.ts";
22
+ getMinTermCoverage, getSearchAlpha, logUsage } from "./_utils.ts";
23
23
  import { lookupProjectId } from "./_projects.ts";
24
24
  import { McpInvalidParams, type ToolContext, type ToolDefinition } from "./types.ts";
25
25
 
@@ -32,7 +32,7 @@ async function handler(
32
32
  const project_name = args.project_name as string | undefined;
33
33
  const match_count = (args.match_count as number | undefined) ?? 5;
34
34
  const mode = (args.mode as string | undefined) ?? "docs";
35
- const alpha = (args.alpha as number | undefined) ?? 0.7;
35
+ const alpha = (args.alpha as number | undefined) ?? getSearchAlpha();
36
36
  const min_score = (args.min_score as number | undefined) ?? getMinSearchScore();
37
37
  // v1.0.4: coverage gate default from CEREFOX_MIN_TERM_COVERAGE; only sent
38
38
  // when configured (see getMinTermCoverage — keeps pre-0.9.1 servers working).
@@ -285,7 +285,21 @@ BEGIN
285
285
  (combined.has_fts_match OR combined.vec_score >= p_min_score) AS passes
286
286
  FROM combined
287
287
  ),
288
- any_pass AS (SELECT bool_or(fl.passes) AS ok FROM flagged fl)
288
+ any_pass AS (SELECT bool_or(fl.passes) AS ok FROM flagged fl),
289
+ -- v1.0.6: in the below-confidence fallback, rank each candidate WITHIN
290
+ -- its parent document so we can return one chunk per document. The cap
291
+ -- used to apply to chunks, so when a document owned several of the top
292
+ -- chunks the caller saw fewer than 3 results after document-level
293
+ -- de-duplication (cerefox_search_docs, CLI and web) — the count varied
294
+ -- with corpus shape rather than with the cap.
295
+ ranked AS (
296
+ SELECT fl.*,
297
+ ROW_NUMBER() OVER (
298
+ PARTITION BY ch.document_id ORDER BY fl.score DESC
299
+ ) AS rank_in_doc
300
+ FROM flagged fl
301
+ JOIN cerefox_chunks ch ON ch.id = fl.id
302
+ )
289
303
  SELECT
290
304
  c.id AS chunk_id,
291
305
  c.document_id,
@@ -311,11 +325,13 @@ BEGIN
311
325
  -- memory layer can produce. "Truly nothing" (no candidates at all)
312
326
  -- still returns zero rows.
313
327
  NOT ap.ok AS below_confidence
314
- FROM flagged cm
328
+ FROM ranked cm
315
329
  CROSS JOIN any_pass ap
316
330
  JOIN cerefox_chunks c ON c.id = cm.id
317
331
  JOIN cerefox_documents d ON c.document_id = d.id
318
- WHERE cm.passes OR NOT ap.ok
332
+ -- Normal results are unchanged; fallback rows are restricted to each
333
+ -- document's best chunk so the cap below counts documents, not chunks.
334
+ WHERE cm.passes OR (NOT ap.ok AND cm.rank_in_doc = 1)
319
335
  ORDER BY cm.score DESC
320
336
  LIMIT (SELECT CASE WHEN ap2.ok THEN p_match_count
321
337
  ELSE LEAST(p_match_count, 3) END
@@ -1938,7 +1954,7 @@ SET search_path = public, pg_catalog
1938
1954
  AS $$
1939
1955
  -- Keep in lockstep with the `@version:` marker in schema.sql (cut_release.ts
1940
1956
  -- enforces it). Bump whenever schema.sql OR rpcs.sql changes.
1941
- SELECT '0.9.1'::TEXT;
1957
+ SELECT '0.9.2'::TEXT;
1942
1958
  $$;
1943
1959
 
1944
1960
  -- ── cerefox_content_format_stats ─────────────────────────────────────────────
@@ -5,7 +5,7 @@
5
5
  -- Requires extensions: vector (pgvector), uuid-ossp
6
6
  -- These are enabled at the top of db_deploy.py before this file is applied.
7
7
  --
8
- -- @version: 0.9.1
8
+ -- @version: 0.9.2
9
9
  -- The `@version` marker above is read by the schema-version-mismatch banner
10
10
  -- (see /api/v1/schema-version). Bump it whenever schema.sql OR rpcs.sql
11
11
  -- changes in a way that requires `cerefox server deploy` to be re-run —
@@ -119,16 +119,19 @@ This handles intermittent OpenAI API errors (500s) that would otherwise cause se
119
119
 
120
120
  > **Which paths read these?** Client-side tunables in this section are read
121
121
  > from *your* `.env` by the **CLI**, the **local MCP server**, and `cerefox
122
- > web`. The **remote MCP / Edge Function path** runs on Supabase and does not
123
- > see your `.env` — it uses the server defaults unless the caller passes the
124
- > per-call parameter (e.g. `min_score`, `min_term_coverage` on
125
- > `cerefox_search`). Setting them as Supabase **Function secrets** may also
126
- > work but is not a tested configuration.
122
+ > web`. Since **v1.0.6** the same values are also honored on the **remote MCP /
123
+ > Edge Function path** when set as Supabase **Function secrets**
124
+ > (`supabase secrets set CEREFOX_MIN_SEARCH_SCORE=0.55 --project-ref <ref>`) —
125
+ > the shared helpers read `Deno.env` as well as `process.env`. Per-call
126
+ > parameters (`min_score`, `min_term_coverage`, `alpha` on `cerefox_search`)
127
+ > override both. A single setting that governs every path without secrets is
128
+ > tracked as issue #133 (DB-backed config).
127
129
 
128
130
  | Variable | Default | Description |
129
131
  |----------|---------|-------------|
130
132
  | `CEREFOX_MAX_RESPONSE_BYTES` | `200000` | Maximum bytes in a single search response (local MCP path). See explanation below. |
131
133
  | `CEREFOX_MIN_SEARCH_SCORE` | `0.50` (`0.60` with the local embedder) | Minimum cosine similarity for hybrid and semantic search results (0.0–1.0). The default is embedder-aware: nomic scores unrelated text higher than OpenAI, so `CEREFOX_EMBEDDER=local` raises the floor to 0.60. In **hybrid search**, chunks that matched the FTS keyword operator (`@@`) always pass through regardless of their vector score — the threshold only filters vector-only results. In **semantic search**, all results are filtered. The pure **FTS search** mode is unaffected. Increase for stricter precision; decrease for wider recall. |
134
+ | `CEREFOX_SEARCH_ALPHA` | `0.7` | Hybrid fusion weight (0.0–1.0): `1.0` = pure semantic, `0.0` = pure keyword. Applies to hybrid and document-mode search. Per-call override: `cerefox search --alpha`, or the `alpha` parameter on the `cerefox_search` MCP tool. |
132
135
  | `CEREFOX_MIN_TERM_COVERAGE` | *(unset — server default `0.5`)* | Confidence bar for the keyword OR-fallback (v1.0.4, schema ≥ 0.9.1): when a strict all-terms match fails and search relaxes to any-term matching, a result counts as a confident hit only if it matches at least this fraction of the query's meaningful terms; weaker matches surface as below-confidence candidates. `0` restores pre-gate behavior (any matching term passes); `1` requires every term. Per-call override: `cerefox search --min-term-coverage`. Leave unset against pre-0.9.1 servers. |
133
136
  | `CEREFOX_EMBED_MAX_INPUT_CHARS` | `20000` | Safety cap on the characters sent to the embedding model per input. The full chunk content is always stored and reconstructed untouched; only the embedding uses the (rare) truncated prefix, so an oversized chunk can never fail an ingest. |
134
137
  | `CEREFOX_MODELS_DIR` | `~/.cerefox/models` (in-container: inside the data volume) | Where the local embedder caches downloaded model weights (Cerefox Local; `CEREFOX_EMBEDDER=local`). |
@@ -40,7 +40,14 @@ in — even after the current version has moved to format 2.
40
40
  - A document **moves to format 2 automatically the next time it is edited/saved**
41
41
  (it gets re-chunked by the new chunker).
42
42
  - If you want to convert everything now rather than on next edit, run
43
- `cerefox server reindex` (re-chunks + re-embeds the whole knowledge base).
43
+ `cerefox server migrate-format`. It re-ingests each legacy document through
44
+ the normal pipeline (re-chunk + re-embed + stamp the current format), which
45
+ costs embedding spend — so it is opt-in, supports `--dry-run` and `--limit`,
46
+ and skips any document that changes mid-run rather than overwriting it.
47
+
48
+ > **Not `cerefox server reindex`.** Reindex refreshes *embeddings* on the
49
+ > existing chunk rows; it never re-chunks, so it cannot advance the stored
50
+ > format. Earlier versions of this guide said otherwise (#164).
44
51
 
45
52
  `cerefox doctor` reports how many documents still use the legacy format — purely
46
53
  informational, never a failure. A fresh install shows zero.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cerefox/memory",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
4
4
  "description": "Cerefox — user-owned shared memory for AI agents. CLI + stdio MCP server + web UI + ingestion for a knowledge base on your own Supabase project (or fully self-hosted with Cerefox Local).",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://github.com/fstamatelopoulos/cerefox",