GeniffyDocs
Changelog Log In Get a key

LangGraph

Give a LangGraph graph a memory of each of your users. Memory is two nodes in your graph: recall before the model, and remember after it. The user comes from the graph's runtime context, so one compiled graph serves every user.

Install

Terminal
pip install langgraph langchain langchain-anthropic geniffy

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

Recall, respond, remember

graph.py
from dataclasses import dataclass

from geniffy import Geniffy
from langchain.chat_models import init_chat_model
from langgraph.graph import END, START, MessagesState, StateGraph
from langgraph.runtime import Runtime

geniffy = Geniffy()                      # reads GENIFFY_API_KEY
model = init_chat_model("anthropic:claude-opus-5-5")


@dataclass
class User:
    id: str


class State(MessagesState):
    memory: str


def recall(state: State, runtime: Runtime[User]) -> dict:
    mem = geniffy.space(f"user_{runtime.context.id}")
    return {"memory": mem.context(state["messages"][-1].text)}


def respond(state: State) -> dict:
    system = f"You are a helpful assistant.\n\n<memory>\n{state['memory']}\n</memory>"
    reply = model.invoke([{"role": "system", "content": system}, *state["messages"]])
    return {"messages": [reply]}


def remember(state: State, runtime: Runtime[User]) -> dict:
    asked, answer = state["messages"][-2], state["messages"][-1]
    geniffy.space(f"user_{runtime.context.id}").memories.add(messages=[
        {"role": "user", "content": asked.text},
        {"role": "assistant", "content": answer.text},
    ])
    return {}


builder = StateGraph(State, context_schema=User)
builder.add_sequence([recall, respond, remember])
builder.add_edge(START, "recall")
builder.add_edge("remember", END)
graph = builder.compile()

Pass the user with each run, from your own sign-in:

result = graph.invoke(
    {"messages": [{"role": "user", "content": "Who signs the Lumen renewal?"}]},
    context=User(id=user.id),
)
print(result["messages"][-1].text)

recall puts what is known about this user in the state, each line with where it came from, and respond gives it to the model. When nothing is known, the block says so in one sentence, so the model says it doesn't know instead of guessing.

Let the model look things up

To let the model decide when to look something up, give it a recall tool, loop through a ToolNode, and remember once the model has its answer.

graph.py
from dataclasses import dataclass

from geniffy import Geniffy
from langchain.chat_models import init_chat_model
from langchain.tools import ToolRuntime, tool
from langgraph.graph import END, START, MessagesState, StateGraph
from langgraph.prebuilt import ToolNode, tools_condition
from langgraph.runtime import Runtime

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


@dataclass
class User:
    id: str


@tool
def recall(query: str, runtime: ToolRuntime[User]) -> str:
    """Look up what is known about the user, with where it came from."""
    return geniffy.space(f"user_{runtime.context.id}").context(query)


model = init_chat_model("anthropic:claude-opus-5-5").bind_tools([recall])


def respond(state: MessagesState) -> dict:
    system = {"role": "system", "content": INSTRUCTIONS}
    return {"messages": [model.invoke([system, *state["messages"]])]}


def remember(state: MessagesState, runtime: Runtime[User]) -> dict:
    asked = next(m for m in reversed(state["messages"]) if m.type == "human")
    geniffy.space(f"user_{runtime.context.id}").memories.add(messages=[
        {"role": "user", "content": asked.text},
        {"role": "assistant", "content": state["messages"][-1].text},
    ])
    return {}


builder = StateGraph(MessagesState, context_schema=User)
builder.add_node(respond)
builder.add_node("tools", ToolNode([recall]))
builder.add_node(remember)
builder.add_edge(START, "respond")
builder.add_conditional_edges(
    "respond", tools_condition, {"tools": "tools", END: "remember"},
)
builder.add_edge("tools", "respond")
builder.add_edge("remember", END)
graph = builder.compile()

Memory across threads

A LangGraph checkpointer keeps one thread going, and its store holds what you put in it. Geniffy is what the graph knows about the user across every thread: what they said last month, in another app, or in a file you added, with where each fact came from. Use the checkpointer for the thread, and Geniffy for the person.

Last updated October 5, 2026