Files kept exactly
Some text has to come back exactly as it was written, such as the notes an agent keeps for itself. Put it
under a path and Geniffy keeps it character for character, spaces and line endings included. It also learns
from it like a note titled by its path, so context() and ask() recall what it says.
notes = "# Lumen\n- Priya Nair signs the renewal.\n"
mem.files.put("/memories/notes.md", notes) # creates or replaces it
mem.files.get("/memories/notes.md").text # notes, exactlyconst notes = "# Lumen\n- Priya Nair signs the renewal.\n";
await mem.files.put("/memories/notes.md", notes); // creates or replaces it
const { text } = await mem.files.get("/memories/notes.md"); // notes, exactlycurl -X PUT https://api.geniffy.com/v1/files \
-H "Authorization: Bearer $GENIFFY_API_KEY" \
-H "X-Geniffy-Space: customer_1042" \
-H "Content-Type: application/json" \
-d '{"path": "/memories/notes.md",
"text": "# Lumen\n- Priya Nair signs the renewal.\n"}'A write answers with the file and the source it is learned from. size is in characters:
{
"path": "/memories/notes.md",
"size": 40,
"updated_at": "2026-10-06T11:00:01.204913+00:00",
"created": true,
"source": {
"id": "6f1c0a3e9b2d4c8e8a7f5b3d2e1c0f9a",
"kind": "note",
"title": "/memories/notes.md",
"labels": {}
}
}This is what Claude's memory tool keeps its notes in. Any agent that writes its own notes as files can do the same, with any model.
What a write does
- A new path gets a file, and what it says is learned as a note titled by its path.
createdistrue. - A path that has a file gets the new text whole. Only the paragraphs that changed are learned, and what was removed is taken back, as when a record is sent again under your own id.
- Empty text is a file too: it is kept, with nothing learned from it, and emptying a file takes back what it taught.
- Labels work as on anything you add, such as
labels={"channel": "claude-memory"}. Written again, new labels replace the old ones; left out, the file keeps its own;{}clears them. See Labels. - A write is whole or not at all. If the memory service refuses it, a new file leaves nothing behind, and a file written before keeps its old text and what that taught. In the rare case the memory service can't be reached to take back what it may have kept of a new file, the error names the failed source that holds it: the file still reads as missing, and writing it again or deleting it clears that too.
Read and list
f = mem.files.get("/memories/notes.md") # .text, .size, .updated_at
page = mem.files.list("/memories/") # .files, .total and .nextconst f = await mem.files.get("/memories/notes.md"); // text, size, updated_at
const { files, total, next } = await mem.files.list({ prefix: "/memories/" });curl "https://api.geniffy.com/v1/files?prefix=/memories/" \
-H "Authorization: Bearer $GENIFFY_API_KEY" \
-H "X-Geniffy-Space: customer_1042"- A file's
sizeis in characters. A list holds each file's path, size andupdated_at, without the text, in path order. - A prefix is a plain prefix of paths, not a pattern:
/memories/is every file beneath that directory,/is every file, and/memories/prefinds/memories/preferences.md. Paths are compared character by character, so/Memories/is another directory. - A page holds up to 200 files, 100 unless you ask for more with
limit, andnextis the cursor of the page after, to pass ascursor; it isnull(Nonein Python) at the end. - A path with no file is a
404with the codenot_found; the SDKs raiseNotFoundError.
Move
mem.files.move("/memories/notes.md", "/memories/lumen.md") # a file
mem.files.move("/memories/drafts", "/memories/archive") # a directoryawait mem.files.move("/memories/notes.md", "/memories/lumen.md"); // a file
await mem.files.move("/memories/drafts", "/memories/archive"); // a directorycurl https://api.geniffy.com/v1/files/move \
-H "Authorization: Bearer $GENIFFY_API_KEY" \
-H "X-Geniffy-Space: customer_1042" \
-H "Content-Type: application/json" \
-d '{"from": "/memories/drafts", "to": "/memories/archive"}'When there is a file at from, it moves to to. When there isn't, every file beneath from/ moves to the
same place beneath to/. Each keeps its text and what it taught, and nothing is learned again. A move answers
with how many files moved. When a file is already where one of them would go, nothing moves and the call is a
409 with the code conflict; when there is nothing at from, a 404.
Delete
mem.files.delete("/memories/lumen.md") # a file
mem.files.delete_prefix("/memories/archive/") # every file under a prefixawait mem.files.delete("/memories/lumen.md"); // a file
await mem.files.deletePrefix("/memories/archive/"); // every file under a prefixcurl -X DELETE "https://api.geniffy.com/v1/files?prefix=/memories/archive/" \
-H "Authorization: Bearer $GENIFFY_API_KEY" \
-H "X-Geniffy-Space: customer_1042"Each file goes with its text and every memory only it taught. A memory that another source also taught stays,
because something still says it. Over HTTP, a prefix deletes up to 100 files a call and more: true says to
call again; the SDKs call again for you, and return how many files were deleted.
The rules for a path
- It starts with
/and is up to 255 characters. - None of its parts is empty,
.or.., and it holds no backslash and no control character. - A file's path doesn't end with
/; a prefix may.
A path that breaks a rule is refused with bad_path, and nothing is stored.
Files and the rest of a memory
- Each file is a source, a note titled by its path under the
external_idfile:and its path:sources.list()shows it, andDELETE /v1/sources?external_id=file:/memories/lumen.mddeletes it as deleting the file does. OnlyPUT /v1/fileswrites one, so adding a note, a page or a file under anexternal_idthat starts withfile:is refused withbad_external_id. - Recall reads them with everything else, and each line cites the file's path:
- Priya Nair signs the renewal. [/memories/notes.md, 2026-10-06]. - They go with the user. Forgetting a user takes their files, and
their copy,
export(), lists every file by its path, size and when it was written.files.get(path)hands back each one's text as written, so the copy stays small however much the files hold. - A file holds up to 2,000,000 characters, as a note does. Text with a NUL character (
\u0000) in it, or half of a surrogate pair, can't be kept exactly as sent, so it is refused withinvalid_request.