@cerefox/memory 0.10.4 → 0.11.1
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/AGENT_GUIDE.md +38 -15
- package/AGENT_QUICK_REFERENCE.md +13 -8
- package/dist/bin/cerefox.js +189 -69
- package/dist/frontend/assets/{index-DVXDQ7__.js → index-ojNhWSxm.js} +3 -3
- package/dist/frontend/assets/index-ojNhWSxm.js.map +1 -0
- package/dist/frontend/index.html +1 -1
- package/dist/server-assets/_shared/ef-meta/index.ts +1 -1
- package/dist/server-assets/_shared/mcp-tools/get-document.ts +6 -2
- package/dist/server-assets/_shared/mcp-tools/get-help-content.ts +6 -6
- package/dist/server-assets/_shared/mcp-tools/ingest.ts +75 -7
- package/dist/server-assets/_shared/mcp-tools/metadata-search.ts +3 -1
- package/dist/server-assets/_shared/mcp-tools/search.ts +4 -1
- package/dist/server-assets/db/rpcs.sql +89 -16
- package/dist/server-assets/db/schema.sql +1 -1
- package/dist/server-assets/supabase/functions/cerefox-get-document/index.ts +5 -1
- package/dist/server-assets/supabase/functions/cerefox-ingest/index.ts +71 -2
- package/docs/guides/agent-coordination.md +14 -1
- package/docs/guides/cli.md +23 -8
- package/docs/guides/connect-agents.md +44 -3
- package/package.json +1 -1
- package/dist/frontend/assets/index-DVXDQ7__.js.map +0 -1
|
@@ -17,7 +17,9 @@ import { isVersionRequest, versionResponse } from "../../../_shared/ef-meta/inde
|
|
|
17
17
|
* content string required Markdown content
|
|
18
18
|
* project_name string optional Project to assign to (looked up by name, created if absent)
|
|
19
19
|
* source string optional Origin label (default: "agent")
|
|
20
|
-
* metadata object optional Arbitrary JSONB metadata
|
|
20
|
+
* metadata object optional Arbitrary JSONB metadata. Omitted on an
|
|
21
|
+
* update → existing metadata is KEPT
|
|
22
|
+
* (v0.11.1); pass {} explicitly to clear.
|
|
21
23
|
*
|
|
22
24
|
* Response: { document_id, title, chunk_count, project_id? }
|
|
23
25
|
*/
|
|
@@ -40,6 +42,42 @@ interface IngestRequest {
|
|
|
40
42
|
update_if_exists?: boolean;
|
|
41
43
|
author?: string;
|
|
42
44
|
author_type?: string; // 'user' | 'agent'
|
|
45
|
+
// Optimistic concurrency (iter-32): REQUIRED on content updates — the
|
|
46
|
+
// content_hash of the version this edit was based on. Conflict → HTTP 409.
|
|
47
|
+
expected_content_hash?: string;
|
|
48
|
+
// Explicitly skip the concurrency check (external source of truth).
|
|
49
|
+
last_write_wins?: boolean;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Map the RPC's CEREFOX_CONFLICT / CEREFOX_TOKEN_REQUIRED errors to HTTP
|
|
53
|
+
// responses (409 conflict / 400 missing token). Returns null for other errors.
|
|
54
|
+
function concurrencyErrorResponse(
|
|
55
|
+
message: string,
|
|
56
|
+
headers: Record<string, string>,
|
|
57
|
+
): Response | null {
|
|
58
|
+
if (message.includes("CEREFOX_CONFLICT")) {
|
|
59
|
+
return new Response(
|
|
60
|
+
JSON.stringify({
|
|
61
|
+
error: "conflict",
|
|
62
|
+
message:
|
|
63
|
+
"Document changed since it was read. Re-read it (getDocument), merge your changes, and retry with the new expected_content_hash.",
|
|
64
|
+
detail: message,
|
|
65
|
+
}),
|
|
66
|
+
{ status: 409, headers },
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
if (message.includes("CEREFOX_TOKEN_REQUIRED")) {
|
|
70
|
+
return new Response(
|
|
71
|
+
JSON.stringify({
|
|
72
|
+
error: "expected_content_hash required",
|
|
73
|
+
message:
|
|
74
|
+
"Content updates require expected_content_hash (the content_hash returned by getDocument / searchKnowledgeBase / metadataSearch) or last_write_wins=true.",
|
|
75
|
+
detail: message,
|
|
76
|
+
}),
|
|
77
|
+
{ status: 400, headers },
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
return null;
|
|
43
81
|
}
|
|
44
82
|
|
|
45
83
|
interface Chunk {
|
|
@@ -428,7 +466,10 @@ Deno.serve(async (req: Request) => {
|
|
|
428
466
|
});
|
|
429
467
|
}
|
|
430
468
|
|
|
431
|
-
|
|
469
|
+
// metadata: null = "not provided" — the RPC keeps existing metadata on
|
|
470
|
+
// update and uses {} on create (v0.11.1; a `= {}` default here used to wipe
|
|
471
|
+
// a document's tags on every content update that didn't re-pass them).
|
|
472
|
+
const { title, content, document_id = null, project_name, source = "agent", metadata = null, update_if_exists = false, author = "agent", author_type = "agent", expected_content_hash = null, last_write_wins = false } = body;
|
|
432
473
|
|
|
433
474
|
// Validate + normalize project_names if provided (full-set destructive form)
|
|
434
475
|
let project_names: string[] | null = null;
|
|
@@ -525,6 +566,16 @@ Deno.serve(async (req: Request) => {
|
|
|
525
566
|
);
|
|
526
567
|
}
|
|
527
568
|
|
|
569
|
+
// Optimistic-concurrency fast-fail (iter-32): stale token fails BEFORE
|
|
570
|
+
// the embedding spend. Advisory only — the authoritative race-free check
|
|
571
|
+
// is inside the RPC (SELECT … FOR UPDATE).
|
|
572
|
+
if (!last_write_wins && expected_content_hash && expected_content_hash !== existingDoc.content_hash) {
|
|
573
|
+
return concurrencyErrorResponse(
|
|
574
|
+
`CEREFOX_CONFLICT: document ${existingDoc.id} changed since it was read (expected hash ${expected_content_hash}, current hash ${existingDoc.content_hash}).`,
|
|
575
|
+
headers,
|
|
576
|
+
)!;
|
|
577
|
+
}
|
|
578
|
+
|
|
528
579
|
// Content changed -- re-chunk, re-embed, ingest via RPC
|
|
529
580
|
const chunks = chunkMarkdown(content);
|
|
530
581
|
if (chunks.length === 0) {
|
|
@@ -562,9 +613,13 @@ Deno.serve(async (req: Request) => {
|
|
|
562
613
|
p_author: author,
|
|
563
614
|
p_author_type: author_type,
|
|
564
615
|
p_source_label: source,
|
|
616
|
+
p_expected_content_hash: expected_content_hash,
|
|
617
|
+
p_last_write_wins: last_write_wins,
|
|
565
618
|
});
|
|
566
619
|
|
|
567
620
|
if (ingestErr) {
|
|
621
|
+
const mapped = concurrencyErrorResponse(ingestErr.message ?? "", headers);
|
|
622
|
+
if (mapped) return mapped;
|
|
568
623
|
return new Response(JSON.stringify({ error: `Ingest RPC failed: ${ingestErr.message}` }), { status: 500, headers });
|
|
569
624
|
}
|
|
570
625
|
|
|
@@ -593,6 +648,7 @@ Deno.serve(async (req: Request) => {
|
|
|
593
648
|
chunk_count: chunks.length,
|
|
594
649
|
total_chars: totalChars,
|
|
595
650
|
updated: true,
|
|
651
|
+
content_hash: contentHash,
|
|
596
652
|
...(note && { note }),
|
|
597
653
|
}),
|
|
598
654
|
{ headers },
|
|
@@ -625,6 +681,14 @@ Deno.serve(async (req: Request) => {
|
|
|
625
681
|
);
|
|
626
682
|
}
|
|
627
683
|
|
|
684
|
+
// Optimistic-concurrency fast-fail (iter-32) — see ID-based path.
|
|
685
|
+
if (!last_write_wins && expected_content_hash && expected_content_hash !== existingDoc.content_hash) {
|
|
686
|
+
return concurrencyErrorResponse(
|
|
687
|
+
`CEREFOX_CONFLICT: document ${existingDoc.id} changed since it was read (expected hash ${expected_content_hash}, current hash ${existingDoc.content_hash}).`,
|
|
688
|
+
headers,
|
|
689
|
+
)!;
|
|
690
|
+
}
|
|
691
|
+
|
|
628
692
|
// Content changed — re-chunk, re-embed, ingest via RPC
|
|
629
693
|
const chunks = chunkMarkdown(content);
|
|
630
694
|
if (chunks.length === 0) {
|
|
@@ -667,9 +731,13 @@ Deno.serve(async (req: Request) => {
|
|
|
667
731
|
p_author: author,
|
|
668
732
|
p_author_type: author_type,
|
|
669
733
|
p_source_label: source,
|
|
734
|
+
p_expected_content_hash: expected_content_hash,
|
|
735
|
+
p_last_write_wins: last_write_wins,
|
|
670
736
|
});
|
|
671
737
|
|
|
672
738
|
if (ingestErr) {
|
|
739
|
+
const mapped = concurrencyErrorResponse(ingestErr.message ?? "", headers);
|
|
740
|
+
if (mapped) return mapped;
|
|
673
741
|
return new Response(
|
|
674
742
|
JSON.stringify({ error: `Ingest RPC failed: ${ingestErr.message}` }),
|
|
675
743
|
{ status: 500, headers },
|
|
@@ -701,6 +769,7 @@ Deno.serve(async (req: Request) => {
|
|
|
701
769
|
chunk_count: chunks.length,
|
|
702
770
|
total_chars: totalChars,
|
|
703
771
|
updated: true,
|
|
772
|
+
content_hash: contentHash,
|
|
704
773
|
}),
|
|
705
774
|
{ headers },
|
|
706
775
|
);
|
|
@@ -33,6 +33,19 @@ The coordination model is **asynchronous and knowledge-based**:
|
|
|
33
33
|
|
|
34
34
|
This is not real-time orchestration. It is persistent, searchable shared memory.
|
|
35
35
|
|
|
36
|
+
### Concurrent writers are conflict-guarded (v0.11+)
|
|
37
|
+
|
|
38
|
+
Shared memory means two agents can hold the same document at once. Cerefox
|
|
39
|
+
protects content updates with **optimistic concurrency control**: every read
|
|
40
|
+
surface returns the document's `content_hash`, and a content update must pass
|
|
41
|
+
it back as `expected_content_hash`. If the document changed in between, the
|
|
42
|
+
write fails with a **conflict** instead of silently overwriting the other
|
|
43
|
+
writer's work — the losing agent re-reads, merges, and retries with the fresh
|
|
44
|
+
hash. (Versioning remains the recovery net; the conflict guard is the
|
|
45
|
+
prevention layer.) An explicit `last_write_wins: true` exists for re-sync flows
|
|
46
|
+
where an external source of truth makes conflicts meaningless — agents should
|
|
47
|
+
never use it to silence a conflict. See `AGENT_GUIDE.md → Concurrent writers`.
|
|
48
|
+
|
|
36
49
|
---
|
|
37
50
|
|
|
38
51
|
## Coordination Patterns
|
|
@@ -53,7 +66,7 @@ A living document where agents record decisions, experiment outcomes, and lesson
|
|
|
53
66
|
|
|
54
67
|
**Example**: A coding agent working on a project records "Chose PostgreSQL RPC approach over application-level logic because..." in a decision log document. Next week, a different agent working on a related feature searches Cerefox, finds the decision log, and understands the rationale without re-deriving it.
|
|
55
68
|
|
|
56
|
-
**How it works**: Create a document with a structured format (date, context, decision, outcome). Use a consistent title or project tag so agents can find it. To add entries over time, re-ingest with `update_if_exists: true` (or `document_id`) — this replaces the document in place, so build the new full content by appending to the prior content you fetched.
|
|
69
|
+
**How it works**: Create a document with a structured format (date, context, decision, outcome). Use a consistent title or project tag so agents can find it. To add entries over time, re-ingest with `update_if_exists: true` (or `document_id`) — this replaces the document in place, so build the new full content by appending to the prior content you fetched, and pass the `content_hash` you fetched as `expected_content_hash` (two agents appending entries concurrently is exactly the conflict the guard catches).
|
|
57
70
|
|
|
58
71
|
**Best for**: Project-level institutional memory, avoiding repeated decisions, onboarding new agent sessions.
|
|
59
72
|
|
package/docs/guides/cli.md
CHANGED
|
@@ -43,9 +43,11 @@ cerefox document ingest --paste --title "<title>" [OPTIONS] # stdin
|
|
|
43
43
|
| `--title` | `-t` | str | filename stem | Document title. Required with `--paste`. |
|
|
44
44
|
| `--project-name` | `--project`, `-p` | str | _none_ | Project name to assign the document to (created if missing). |
|
|
45
45
|
| `--paste` | — | flag | off | Read markdown from stdin. Requires `--title`. |
|
|
46
|
-
| `--metadata` | `-m` | JSON |
|
|
46
|
+
| `--metadata` | `-m` | JSON | _not provided_ | Extra metadata as a JSON object, e.g. `'{"tags":["work"]}'`. **On update, omitting this keeps the document's existing metadata** (v0.11.1); pass `'{}'` to deliberately clear all metadata. |
|
|
47
47
|
| `--update-if-exists` | `-u` | flag | off | Title/source-path-based fallback update. Mutually exclusive with `--document-id`. |
|
|
48
48
|
| `--document-id` | `-i` | UUID | _none_ | Deterministic ID-based update. Errors if the document doesn't exist. |
|
|
49
|
+
| `--expected-content-hash` | — | sha256 | _none_ | **Required on content updates** (v0.11 optimistic concurrency): the `content_hash` of the version this edit is based on, shown by `cerefox document get` / `cerefox search`. Stale → conflict error (re-read, merge, retry). |
|
|
50
|
+
| `--last-write-wins` | — | flag | off | Skip the concurrency check and overwrite regardless of concurrent changes. For re-sync flows where an external source of truth makes conflicts meaningless. Recorded in the audit log. |
|
|
49
51
|
| `--source` | — | str | `paste` / `file` | Source label recorded on the document. |
|
|
50
52
|
| `--author` | — | str | `CEREFOX_AUTHOR_NAME` or `unknown` | Audit-log author identity. |
|
|
51
53
|
| `--author-type` | — | `user`\|`agent` | `CEREFOX_AUTHOR_TYPE` or `user` | Caller type. Agent writes auto-routed to `pending_review`. |
|
|
@@ -63,12 +65,19 @@ cerefox document ingest notes.md \
|
|
|
63
65
|
--author "claude-code" --author-type "agent" \
|
|
64
66
|
--project-name "research" --metadata '{"type":"design-doc"}'
|
|
65
67
|
|
|
66
|
-
# Deterministic update (preferred — agents should search → grab ID → ingest)
|
|
68
|
+
# Deterministic update (preferred — agents should search → grab ID + hash → ingest)
|
|
67
69
|
cerefox document ingest --paste --title "Same Title" \
|
|
68
70
|
--document-id "abc12345-..." \
|
|
71
|
+
--expected-content-hash "<hash from `document get`>" \
|
|
69
72
|
--author "claude-code" --author-type "agent"
|
|
70
73
|
```
|
|
71
74
|
|
|
75
|
+
> **Concurrency (v0.11+)**: content updates require `--expected-content-hash`
|
|
76
|
+
> (or an explicit `--last-write-wins`). On a conflict, re-run
|
|
77
|
+
> `cerefox document get <id>`, merge your changes into the latest content, and
|
|
78
|
+
> retry with the new hash. `document ingest-dir` and `guides ingest` bypass the
|
|
79
|
+
> check internally (the filesystem / npm package is their source of truth).
|
|
80
|
+
|
|
72
81
|
**Output**: human-readable summary line(s) — "Ingested" or "Updated" with the document ID, chunk count, character count.
|
|
73
82
|
|
|
74
83
|
**Exit codes**: `0` success, `1` on validation error (missing `--title`, invalid JSON, document-not-found, mutually-exclusive flags, etc.).
|
|
@@ -183,7 +192,7 @@ cerefox document get abc12345-... --version-id <version-uuid> # archived
|
|
|
183
192
|
cerefox document get abc12345-... | bat -l md # pipe to viewer
|
|
184
193
|
```
|
|
185
194
|
|
|
186
|
-
**Output**: title + metadata line, blank line, then raw markdown.
|
|
195
|
+
**Output**: title + metadata line + `content_hash` line (the optimistic-concurrency token — pass back via `document ingest --expected-content-hash` when updating), blank line, then raw markdown.
|
|
187
196
|
|
|
188
197
|
**MCP equivalent**: [`cerefox_get_document`](../../AGENT_GUIDE.md).
|
|
189
198
|
|
|
@@ -401,8 +410,8 @@ cerefox metadata search --metadata-filter '<json>' [OPTIONS]
|
|
|
401
410
|
|
|
402
411
|
| Flag | Type | Default | Description |
|
|
403
412
|
|---|---|---|---|
|
|
404
|
-
| `--metadata-filter <json>` (`-f`) | JSON |
|
|
405
|
-
| `--project-name <name>` (`-p`) | str | _none_ | Filter by project name. |
|
|
413
|
+
| `--metadata-filter <json>` (`-f`) | JSON | _none_ | Metadata filter, e.g. `'{"type":"decision-log"}'`. Optional since v0.11.1 — at least one of filter / `--project-name` / `--updated-since` / `--created-since` is required (parity with the MCP tool). |
|
|
414
|
+
| `--project-name <name>` (`-p`) | str | _none_ | Filter by project name. Sufficient on its own to list that project's documents. |
|
|
406
415
|
| `--updated-since TEXT` | ISO-8601 | _none_ | Documents updated after this timestamp. |
|
|
407
416
|
| `--created-since TEXT` | ISO-8601 | _none_ | Documents created after this timestamp. |
|
|
408
417
|
| `--limit INTEGER` | int | `10` | Max results. |
|
|
@@ -633,7 +642,7 @@ Every MCP parameter has an exact-name CLI flag (kebab-cased). Short forms exist
|
|
|
633
642
|
| MCP tool | CLI command |
|
|
634
643
|
|---|---|
|
|
635
644
|
| `cerefox_search(query, match_count, project_name, metadata_filter, requestor)` | `cerefox search "<q>" --match-count N --project-name <name> --metadata-filter '<json>' --requestor <name>` |
|
|
636
|
-
| `cerefox_ingest(title, content, project_name, metadata, update_if_exists, document_id, source, author, author_type)` (file) | `cerefox document ingest <path> --title <t> --project-name <n> --metadata '<json>' --update-if-exists\|--document-id <uuid> --source <s> --author <a> --author-type <t>` |
|
|
645
|
+
| `cerefox_ingest(title, content, project_name, metadata, update_if_exists, document_id, expected_content_hash, last_write_wins, source, author, author_type)` (file) | `cerefox document ingest <path> --title <t> --project-name <n> --metadata '<json>' --update-if-exists\|--document-id <uuid> --expected-content-hash <hash>\|--last-write-wins --source <s> --author <a> --author-type <t>` |
|
|
637
646
|
| `cerefox_ingest(...)` (paste) | `printf '...' \| cerefox document ingest --paste --title "<t>"` (same flags) |
|
|
638
647
|
| `cerefox_get_document(document_id, version_id, requestor)` | `cerefox document get <id> --version-id <vid> --requestor <name>` |
|
|
639
648
|
| `cerefox_list_versions(document_id, requestor)` | `cerefox document version list <id> --requestor <name>` |
|
|
@@ -709,12 +718,18 @@ cerefox document ingest-dir ./papers --extensions .md \
|
|
|
709
718
|
# Step 1: find it
|
|
710
719
|
cerefox search "the OAuth design doc" --match-count 1
|
|
711
720
|
|
|
712
|
-
# Step 2:
|
|
713
|
-
|
|
721
|
+
# Step 2: read it — note the id AND the `content_hash:` line (the concurrency token)
|
|
722
|
+
cerefox document get "<uuid>"
|
|
723
|
+
|
|
724
|
+
# Step 3: update in place, proving freshness with the hash from step 2
|
|
714
725
|
printf '%s' "$NEW_CONTENT" | cerefox document ingest --paste \
|
|
715
726
|
--title "OAuth 2.1 Design Document" \
|
|
716
727
|
--document-id "<uuid>" \
|
|
728
|
+
--expected-content-hash "<hash>" \
|
|
717
729
|
--author "claude-code" --author-type "agent"
|
|
730
|
+
|
|
731
|
+
# On a conflict error: repeat from step 2 (fresh content + fresh hash),
|
|
732
|
+
# merge your changes into the latest content, then retry.
|
|
718
733
|
```
|
|
719
734
|
|
|
720
735
|
### Unattended sync job
|
|
@@ -568,6 +568,11 @@ You have access to a personal knowledge base via the searchKnowledgeBase action.
|
|
|
568
568
|
When the user asks a question, always search the knowledge base first using a
|
|
569
569
|
relevant query. Present results by document title, citing the source for every claim.
|
|
570
570
|
Use ingestNote to save any new information the user asks you to remember.
|
|
571
|
+
When UPDATING an existing document, first call getDocument and note its
|
|
572
|
+
content_hash, then pass it as expected_content_hash on ingestNote. If you get a
|
|
573
|
+
409 conflict, the document changed underneath you: call getDocument again, merge
|
|
574
|
+
your changes into the latest content, and retry with the new hash — never
|
|
575
|
+
overwrite blindly.
|
|
571
576
|
```
|
|
572
577
|
|
|
573
578
|
### Path B verification
|
|
@@ -604,7 +609,7 @@ In the action editor, paste this schema (replace `<your-project-ref>`):
|
|
|
604
609
|
openapi: 3.1.0
|
|
605
610
|
info:
|
|
606
611
|
title: Cerefox Knowledge Base
|
|
607
|
-
version: 1.
|
|
612
|
+
version: 2.1.0
|
|
608
613
|
servers:
|
|
609
614
|
- url: https://<your-project-ref>.supabase.co/functions/v1
|
|
610
615
|
paths:
|
|
@@ -723,6 +728,10 @@ paths:
|
|
|
723
728
|
default: agent
|
|
724
729
|
metadata:
|
|
725
730
|
type: object
|
|
731
|
+
description: >
|
|
732
|
+
Arbitrary JSON metadata. On an UPDATE, omitting this keeps
|
|
733
|
+
the document's existing metadata (v2.1.0); pass {} to
|
|
734
|
+
deliberately clear all tags.
|
|
726
735
|
update_if_exists:
|
|
727
736
|
type: boolean
|
|
728
737
|
default: false
|
|
@@ -731,6 +740,23 @@ paths:
|
|
|
731
740
|
instead of creating a new one. The previous content is archived
|
|
732
741
|
as a version. If content is unchanged, the document is skipped
|
|
733
742
|
(no re-indexing). Ignored when document_id is provided.
|
|
743
|
+
expected_content_hash:
|
|
744
|
+
type: string
|
|
745
|
+
description: >
|
|
746
|
+
REQUIRED on content updates (optimistic concurrency, v2.0.0):
|
|
747
|
+
the content_hash of the version this edit was based on, as
|
|
748
|
+
returned by getDocument / searchKnowledgeBase / metadataSearch.
|
|
749
|
+
If the document changed since it was read, the update fails
|
|
750
|
+
with HTTP 409 — re-read the document, merge your changes,
|
|
751
|
+
retry with the new hash. Not needed when creating.
|
|
752
|
+
last_write_wins:
|
|
753
|
+
type: boolean
|
|
754
|
+
default: false
|
|
755
|
+
description: >
|
|
756
|
+
Explicitly skip the concurrency check and overwrite regardless
|
|
757
|
+
of concurrent changes. Use ONLY when an external source of
|
|
758
|
+
truth makes conflicts meaningless. Recorded in the audit log.
|
|
759
|
+
Never use it to silence a 409 conflict.
|
|
734
760
|
author:
|
|
735
761
|
type: string
|
|
736
762
|
description: >
|
|
@@ -753,8 +779,19 @@ paths:
|
|
|
753
779
|
project_id?, project_name?, # set when a project was assigned on create
|
|
754
780
|
skipped?, # true when identical content was deduplicated
|
|
755
781
|
updated?, # true when an existing doc was updated
|
|
782
|
+
content_hash?, # the NEW hash after an update (the next edit's token)
|
|
756
783
|
message?, # human note on dedup/skip/update
|
|
757
784
|
note? } # note when a flag (e.g. update_if_exists) was overridden
|
|
785
|
+
'400':
|
|
786
|
+
description: >
|
|
787
|
+
Missing expected_content_hash on a content update (and
|
|
788
|
+
last_write_wins not set). Read the document first, then retry
|
|
789
|
+
with its content_hash.
|
|
790
|
+
'409':
|
|
791
|
+
description: >
|
|
792
|
+
Conflict — the document changed since it was read. Call getDocument
|
|
793
|
+
for the latest content + content_hash, merge your changes, and
|
|
794
|
+
retry with the new hash. Do not overwrite blindly.
|
|
758
795
|
/cerefox-metadata:
|
|
759
796
|
post:
|
|
760
797
|
operationId: listMetadataKeys
|
|
@@ -802,7 +839,9 @@ paths:
|
|
|
802
839
|
description: >
|
|
803
840
|
Document content and metadata:
|
|
804
841
|
{ document_id, doc_title, full_content, chunk_count, total_chars,
|
|
805
|
-
is_archived, version_id }
|
|
842
|
+
is_archived, version_id, content_hash }.
|
|
843
|
+
content_hash is the document's CURRENT hash — pass it back as
|
|
844
|
+
expected_content_hash when updating via ingestNote.
|
|
806
845
|
'404':
|
|
807
846
|
description: Document not found
|
|
808
847
|
/cerefox-list-versions:
|
|
@@ -952,7 +991,9 @@ paths:
|
|
|
952
991
|
Array of matching documents:
|
|
953
992
|
[{ document_id, title, doc_metadata, review_status, source, created_at,
|
|
954
993
|
updated_at, total_chars, chunk_count, project_ids, project_names,
|
|
955
|
-
version_count, content }]
|
|
994
|
+
version_count, content_hash, content }].
|
|
995
|
+
content_hash is the concurrency token — pass it back as
|
|
996
|
+
expected_content_hash when updating via ingestNote.
|
|
956
997
|
```
|
|
957
998
|
|
|
958
999
|
**Step 3 — Configure authentication**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cerefox/memory",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.1",
|
|
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",
|