GeniffyDocs
Changelog Log In Get a key

Claude's memory tool

Claude's memory tool lets Claude keep notes as files in a /memories directory and read them back in later conversations. Claude asks for each file operation and your app carries it out, so your app decides where the files live. Keep them in Geniffy and each user's Claude keeps its notes in that user's memory: every file comes back exactly as Claude wrote it, and what it says is learned as well, so context() and ask() recall it anywhere else in your app.

Why keep the files in Geniffy

  • Nothing to host. No disk, bucket or table of your own for the files, and nothing to keep in step with your users.
  • Each file exactly as Claude wrote it. Claude edits a file by quoting a run of its text, so the text has to come back character for character. Geniffy keeps it that way, spaces and line endings included.
  • What Claude wrote is memory too. Each file is learned like a note titled by its path, so a support screen, a nightly job or a model from another provider can recall what Claude noted, with the file each line came from.
  • One memory per user. The tool works on a client bound to one of your users with space(), so one user's notes never reach another user's Claude.
  • Gone when the user goes. Deleting a file takes back what only it taught, forgetting a user takes their files with everything else, and the copy you give a user who asks what you hold includes every file.

Install

Terminal
pip install "geniffy[claude]"

geniffy[claude] installs the anthropic package as well (1.0 or later, on Python 3.10 or later), which geniffy.claude builds on; import geniffy on its own still needs nothing more. In TypeScript, geniffy/claude needs @anthropic-ai/sdk 0.72 or later, and the main geniffy entry still has no dependencies. Set ANTHROPIC_API_KEY, and GENIFFY_API_KEY from API keys in the Geniffy app.

Give each user's Claude a memory

assistant.py
import anthropic
from geniffy import Geniffy
from geniffy.claude import GeniffyMemoryTool

claude = anthropic.Anthropic()           # reads ANTHROPIC_API_KEY
geniffy = Geniffy()                      # reads GENIFFY_API_KEY


def chat(user_id: str, message: str) -> str:
    # This user's Claude keeps its notes in this user's memory, and no one else's.
    memory = GeniffyMemoryTool(geniffy.space(f"user_{user_id}"))

    final = claude.beta.messages.tool_runner(
        model="claude-opus-5-5",
        max_tokens=16000,
        tools=[memory],
        messages=[{"role": "user", "content": message}],
    ).until_done()
    return "".join(block.text for block in final.content if block.type == "text")

The tool runner carries out each command Claude sends and keeps going until Claude has its answer. The memory tool itself needs no beta header, and it works with every Claude model from Claude 4 on.

With the memory tool in a request, Claude looks at its memory directory before it starts. The first time it finds nothing and writes down what is worth keeping; the next conversation, a minute or a month later, starts by reading that back.

chat("1042", "Remember that I prefer email follow-ups, not calls.")
# a later conversation: Claude reads /memories first
chat("1042", "How should you follow up with me?")

See what Claude wrote

Claude names its own files, such as /memories/preferences.md. They are that user's files under /memories:

mem = geniffy.space("user_1042")
for f in mem.files.list("/memories/").files:    # by path
    print(f.path, f.size, f.updated_at)          # size in characters
    print(mem.files.get(f.path).text)            # exactly as Claude wrote it

Recall what Claude wrote

Anywhere in your app, context() and ask() recall what Claude noted, beside everything else you added for that user:

print(mem.context("How does this user want us to follow up?"))

Each line names the file it came from:

- Prefers email follow-ups, not calls.  [/memories/preferences.md, 2026-10-06]

Every file the tool writes carries the label channel: claude-memory, so a recall can keep to Claude's notes: mem.context(question, labels={"channel": "claude-memory"}) in Python, or mem.context(question, { labels: LABELS }) with LABELS from geniffy/claude in TypeScript. See Labels.

With recall first

The memory tool holds what Claude chose to note. Recall first puts everything else your app knows about the user in front of Claude as well: what they said in other conversations, and anything you synced from their email, documents or tickets. The two work together. Put context() in the system prompt, as on the Anthropic page, and give Claude the memory tool for its own notes:

mem = geniffy.space(f"user_{user_id}")
context = mem.context(message)            # what bears on the message
final = claude.beta.messages.tool_runner(
    model="claude-opus-5-5",
    max_tokens=16000,
    system=f"You are a helpful assistant.\n\n<memory>\n{context}\n</memory>",
    tools=[GeniffyMemoryTool(mem)],
    messages=[{"role": "user", "content": message}],
).until_done()

How it behaves

  • Each command is a call to the files API. view lists a directory or reads a file; create writes a file whole, replacing any file already at that path, as Claude's tool description says it does; str_replace and insert read the file, change it and write it back; delete takes a file or a whole directory; rename moves a file or a directory, and never onto a path that is taken.
  • An edit learns only what changed. A file written again is the same source, so only the paragraphs that changed are learned, and what was removed is taken back. A renamed file keeps its text and what it taught: nothing is learned again. An empty file is kept, with nothing learned from it.
  • A directory is the files under it. view lists two levels deep, leaving out hidden names and node_modules, with sizes in characters; a directory's size is what its files hold.
  • Paths stay inside /memories. A path that leads out of it is refused, typed or URL-encoded (.., %2e%2e, a backslash), and so is deleting or renaming /memories itself. Doubled and trailing slashes are dropped.
  • Claude hears what went wrong. A command that can't be carried out, such as a missing file, text that isn't there or a line past the end, goes back to Claude as an error result in the words of Anthropic's memory tool documentation, so it can try again. So does a write Geniffy refuses, such as a file over 2,000,000 characters, in Geniffy's words. The tool runner hands any other failure to Claude the same way, such as a revoked key or Geniffy out of reach, rather than raising it in your app; the Python runner logs it as well.

Reset, forget and export

mem = geniffy.space("user_1042")
mem.export()                                # their copy, every file listed by path
GeniffyMemoryTool(mem).clear_all_memory()   # deletes every file under /memories
geniffy.forget_space("user_1042")           # forgets the user, files and all

The copy lists each file by its path, size and when it was last written; files.get(path) hands back its text, exactly as Claude wrote it, so the copy stays small however much the files hold. Clearing takes back what only those files taught, too. It is for your app to call, such as from a button that resets the assistant: it isn't one of the tool's commands, so Claude can't call it. See Correct and forget.

With asyncio

With AsyncAnthropic in Python, use AsyncGeniffyMemoryTool on an AsyncGeniffy client:

from anthropic import AsyncAnthropic
from geniffy import AsyncGeniffy
from geniffy.claude import AsyncGeniffyMemoryTool

memory = AsyncGeniffyMemoryTool(AsyncGeniffy().space(f"user_{user_id}"))
final = await AsyncAnthropic().beta.messages.tool_runner(
    model="claude-opus-5-5", max_tokens=16000, tools=[memory],
    messages=[{"role": "user", "content": message}],
).until_done()

The files underneath

The tool is built on Geniffy's files API, which works the same for any agent that keeps its own notes as files, with any model:

Call What it does
PUT /v1/files Writes a file, {"path", "text", "labels"}: kept exactly, and learned like a note
GET /v1/files?path= One file, with its text exactly as written
GET /v1/files?prefix= The files under a prefix, in path order
DELETE /v1/files?path= A file, its text and what only it taught; ?prefix= for every file under one
POST /v1/files/move {"from", "to"}: a file, or every file beneath a directory

See Files kept exactly for the rules, and the reference for every field.

Last updated October 6, 2026