@cerefox/memory 1.1.0 → 1.2.0

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-DD7DDGGU.js"></script>
18
+ <script type="module" crossorigin src="/app/assets/index-B1pgikxA.js"></script>
19
19
  <link rel="stylesheet" crossorigin href="/app/assets/index-C1JXZA9m.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.1.0";
21
+ export const EF_VERSION = "1.2.0";
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.1.0";
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.1.0-beta.7";
31
+ export const EF_LAST_CHANGED = "1.2.0";
32
32
 
33
33
  /**
34
34
  * The 8 peer EFs the cerefox-mcp aggregator probes (excludes cerefox-mcp
@@ -65,6 +65,13 @@ export const auditLogTool: ToolDefinition = {
65
65
  name: "cerefox_get_audit_log",
66
66
  description:
67
67
  "Retrieve audit log entries showing who changed what and when. Supports filtering by document, author, operation type, and time range. Returns entries with document titles, author attribution, size changes, and descriptions.",
68
+ // Read-only: touches nothing. Safe for a client to run without prompting.
69
+ annotations: {
70
+ title: "Read audit log",
71
+ readOnlyHint: true,
72
+ idempotentHint: true,
73
+ openWorldHint: false,
74
+ },
68
75
  inputSchema: {
69
76
  type: "object",
70
77
  required: [],
@@ -57,6 +57,13 @@ export const getDocumentTool: ToolDefinition = {
57
57
  name: "cerefox_get_document",
58
58
  description:
59
59
  "Retrieve the full reconstructed content of a document. Pass version_id to retrieve an archived version; omit it (or pass null) for the current version. Version UUIDs are returned by cerefox_list_versions. The response header includes the document's current content_hash — pass it back as expected_content_hash when updating via cerefox_ingest (optimistic concurrency).",
60
+ // Read-only: touches nothing. Safe for a client to run without prompting.
61
+ annotations: {
62
+ title: "Read document",
63
+ readOnlyHint: true,
64
+ idempotentHint: true,
65
+ openWorldHint: false,
66
+ },
60
67
  inputSchema: {
61
68
  type: "object",
62
69
  required: ["document_id"],
@@ -71,6 +71,13 @@ export const getHelpTool: ToolDefinition = {
71
71
  name: "cerefox_get_help",
72
72
  description:
73
73
  "Retrieve Cerefox's own agent-usage guidance (the AGENT_QUICK_REFERENCE.md content). Call with no arguments to get the full reference + a section index. Call with `topic` to get a single section (case-insensitive substring match against H2 headings). Use this whenever you're uncertain about Cerefox conventions — link forms, project-membership semantics, update workflows, etc.",
74
+ // Read-only: touches nothing. Safe for a client to run without prompting.
75
+ annotations: {
76
+ title: "Read Cerefox conventions",
77
+ readOnlyHint: true,
78
+ idempotentHint: true,
79
+ openWorldHint: false,
80
+ },
74
81
  inputSchema: {
75
82
  type: "object",
76
83
  properties: {
@@ -350,6 +350,17 @@ async function handler(
350
350
  export const ingestTool: ToolDefinition = {
351
351
  name: "cerefox_ingest",
352
352
  description: "Save a note or document to the Cerefox knowledge base.",
353
+ /** Destructive: `project_names` REPLACES the document's project memberships, and
354
+ * memberships have no version history — a partial list silently drops the rest.
355
+ * Content itself is version-snapshotted and guarded by expected_content_hash, so
356
+ * the destructive part is the membership replace, not the body. */
357
+ annotations: {
358
+ title: "Save or update a document",
359
+ readOnlyHint: false,
360
+ destructiveHint: true,
361
+ idempotentHint: false,
362
+ openWorldHint: false,
363
+ },
353
364
  inputSchema: {
354
365
  type: "object",
355
366
  required: ["title", "content"],
@@ -41,6 +41,13 @@ export const listMetadataKeysTool: ToolDefinition = {
41
41
  name: "cerefox_list_metadata_keys",
42
42
  description:
43
43
  "List all metadata keys currently in use across documents in the Cerefox knowledge base. Returns each key with its document count and up to 5 example values.",
44
+ // Read-only: touches nothing. Safe for a client to run without prompting.
45
+ annotations: {
46
+ title: "List metadata keys",
47
+ readOnlyHint: true,
48
+ idempotentHint: true,
49
+ openWorldHint: false,
50
+ },
44
51
  inputSchema: {
45
52
  type: "object",
46
53
  properties: {
@@ -45,6 +45,13 @@ export const listProjectsTool: ToolDefinition = {
45
45
  name: "cerefox_list_projects",
46
46
  description:
47
47
  "List all projects with their names and IDs. Use this to discover available projects before filtering by project_name in other tools.",
48
+ // Read-only: touches nothing. Safe for a client to run without prompting.
49
+ annotations: {
50
+ title: "List projects",
51
+ readOnlyHint: true,
52
+ idempotentHint: true,
53
+ openWorldHint: false,
54
+ },
48
55
  inputSchema: {
49
56
  type: "object",
50
57
  properties: {
@@ -53,6 +53,13 @@ export const listVersionsTool: ToolDefinition = {
53
53
  name: "cerefox_list_versions",
54
54
  description:
55
55
  "List all archived versions of a document, newest first. Returns version_id (use with cerefox_get_document), version_number, source, chunk_count, total_chars, and created_at.",
56
+ // Read-only: touches nothing. Safe for a client to run without prompting.
57
+ annotations: {
58
+ title: "List document versions",
59
+ readOnlyHint: true,
60
+ idempotentHint: true,
61
+ openWorldHint: false,
62
+ },
56
63
  inputSchema: {
57
64
  type: "object",
58
65
  required: ["document_id"],
@@ -124,6 +124,13 @@ export const metadataSearchTool: ToolDefinition = {
124
124
  name: "cerefox_metadata_search",
125
125
  description:
126
126
  "Find or list documents by metadata key-value criteria without a text search term. Use to discover documents tagged with specific attributes, browse by taxonomy, retrieve messages/tasks by type and status, or list all documents in a project (pass project_name alone). At least one of metadata_filter, project_name, updated_since, or created_since must be supplied; results are ordered newest-updated first.",
127
+ // Read-only: touches nothing. Safe for a client to run without prompting.
128
+ annotations: {
129
+ title: "Find documents by metadata",
130
+ readOnlyHint: true,
131
+ idempotentHint: true,
132
+ openWorldHint: false,
133
+ },
127
134
  inputSchema: {
128
135
  type: "object",
129
136
  properties: {
@@ -81,6 +81,14 @@ export const setRelationTool: ToolDefinition = {
81
81
  "contradicts, duplicates) write both directions. `supersedes` marks the target " +
82
82
  "superseded; `contradicts` marks both stale. Any other type string is accepted " +
83
83
  "and stored, just without special behaviour. Re-setting the same edge updates it.",
84
+ /** Upsert: re-running with the same edge changes nothing. Adds an edge; removes nothing. */
85
+ annotations: {
86
+ title: "Link two documents",
87
+ readOnlyHint: false,
88
+ destructiveHint: false,
89
+ idempotentHint: true,
90
+ openWorldHint: false,
91
+ },
84
92
  inputSchema: {
85
93
  type: "object",
86
94
  required: ["source_id", "target_id", "rel_type"],
@@ -143,6 +151,14 @@ export const deleteRelationTool: ToolDefinition = {
143
151
  description:
144
152
  "Remove a typed relation between two documents. Symmetric types remove both " +
145
153
  "directions. Lifecycle status set by an earlier relation is NOT reverted.",
154
+ /** Removes an edge (and its mirror for symmetric types). Relations are not versioned, so the edge is gone. */
155
+ annotations: {
156
+ title: "Remove a relation",
157
+ readOnlyHint: false,
158
+ destructiveHint: true,
159
+ idempotentHint: true,
160
+ openWorldHint: false,
161
+ },
146
162
  inputSchema: {
147
163
  type: "object",
148
164
  required: ["source_id", "target_id", "rel_type"],
@@ -203,6 +219,13 @@ export const getRelationsTool: ToolDefinition = {
203
219
  "List every relation touching a document, in both directions (→ outbound, " +
204
220
  "← inbound). Shows each neighbour's title and lifecycle status, so an agent " +
205
221
  "can tell whether retrieved knowledge has been superseded or contradicted.",
222
+ // Read-only: traversal only, mutates nothing.
223
+ annotations: {
224
+ title: "List a document's relations",
225
+ readOnlyHint: true,
226
+ idempotentHint: true,
227
+ openWorldHint: false,
228
+ },
206
229
  inputSchema: {
207
230
  type: "object",
208
231
  required: ["document_id"],
@@ -267,6 +290,13 @@ export const getNeighborsTool: ToolDefinition = {
267
290
  "Use after cerefox_get_relations shows which types exist. depth > 1 follows " +
268
291
  "chains (useful for follows / reply_to); cycles terminate safely. Optional " +
269
292
  "from_time / to_time filter neighbours by their creation time.",
293
+ // Read-only: traversal only, mutates nothing.
294
+ annotations: {
295
+ title: "Walk the relation graph",
296
+ readOnlyHint: true,
297
+ idempotentHint: true,
298
+ openWorldHint: false,
299
+ },
270
300
  inputSchema: {
271
301
  type: "object",
272
302
  required: ["document_id", "rel_type"],
@@ -192,6 +192,13 @@ export const searchTool: ToolDefinition = {
192
192
  name: "cerefox_search",
193
193
  description:
194
194
  "Search the Cerefox personal knowledge base. Returns complete documents ranked by hybrid (FTS + semantic) relevance.",
195
+ // Read-only: touches nothing. Safe for a client to run without prompting.
196
+ annotations: {
197
+ title: "Search knowledge base",
198
+ readOnlyHint: true,
199
+ idempotentHint: true,
200
+ openWorldHint: false,
201
+ },
195
202
  inputSchema: {
196
203
  type: "object",
197
204
  required: ["query"],
@@ -72,6 +72,15 @@ export const setDocumentProjectsTool: ToolDefinition = {
72
72
  name: "cerefox_set_document_projects",
73
73
  description:
74
74
  "Set the document's project memberships to EXACTLY the given list. Destructive replace: any existing memberships not in this list are removed. Pass an empty list to clear all project memberships. Projects are looked up by name (case-insensitive); missing projects are created. Logged as update-metadata in the audit log — content is untouched. Use cerefox_ingest with project_names if you want to set memberships AND update content in one call. Use this tool when you only need to change project membership without re-writing the document body.",
75
+ /** Destructive by contract: any membership not in the list is removed, and an
76
+ * empty list clears all of them. No version history for memberships. */
77
+ annotations: {
78
+ title: "Replace project memberships",
79
+ readOnlyHint: false,
80
+ destructiveHint: true,
81
+ idempotentHint: true,
82
+ openWorldHint: false,
83
+ },
75
84
  inputSchema: {
76
85
  type: "object",
77
86
  required: ["document_id", "project_names"],
@@ -66,10 +66,44 @@ export interface ToolContext {
66
66
  accessPath: AccessPath;
67
67
  }
68
68
 
69
+ /**
70
+ * MCP tool annotations (spec revision 2025-03-26).
71
+ *
72
+ * Hints that let a client reason about a tool BEFORE calling it. Without them a
73
+ * tool inherits the spec defaults `readOnlyHint: false` and
74
+ * `destructiveHint: true` — i.e. "may do something irreversible" — so declaring
75
+ * nothing tells every client that `cerefox_search` is as dangerous as a
76
+ * destructive write. The usual result is that users blanket-approve the server,
77
+ * which drains the meaning from the prompt on the tools that genuinely warrant
78
+ * one.
79
+ *
80
+ * These are hints from a server the client may not trust, so a client must not
81
+ * use them as a security boundary. They exist to inform UX, not to enforce it.
82
+ */
83
+ export interface ToolAnnotations {
84
+ /** Human-readable label for UIs. */
85
+ title?: string;
86
+ /** The tool does not modify anything. */
87
+ readOnlyHint?: boolean;
88
+ /** The tool may perform IRREVERSIBLE updates. Only meaningful when the tool
89
+ * is not read-only. Static per tool: if any argument shape can destroy, the
90
+ * tool is destructive. */
91
+ destructiveHint?: boolean;
92
+ /** Repeated calls with the same arguments have no additional effect. */
93
+ idempotentHint?: boolean;
94
+ /** The tool reaches external entities (a web search) rather than a closed
95
+ * domain. False throughout Cerefox: every tool talks to the operator's own
96
+ * store. */
97
+ openWorldHint?: boolean;
98
+ }
99
+
69
100
  export interface ToolDefinition {
70
101
  name: string;
71
102
  description: string;
72
103
  inputSchema: JsonSchema;
104
+ /** See `ToolAnnotations`. Required in practice: a unit test fails if a tool
105
+ * omits it, so adding a tool forces the read-only/destructive decision. */
106
+ annotations?: ToolAnnotations;
73
107
  /** Returns the MCP `TextContent.text` body. Tools that fail throw; the
74
108
  * consumer's request wrapper translates thrown errors into JSON-RPC
75
109
  * `-32603` (internal error) responses, or `-32602` (invalid params)
@@ -0,0 +1,48 @@
1
+ -- 0018_ingest_defers_retention_to_config.sql — make the store's retention policy
2
+ -- actually take effect on the write path (#183, reported by @tdebasis).
3
+ --
4
+ -- v1.1.0 moved version retention into `cerefox_config` and changed
5
+ -- `cerefox_snapshot_version` to default its parameters to NULL so that
6
+ -- COALESCE(param, config, default) could fall through to the store's policy.
7
+ --
8
+ -- That half worked. `cerefox_ingest_document` — snapshot_version's ONLY caller —
9
+ -- kept the pre-1.1.0 concrete defaults:
10
+ --
11
+ -- p_retention_hours INT DEFAULT 48
12
+ -- p_cleanup_enabled BOOLEAN DEFAULT TRUE
13
+ --
14
+ -- and passed them straight through. So snapshot_version never once received NULL
15
+ -- on a real write, and never once consulted `cerefox_config`. The store-level
16
+ -- switch was inert on the only path that matters.
17
+ --
18
+ -- Consequences, all silent:
19
+ --
20
+ -- * `version_cleanup_enabled = false` did nothing. Pruning ran anyway.
21
+ -- * Migration 0016's fail-safe — which seeds `false` on existing stores during
22
+ -- the 1.1.0 upgrade precisely so history could not be quietly discarded —
23
+ -- was defeated by this.
24
+ -- * `version_retention_hours` did nothing. The window stayed at 48 hours, not
25
+ -- the configured value (or the 120h default from migration 0017).
26
+ -- * The v1.1.0 release notes promised "Nothing is deleted." That was false.
27
+ --
28
+ -- The damage is per-document and latent rather than immediate: cleanup only runs
29
+ -- for a document when THAT document is next written. So versions older than the
30
+ -- window survive until their document is edited, then vanish. On the maintainer's
31
+ -- store, 362 of 397 versions were older than 48h and still intact when this was
32
+ -- found — every one of them was one edit away from being pruned.
33
+ --
34
+ -- Fix: `cerefox_ingest_document`'s parameters default to NULL, so the store's
35
+ -- policy resolves. An explicit value still overrides for a single call.
36
+ --
37
+ -- Lives in rpcs.sql, which `cerefox server deploy` re-applies. This migration
38
+ -- exists so the schema version moves and operators are told to redeploy.
39
+ --
40
+ -- Idempotent: safe to re-run.
41
+
42
+ DO $$
43
+ BEGIN
44
+ RAISE NOTICE
45
+ 'Migration 0018: cerefox_ingest_document now defers version retention to '
46
+ 'cerefox_config. Before this, version_cleanup_enabled and '
47
+ 'version_retention_hours were silently ignored on every write (#183).';
48
+ END $$;
@@ -1268,8 +1268,10 @@ $$;
1268
1268
  -- content, char_count, embedding (float[]), embedder (text)
1269
1269
  -- p_author, p_author_type : for audit entry
1270
1270
  -- p_source_label : version source label for snapshot ('file','paste','agent','manual')
1271
- -- p_retention_hours : for version cleanup (default 48)
1272
- -- p_cleanup_enabled : whether version cleanup runs (default true)
1271
+ -- p_retention_hours : version-cleanup window. NULL (default) = use the store's
1272
+ -- `version_retention_hours` from cerefox_config.
1273
+ -- p_cleanup_enabled : whether cleanup runs. NULL (default) = use the store's
1274
+ -- `version_cleanup_enabled`.
1273
1275
  -- p_expected_content_hash : optimistic-concurrency token (iter-32). On the UPDATE
1274
1276
  -- path this must equal the document's current content_hash —
1275
1277
  -- the caller proves they based their edit on the live version.
@@ -1301,8 +1303,14 @@ CREATE FUNCTION cerefox_ingest_document(
1301
1303
  p_author TEXT DEFAULT 'unknown',
1302
1304
  p_author_type TEXT DEFAULT 'user',
1303
1305
  p_source_label TEXT DEFAULT 'manual',
1304
- p_retention_hours INT DEFAULT 48,
1305
- p_cleanup_enabled BOOLEAN DEFAULT TRUE,
1306
+ -- NULL, so `cerefox_snapshot_version` falls through to the store's policy in
1307
+ -- cerefox_config. These carried concrete defaults (48 / TRUE) until v1.1.2,
1308
+ -- which silently defeated the whole store-level retention feature: this is
1309
+ -- snapshot_version's ONLY caller, so it never once received NULL and never
1310
+ -- once consulted the config (#183, reported by @tdebasis). A caller may still
1311
+ -- pass explicit values to override the store for one call.
1312
+ p_retention_hours INT DEFAULT NULL,
1313
+ p_cleanup_enabled BOOLEAN DEFAULT NULL,
1306
1314
  p_expected_content_hash TEXT DEFAULT NULL,
1307
1315
  p_last_write_wins BOOLEAN DEFAULT FALSE,
1308
1316
  -- content_format for the chunks being written (iter-28D). 2 = exact-partition
@@ -2325,7 +2333,7 @@ SET search_path = public, pg_catalog
2325
2333
  AS $$
2326
2334
  -- Keep in lockstep with the `@version:` marker in schema.sql (cut_release.ts
2327
2335
  -- enforces it). Bump whenever schema.sql OR rpcs.sql changes.
2328
- SELECT '0.10.4'::TEXT;
2336
+ SELECT '0.10.5'::TEXT;
2329
2337
  $$;
2330
2338
 
2331
2339
  -- ── 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.10.4
8
+ -- @version: 0.10.5
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 —
@@ -68,6 +68,8 @@ async function buildToolList(supabase: MCPSupabaseClient) {
68
68
  name: t.name,
69
69
  description: t.description,
70
70
  inputSchema: t.inputSchema,
71
+ // MCP 2025-03-26 tool annotations; MCP_VERSION already declares that revision.
72
+ ...(t.annotations ? { annotations: t.annotations } : {}),
71
73
  }));
72
74
  }
73
75
 
@@ -145,13 +145,20 @@ cerefox-local start # start a stopped container
145
145
  cerefox-local stop # stop it (your data persists in the Docker volume)
146
146
  cerefox-local restart
147
147
  cerefox-local logs -f # follow the logs
148
- cerefox-local upgrade # upgrade to the newest release + recreate (keeps data + OPENAI key)
148
+ cerefox-local upgrade # newest stable release + recreate (keeps data + OPENAI key)
149
149
  cerefox-local upgrade v1.2.3 # pin an exact version (also how you downgrade)
150
150
  cerefox-local upgrade --latest # follow the moving :latest tag from now on
151
151
  cerefox-local uninstall # remove the container, KEEP the data volume
152
152
  cerefox-local uninstall --purge # remove the container AND delete the data volume
153
153
  ```
154
154
 
155
+ > **Coming from an image older than v1.1.0?** Run `cerefox-local upgrade v1.1.1`
156
+ > (an explicit tag) once. A bare `upgrade` on those versions re-pulled the pinned
157
+ > tag and reported success without changing anything (#153), and it cannot fix
158
+ > itself: the launcher on your host is refreshed out of the container image, so an
159
+ > old image keeps reinstalling the old launcher. See
160
+ > [`upgrading.md`](upgrading.md).
161
+
155
162
  `upgrade` is the single update path: it pulls the newest image, recreates the container,
156
163
  and refreshes the `cerefox-local` script itself. Because the CLI, web server, PostgREST,
157
164
  and database schema all ship together in one versioned image, they never drift out of
@@ -18,6 +18,33 @@ to re-run.
18
18
  > repo `.env` to `~/.cerefox/.env`, deploy the server, and wire up your agent.
19
19
  > After that you're on the end-user path below.
20
20
 
21
+ > ### Cerefox Local on an image older than v1.1.0: upgrade once with an explicit tag
22
+ >
23
+ > If your container is on **v1.0.6 or earlier**, run this once:
24
+ >
25
+ > ```bash
26
+ > cerefox-local upgrade v1.1.1 # or any newer tag
27
+ > ```
28
+ >
29
+ > A bare `cerefox-local upgrade` will **not** get you off an old image, and it
30
+ > fails quietly: it prints "Pulling …", "container (re)started" and "refreshed
31
+ > cerefox-local from the new image", having changed nothing. Before v1.1.0 a bare
32
+ > `upgrade` re-pulled the **pinned** tag rather than resolving the newest release
33
+ > (#153).
34
+ >
35
+ > The reason it cannot fix itself is a bootstrap loop: the `cerefox-local`
36
+ > launcher on your host is refreshed *out of the container image* at the end of
37
+ > every upgrade, so an old image keeps reinstalling the old launcher. Naming a
38
+ > tag explicitly bypasses the pin, pulls the new image, and the new launcher
39
+ > comes with it. `curl … install-local.sh | sh` also works, since the installer
40
+ > writes the launcher directly.
41
+ >
42
+ > **After that, a bare `cerefox-local upgrade` behaves as you would expect**: it
43
+ > resolves the newest *stable* release and pins that exact version. It does not
44
+ > start following the moving `:latest` tag — that is what `upgrade --latest`
45
+ > does, and automatic operations (`init`, `start`, `restart`) never follow a
46
+ > moving tag by design (#100).
47
+
21
48
  ## End-user upgrade
22
49
 
23
50
  > ### Upgrading to v1.1.0 — `cerefox server deploy` is required
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cerefox/memory",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
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",