GeniffyDocs
Changelog Log In Get a key

Anthropic

Give Claude a memory of each of your users. Before Claude answers, what is known about the user goes in the system prompt; after it answers, the exchange is saved, so the next conversation starts knowing it.

Install

Terminal
pip install anthropic geniffy

Set ANTHROPIC_API_KEY, and GENIFFY_API_KEY from API keys in the Geniffy app.

Remember each user

chat.py
import anthropic
from geniffy import Geniffy

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


def chat(user_id: str, message: str, history: list) -> str:
    """history is the conversation so far, as you send it to Claude."""
    mem = geniffy.space(f"user_{user_id}")
    turn = {"role": "user", "content": message}
    context = mem.context(message)       # what is known that bears on the message

    response = claude.messages.create(
        model="claude-opus-5-5",
        max_tokens=1024,
        system=f"You are a helpful assistant.\n\n<memory>\n{context}\n</memory>",
        messages=[*history, turn],
    )
    reply = "".join(block.text for block in response.content if block.type == "text")

    mem.memories.add(messages=[turn, {"role": "assistant", "content": reply}])
    return reply

Claude now sees what is known about this user before every reply, each line with where it came from:

<memory>
- Priya Nair signs the Lumen renewal.  [note, 2026-10-05]
- The Lumen renewal comes up in March 2027.  [GTM plan, 2026-10-05]
</memory>

When nothing is known, the block says so in one sentence, so Claude says it doesn't know instead of guessing. The reply is joined from its text blocks, so it still works when Claude also thinks or calls a tool.

Let Claude look things up

To let Claude decide when to look something up, give it a recall tool. The SDK's tool runner calls the tool for you, and keeps going until Claude has its answer.

agent.py
import anthropic
from anthropic import beta_tool
from geniffy import Geniffy

claude = anthropic.Anthropic()
geniffy = Geniffy()
INSTRUCTIONS = ("You are a helpful assistant. Use recall before answering anything "
                "that depends on what the user said before.")


def chat(user_id: str, message: str) -> str:
    mem = geniffy.space(f"user_{user_id}")

    @beta_tool
    def recall(query: str) -> str:
        """Look up what is known about the user, with where it came from.

        Args:
            query: What to look up
        """
        return mem.context(query)

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

    mem.memories.add(messages=[{"role": "user", "content": message},
                               {"role": "assistant", "content": reply}])
    return reply

The tool answers with context(), so Claude reads the same lines, with their sources, that a system prompt would hold, and the same sentence when nothing is known.

With prompt caching

What is known changes with every message, so keep it out of the part of the prompt you cache. Put your fixed instructions in the first system block and mark it for caching, and put the memory in a second block after it.

system=[
    {"type": "text", "text": INSTRUCTIONS, "cache_control": {"type": "ephemeral"}},
    {"type": "text", "text": f"<memory>\n{context}\n</memory>"},
],

Streaming

When you stream the reply, save the exchange once the stream ends, with its final text.

with claude.messages.stream(
    model="claude-opus-5-5", max_tokens=1024, system=system, messages=messages,
) as stream:
    for text in stream.text_stream:
        print(text, end="", flush=True)
    reply = stream.get_final_text()
mem.memories.add(messages=[turn, {"role": "assistant", "content": reply}])
Last updated October 5, 2026