@cerefox/memory 1.0.4 → 1.0.6
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/dist/bin/cerefox.js +60 -43
- package/dist/frontend/assets/{index-Csj-6UHY.js → index-VeqA60-v.js} +2 -2
- package/dist/frontend/assets/{index-Csj-6UHY.js.map → index-VeqA60-v.js.map} +1 -1
- package/dist/frontend/index.html +1 -1
- package/dist/server-assets/_shared/ef-meta/index.ts +2 -2
- package/dist/server-assets/_shared/mcp-tools/_utils.ts +48 -12
- package/dist/server-assets/_shared/mcp-tools/search.ts +2 -2
- package/dist/server-assets/db/rpcs.sql +20 -4
- package/dist/server-assets/db/schema.sql +1 -1
- package/docs/guides/configuration.md +8 -5
- package/package.json +1 -1
package/dist/frontend/index.html
CHANGED
|
@@ -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-
|
|
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.
|
|
21
|
+
export const EF_VERSION = "1.0.6";
|
|
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.4";
|
|
|
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.
|
|
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
|
-
(
|
|
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
|
-
|
|
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
|
-
|
|
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) ??
|
|
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
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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`.
|
|
123
|
-
>
|
|
124
|
-
>
|
|
125
|
-
> `
|
|
126
|
-
>
|
|
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`). |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cerefox/memory",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
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",
|