@cerefox/memory 1.3.0 → 1.5.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.
- package/AGENT_GUIDE.md +90 -8
- package/AGENT_QUICK_REFERENCE.md +91 -5
- package/dist/bin/cerefox.js +715 -387
- package/dist/frontend/assets/{index-B1pgikxA.js → index-D9z5yV9u.js} +30 -30
- package/dist/frontend/assets/index-D9z5yV9u.js.map +1 -0
- package/dist/frontend/index.html +1 -1
- package/dist/server-assets/_shared/ef-meta/index.ts +20 -2
- package/dist/server-assets/_shared/mcp-tools/audit-log.ts +14 -1
- package/dist/server-assets/_shared/mcp-tools/get-document.ts +67 -3
- package/dist/server-assets/_shared/mcp-tools/get-help-content.ts +6 -4
- package/dist/server-assets/_shared/mcp-tools/get-help.ts +56 -0
- package/dist/server-assets/_shared/mcp-tools/ingest.ts +2 -1
- package/dist/server-assets/_shared/mcp-tools/list-versions.ts +12 -1
- package/dist/server-assets/_shared/mcp-tools/partial-edits.ts +82 -14
- package/dist/server-assets/_shared/partial-edits/index.ts +222 -13
- package/dist/server-assets/db/migrations/0021_rename_section_audit_op.sql +36 -0
- package/dist/server-assets/db/migrations/0022_rls_on_document_relations.sql +38 -0
- package/dist/server-assets/db/rpcs.sql +4 -1
- package/dist/server-assets/db/schema.sql +12 -2
- package/docs/guides/configuration.md +1 -1
- package/docs/guides/connect-agents.md +14 -1
- package/package.json +1 -1
- package/dist/frontend/assets/index-B1pgikxA.js.map +0 -1
package/AGENT_GUIDE.md
CHANGED
|
@@ -124,7 +124,7 @@ Retrieve the complete text of a document by its UUID.
|
|
|
124
124
|
| `outline` | No | `true` returns the document's **structure instead of its content**: heading paths, levels, per-section sizes, plus `content_hash` and total size. Much cheaper than a full read. The paths are exactly what the edit tools take as `anchor_heading`. |
|
|
125
125
|
| `requestor` | No | Your agent name. |
|
|
126
126
|
|
|
127
|
-
Use this when search returns partial results, or to read a previous version before restoring it. The response header includes the document's current `content_hash` — pass it back as `expected_content_hash` when updating via `cerefox_ingest` or editing via `cerefox_insert` / `cerefox_edit`.
|
|
127
|
+
Use this when search returns partial results, or to read a previous version before restoring it. Pass `outline: true` for the heading structure without the body, or `section: "## Heading"` for one section's text — which is exactly what a `replace_section` on that anchor would overwrite, so read it before replacing a section you did not write yourself. The response header includes the document's current `content_hash` — pass it back as `expected_content_hash` when updating via `cerefox_ingest` or editing via `cerefox_insert` / `cerefox_edit`.
|
|
128
128
|
|
|
129
129
|
**Before editing a document you have not read this session, call it with `outline: true` first.** It answers the three questions an edit needs — what are the anchors, how big is each section, what is the current hash — without pulling the body into your context.
|
|
130
130
|
|
|
@@ -157,7 +157,7 @@ Change parts of a document: **one to many operations applied atomically in a sin
|
|
|
157
157
|
| Parameter | Required | Description |
|
|
158
158
|
|-----------|----------|-------------|
|
|
159
159
|
| `document_id` | Yes | UUID of the document. |
|
|
160
|
-
| `operations` | Yes | Array of operations, applied **in order, all-or-nothing**. Each is `{op, ...}` with `op` one of `insert` (same fields as `cerefox_insert`), `replace_section` (`anchor_heading`, `text`; swaps the body, keeps the heading), `delete_section` (`anchor_heading`, optional `scope`: `body_only` default keeps the heading, `heading_and_body` removes it too). |
|
|
160
|
+
| `operations` | Yes | Array of operations, applied **in order, all-or-nothing**. Each is `{op, ...}` with `op` one of `insert` (same fields as `cerefox_insert`), `replace_section` (`anchor_heading`, `text`; swaps the body, keeps the heading), `delete_section` (`anchor_heading`, optional `scope`: `body_only` default keeps the heading, `heading_and_body` removes it too), `rename_section` (`anchor_heading`, `new_heading`; changes the heading TEXT only — body and position untouched, and the level must stay the same, since changing it would re-parent everything nested underneath). |
|
|
161
161
|
| `expected_content_hash` | **Yes** | One token for the whole call. No `last_write_wins`. |
|
|
162
162
|
| `requestor` | No | Your agent name. |
|
|
163
163
|
|
|
@@ -169,22 +169,25 @@ heading, and the footer — that is a whole-document change wearing a local
|
|
|
169
169
|
disguise, and `cerefox_ingest` is the right tool. Section-scoped edits would take
|
|
170
170
|
several calls, each individually valid, with the document briefly inconsistent
|
|
171
171
|
between them. An agent hit exactly this and correctly stopped rather than
|
|
172
|
-
contorting the tools. Related
|
|
173
|
-
`replace_section` preserves it by design
|
|
174
|
-
a re-ingest
|
|
172
|
+
contorting the tools. (Related, and **fixed in v1.4.0**: a heading's own text
|
|
173
|
+
used to be unchangeable, because `replace_section` preserves it by design, so a
|
|
174
|
+
stale date inside a heading forced a re-ingest. `rename_section` now changes the
|
|
175
|
+
heading and nothing else.)
|
|
175
176
|
|
|
176
177
|
**One sharp edge worth knowing.** A section runs to the next heading of the same
|
|
177
178
|
or higher level — **or to the end of the document**. So the last section owns
|
|
178
179
|
everything appended after it: an `end_of_document` insert becomes part of that
|
|
179
180
|
section's body, and a later `replace_section` or `delete_section` on that heading
|
|
180
181
|
removes it along with the rest. This is correct addressing, not a bug, but it is
|
|
181
|
-
silent.
|
|
182
|
-
|
|
182
|
+
silent. Since v1.4.0 any edit that removes content says so with the amount, and
|
|
183
|
+
a replace or delete on the LAST section gets the full explanation whatever the
|
|
184
|
+
size — the loss that matters here is *small* precisely because it was just
|
|
185
|
+
added. The previous content is in `cerefox_list_versions`. To append somewhere a later section edit
|
|
183
186
|
cannot swallow, give the appended material its own heading.
|
|
184
187
|
|
|
185
188
|
**To change a single line**, `replace_section` on its smallest enclosing heading and resend just that section. That is the intended granularity — line-level anchors were deliberately excluded because they silently edit the wrong place.
|
|
186
189
|
|
|
187
|
-
The audit trail records each operation distinctly (`insert` / `replace-section` / `delete-section`), so *added to*, *rewrote* and *removed* stay distinguishable from a full rewrite.
|
|
190
|
+
The audit trail records each operation distinctly (`insert` / `replace-section` / `delete-section` / `rename-section`), so *added to*, *rewrote* and *removed* stay distinguishable from a full rewrite.
|
|
188
191
|
|
|
189
192
|
---
|
|
190
193
|
|
|
@@ -600,3 +603,82 @@ printf '...new content...' \
|
|
|
600
603
|
cerefox audit list --json --limit 1000 --requestor "claude-code" \
|
|
601
604
|
| jq 'select(.author_type == "agent")'
|
|
602
605
|
```
|
|
606
|
+
|
|
607
|
+
## Timestamps are UTC
|
|
608
|
+
|
|
609
|
+
Every timestamp Cerefox returns — `created_at` on audit entries, version
|
|
610
|
+
history, document metadata — is **UTC**, and now carries its `Z` marker so it
|
|
611
|
+
cannot be mistaken for local time.
|
|
612
|
+
|
|
613
|
+
**When you write a date into a document's CONTENT, use your own clock, not a
|
|
614
|
+
Cerefox timestamp.** These are different things: a timestamp records when the
|
|
615
|
+
server stored something; a date in a log entry or a heading is authored content
|
|
616
|
+
and belongs to your timezone. An agent working a Pacific afternoon read
|
|
617
|
+
`2026-08-11` from version history, wrote "8/11" into its entries, and put a
|
|
618
|
+
day's work in the future — the timestamp was correct, and copying it into
|
|
619
|
+
content was not.
|
|
620
|
+
|
|
621
|
+
Cerefox deliberately does not convert to local time on the API or MCP paths.
|
|
622
|
+
"Local" has no server-side meaning: the remote MCP server runs in a cloud
|
|
623
|
+
function whose local time *is* UTC, while a local MCP server runs in yours, so
|
|
624
|
+
the same document would report two different times depending on transport. The
|
|
625
|
+
web UI converts because a browser knows the viewer's timezone; nothing
|
|
626
|
+
server-side does.
|
|
627
|
+
|
|
628
|
+
## Mistakes that have actually happened
|
|
629
|
+
|
|
630
|
+
Each of these comes from a real agent session, and each is easy to make.
|
|
631
|
+
|
|
632
|
+
- **`cerefox_ingest` always replaces the ENTIRE document.** Never a section.
|
|
633
|
+
Before sending, check that the tool name matches the intent: if the intent is
|
|
634
|
+
"change one section", the call is `cerefox_edit` with `replace_section`. A
|
|
635
|
+
section-sized edit sent as a full ingest truncated a 13,000-character index to
|
|
636
|
+
a single word. It was recovered from version history within the minute, but
|
|
637
|
+
only because it was noticed immediately.
|
|
638
|
+
|
|
639
|
+
- **Do not include the anchor's own heading in your text.** `replace_section`
|
|
640
|
+
keeps the heading and `insert` places your text inside the section, so
|
|
641
|
+
including it produces two. This is now refused rather than silently applied,
|
|
642
|
+
but the shape is worth knowing: it happened twice in one session, the second
|
|
643
|
+
time while trying to repair the first. A *deeper* sub-heading inside your text
|
|
644
|
+
is fine.
|
|
645
|
+
|
|
646
|
+
- **Content between sections belongs to the section ABOVE it.** A section runs
|
|
647
|
+
to the next heading of the same or higher level, so a `---` rule, a note, or
|
|
648
|
+
any trailing text sitting just above the next heading is part of the section
|
|
649
|
+
before it — even when it visually reads as belonging below. Replacing that
|
|
650
|
+
section takes it too. An agent hit exactly this: a `---` that separated two
|
|
651
|
+
major sections disappeared when the section above it was replaced. The write
|
|
652
|
+
was correct by the addressing rules; the surprise is that "the end of this
|
|
653
|
+
section" is further down the page than it looks. Note the loss warning will
|
|
654
|
+
not catch it if your replacement text is longer than what it replaced, since
|
|
655
|
+
there is then no net loss to report.
|
|
656
|
+
|
|
657
|
+
- **Never partial-edit to fix a partial edit.** If a write leaves unexpected
|
|
658
|
+
structure, stop. Use `cerefox_list_versions`, retrieve the last good version,
|
|
659
|
+
and re-ingest cleanly. Repairing edits with more edits compounds the damage.
|
|
660
|
+
|
|
661
|
+
- **A rejected batch is safe.** Operations in one `cerefox_edit` are
|
|
662
|
+
all-or-nothing: if any is invalid, nothing is written. A refusal costs you a
|
|
663
|
+
retry, not data — so prefer one call for changes that belong together, and do
|
|
664
|
+
not split a batch to "make it more likely to succeed".
|
|
665
|
+
|
|
666
|
+
- **Read before replacing.** `cerefox_get_document(section: "## Heading")`
|
|
667
|
+
returns exactly what a `replace_section` on that anchor would overwrite. Use it
|
|
668
|
+
for any section you did not write in this session. The outline gives a
|
|
669
|
+
section's *size*, never its *text*.
|
|
670
|
+
|
|
671
|
+
- **Verify after writing** — read the result back before reporting success, and
|
|
672
|
+
report what the read actually shows.
|
|
673
|
+
|
|
674
|
+
- **Partial edits cannot change a document's stored TITLE.** `rename_section`
|
|
675
|
+
changes a heading inside the content; the title is a separate field and still
|
|
676
|
+
needs `cerefox_ingest`.
|
|
677
|
+
|
|
678
|
+
- **If a capability seems missing from one server, suspect your client first.**
|
|
679
|
+
Local and remote run the same code. Call `cerefox_get_help(topic: "server")`:
|
|
680
|
+
it reports the server's own version and the operations it registers. If that
|
|
681
|
+
disagrees with your tool list, the client is holding a list it fetched before
|
|
682
|
+
an upgrade — clients cache it at connect time. Ask the user to restart the
|
|
683
|
+
client. Do not record a capability difference between servers as a fact; every
|
|
684
|
+
such report so far has been a stale client.
|
package/AGENT_QUICK_REFERENCE.md
CHANGED
|
@@ -9,8 +9,8 @@ Cerefox is a persistent, shared knowledge base. You have **16 MCP tools** (15 of
|
|
|
9
9
|
| `cerefox_search` | Find documents (hybrid FTS + semantic) | `query` (required), `project_name`, `metadata_filter`, `requestor` |
|
|
10
10
|
| `cerefox_ingest` | Save or update a document | `title`, `content` (required), `document_id` (update by ID), `expected_content_hash` (**required on content updates** — see rule 9), `last_write_wins`, `update_if_exists`, `project_name` (single, non-destructive add on update), `project_names` (list, destructive replace on update), `metadata` (omit on update to keep existing tags; `{}` clears), `author` |
|
|
11
11
|
| `cerefox_insert` | **Add** to a document without resending it. Cannot destroy content. | `document_id`, `text`, `position` (`end_of_document`/`end_of_section`/`after_heading`/`before_heading`), `expected_content_hash` (required), `anchor_heading` (unless `end_of_document`), `section_part` |
|
|
12
|
-
| `cerefox_edit` | **Change** parts of a document: 1..n operations applied atomically | `document_id`, `operations` (`insert`/`replace_section`/`delete_section`), `expected_content_hash` (required) |
|
|
13
|
-
| `cerefox_get_document` | Get full document by ID (header includes `content_hash` — the update token), or with `outline: true` just its heading paths, sizes and hash | `document_id` (required), `outline` |
|
|
12
|
+
| `cerefox_edit` | **Change** parts of a document: 1..n operations applied atomically | `document_id`, `operations` (`insert`/`replace_section`/`delete_section`/`rename_section`), `expected_content_hash` (required) |
|
|
13
|
+
| `cerefox_get_document` | Get full document by ID (header includes `content_hash` — the update token), or with `outline: true` just its heading paths, sizes and hash, or with `section: "## Heading"` one section's text | `document_id` (required), `outline`, `section`, `section_part` |
|
|
14
14
|
| `cerefox_list_versions` | Version history of a document | `document_id` (required) |
|
|
15
15
|
| `cerefox_set_relation` ⚑ | Link two documents (`source --rel_type--> target`) | `source_id`, `target_id`, `rel_type` (required), `metadata`, `author` |
|
|
16
16
|
| `cerefox_delete_relation` ⚑ | Remove a relation | `source_id`, `target_id`, `rel_type` |
|
|
@@ -41,12 +41,19 @@ diff. Use the partial-edit tools instead:
|
|
|
41
41
|
2. **Add** → `cerefox_insert`. `end_of_document` is a plain append;
|
|
42
42
|
`end_of_section` adds inside a named section. It is structurally incapable of
|
|
43
43
|
removing anything, so "I meant to append" cannot become "I replaced the file".
|
|
44
|
-
3. **
|
|
44
|
+
3. **Look before you overwrite** — `cerefox_get_document(document_id,
|
|
45
|
+
section: "## Heading")` returns exactly the text a `replace_section` on that
|
|
46
|
+
anchor would destroy. The outline gives you a section's *size*, never its
|
|
47
|
+
*text*, so on a document you did not write yourself this is the difference
|
|
48
|
+
between a replace and a blind overwrite.
|
|
49
|
+
4. **Change or remove** → `cerefox_edit`. Put changes that belong together in
|
|
45
50
|
ONE call: they apply atomically, so a table row and the total it feeds cannot
|
|
46
51
|
end up disagreeing. To change a single line, `replace_section` on its
|
|
47
52
|
smallest enclosing heading — that is the intended granularity, not a
|
|
48
|
-
workaround.
|
|
49
|
-
|
|
53
|
+
workaround. To fix a stale heading (`## OPEN TODOs (as of ...)`), use
|
|
54
|
+
`rename_section`: it changes the heading text and leaves the body and
|
|
55
|
+
position alone.
|
|
56
|
+
5. All of them require `expected_content_hash` and **have no last-write-wins**. A
|
|
50
57
|
conflict means someone else changed the document; re-read and decide, do not
|
|
51
58
|
force it.
|
|
52
59
|
|
|
@@ -128,3 +135,82 @@ Same operations, same conventions. Full reference: [`docs/guides/cli.md`](docs/g
|
|
|
128
135
|
- Reads: `--requestor "<your-name>"`
|
|
129
136
|
|
|
130
137
|
Or have your user set `CEREFOX_AUTHOR_NAME` / `CEREFOX_AUTHOR_TYPE` / `CEREFOX_REQUESTOR_NAME` in their `.env` to apply defaults once.
|
|
138
|
+
|
|
139
|
+
## Timestamps are UTC
|
|
140
|
+
|
|
141
|
+
Every timestamp Cerefox returns — `created_at` on audit entries, version
|
|
142
|
+
history, document metadata — is **UTC**, and now carries its `Z` marker so it
|
|
143
|
+
cannot be mistaken for local time.
|
|
144
|
+
|
|
145
|
+
**When you write a date into a document's CONTENT, use your own clock, not a
|
|
146
|
+
Cerefox timestamp.** These are different things: a timestamp records when the
|
|
147
|
+
server stored something; a date in a log entry or a heading is authored content
|
|
148
|
+
and belongs to your timezone. An agent working a Pacific afternoon read
|
|
149
|
+
`2026-08-11` from version history, wrote "8/11" into its entries, and put a
|
|
150
|
+
day's work in the future — the timestamp was correct, and copying it into
|
|
151
|
+
content was not.
|
|
152
|
+
|
|
153
|
+
Cerefox deliberately does not convert to local time on the API or MCP paths.
|
|
154
|
+
"Local" has no server-side meaning: the remote MCP server runs in a cloud
|
|
155
|
+
function whose local time *is* UTC, while a local MCP server runs in yours, so
|
|
156
|
+
the same document would report two different times depending on transport. The
|
|
157
|
+
web UI converts because a browser knows the viewer's timezone; nothing
|
|
158
|
+
server-side does.
|
|
159
|
+
|
|
160
|
+
## Mistakes that have actually happened
|
|
161
|
+
|
|
162
|
+
Each of these comes from a real agent session, and each is easy to make.
|
|
163
|
+
|
|
164
|
+
- **`cerefox_ingest` always replaces the ENTIRE document.** Never a section.
|
|
165
|
+
Before sending, check that the tool name matches the intent: if the intent is
|
|
166
|
+
"change one section", the call is `cerefox_edit` with `replace_section`. A
|
|
167
|
+
section-sized edit sent as a full ingest truncated a 13,000-character index to
|
|
168
|
+
a single word. It was recovered from version history within the minute, but
|
|
169
|
+
only because it was noticed immediately.
|
|
170
|
+
|
|
171
|
+
- **Do not include the anchor's own heading in your text.** `replace_section`
|
|
172
|
+
keeps the heading and `insert` places your text inside the section, so
|
|
173
|
+
including it produces two. This is now refused rather than silently applied,
|
|
174
|
+
but the shape is worth knowing: it happened twice in one session, the second
|
|
175
|
+
time while trying to repair the first. A *deeper* sub-heading inside your text
|
|
176
|
+
is fine.
|
|
177
|
+
|
|
178
|
+
- **Content between sections belongs to the section ABOVE it.** A section runs
|
|
179
|
+
to the next heading of the same or higher level, so a `---` rule, a note, or
|
|
180
|
+
any trailing text sitting just above the next heading is part of the section
|
|
181
|
+
before it — even when it visually reads as belonging below. Replacing that
|
|
182
|
+
section takes it too. An agent hit exactly this: a `---` that separated two
|
|
183
|
+
major sections disappeared when the section above it was replaced. The write
|
|
184
|
+
was correct by the addressing rules; the surprise is that "the end of this
|
|
185
|
+
section" is further down the page than it looks. Note the loss warning will
|
|
186
|
+
not catch it if your replacement text is longer than what it replaced, since
|
|
187
|
+
there is then no net loss to report.
|
|
188
|
+
|
|
189
|
+
- **Never partial-edit to fix a partial edit.** If a write leaves unexpected
|
|
190
|
+
structure, stop. Use `cerefox_list_versions`, retrieve the last good version,
|
|
191
|
+
and re-ingest cleanly. Repairing edits with more edits compounds the damage.
|
|
192
|
+
|
|
193
|
+
- **A rejected batch is safe.** Operations in one `cerefox_edit` are
|
|
194
|
+
all-or-nothing: if any is invalid, nothing is written. A refusal costs you a
|
|
195
|
+
retry, not data — so prefer one call for changes that belong together, and do
|
|
196
|
+
not split a batch to "make it more likely to succeed".
|
|
197
|
+
|
|
198
|
+
- **Read before replacing.** `cerefox_get_document(section: "## Heading")`
|
|
199
|
+
returns exactly what a `replace_section` on that anchor would overwrite. Use it
|
|
200
|
+
for any section you did not write in this session. The outline gives a
|
|
201
|
+
section's *size*, never its *text*.
|
|
202
|
+
|
|
203
|
+
- **Verify after writing** — read the result back before reporting success, and
|
|
204
|
+
report what the read actually shows.
|
|
205
|
+
|
|
206
|
+
- **Partial edits cannot change a document's stored TITLE.** `rename_section`
|
|
207
|
+
changes a heading inside the content; the title is a separate field and still
|
|
208
|
+
needs `cerefox_ingest`.
|
|
209
|
+
|
|
210
|
+
- **If a capability seems missing from one server, suspect your client first.**
|
|
211
|
+
Local and remote run the same code. Call `cerefox_get_help(topic: "server")`:
|
|
212
|
+
it reports the server's own version and the operations it registers. If that
|
|
213
|
+
disagrees with your tool list, the client is holding a list it fetched before
|
|
214
|
+
an upgrade — clients cache it at connect time. Ask the user to restart the
|
|
215
|
+
client. Do not record a capability difference between servers as a fact; every
|
|
216
|
+
such report so far has been a stale client.
|