Project: support assistant
The support assistant project is the course's finished build: an agent that answers from memory during the chat, a background store manager that writes memories after it, and a store that carries them to the next conversation.
Last updated: 30 Sep, 2026 · LangMem 0.0.30
The overview promised an assistant that remembers Asha's broken order and her contact preference in a conversation days later, and that cannot recall anything for a customer it has never met. This lesson builds it from pieces the course taught: the store with semantic search, a store manager, the search tool, an agent with a checkpointer, and the reflection executor.
Pick one to watch it run, step by step.
How the assistant is split
- The agent only searches. It gets
search_memory, notmanage_memory, so answering never waits on saving. - Saving happens in the background. After each reply the whole thread goes to the
ReflectionExecutor; the store manager extracts from it once the thread goes quiet. - One store for both sides, with Gemini embeddings so the agent's search finds memories by meaning.
Syntax:
agent, reflector, store = build()
reply, saved = chat(agent, reflector, user_id, thread_id, text, delay=60)The agent and the manager in build()
Both use the same model and store. The prompt is grounded: answer only from what the customer said and what the search returned.
agent = create_agent(model, tools=[create_search_memory_tool(namespace=NAMESPACE)],
store=store, checkpointer=InMemorySaver(), system_prompt=PROMPT)
manager = create_memory_store_manager(model, namespace=NAMESPACE, instructions=INSTRUCTIONS, store=store)
return agent, ReflectionExecutor(manager, store=store), storeOne turn in chat()
Answer first, then submit the whole thread for reflection and hand back the future. A real app would pass a delay of minutes and never wait on the future; the demo waits so it can print the store.
result = agent.invoke({"messages": [{"role": "user", "content": text}]}, config=config)
saved = reflector.submit({"messages": result["messages"]}, config=config, after_seconds=delay)
return result["messages"][-1].content, savedThe assistant end to end
Save the first file as assistant.py and the second as main.py in the same folder, then run python main.py.
from langchain.agents import create_agent
from langchain.chat_models import init_chat_model
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.store.memory import InMemoryStore
from langmem import ReflectionExecutor, create_memory_store_manager, create_search_memory_tool
NAMESPACE = ("memories", "{user_id}")
INSTRUCTIONS = "Extract what helps support this customer. Record everything in a single Memory call."
PROMPT = (
"You are a support assistant for an online shop. Before answering, search memory for what you know "
"about this customer. Answer in one or two short sentences, using only what the customer said and "
"what the search returned. If you do not know something, say so."
)
def build():
model = init_chat_model("groq:openai/gpt-oss-120b", temperature=0)
store = InMemoryStore(index={"dims": 3072, "embed": "google_genai:gemini-embedding-2"})
agent = create_agent(
model,
tools=[create_search_memory_tool(namespace=NAMESPACE)],
store=store,
checkpointer=InMemorySaver(),
system_prompt=PROMPT,
)
manager = create_memory_store_manager(model, namespace=NAMESPACE, instructions=INSTRUCTIONS, store=store)
return agent, ReflectionExecutor(manager, store=store), store
def chat(agent, reflector, user_id, thread_id, text, delay=0):
config = {"configurable": {"user_id": user_id, "thread_id": thread_id}}
result = agent.invoke({"messages": [{"role": "user", "content": text}]}, config=config)
saved = reflector.submit({"messages": result["messages"]}, config=config, after_seconds=delay)
return result["messages"][-1].content, savedfrom assistant import build, chat
agent, reflector, store = build()
with reflector:
for text in ["Hi, I'm Asha. Order A-1001 arrived broken.", "Please email me about it, I work nights."]:
reply, saved = chat(agent, reflector, "asha", "asha-monday", text, delay=1)
print("asha (monday):", text)
print(" assistant:", reply)
saved.result()
for item in store.search(("memories", "asha")):
print(" remembered:", item.value["content"]["content"])
for user in ["asha", "ravi"]:
question = "Which order did I write about, and how should you contact me?"
reply, saved = chat(agent, reflector, user, f"{user}-friday", question)
print(f"{user} (friday):", question)
print(" assistant:", reply)asha (monday): Hi, I'm Asha. Order A-1001 arrived broken. assistant: I’m sorry to hear that—unfortunately I don’t have any details about your order. Could you provide more information so I can help? asha (monday): Please email me about it, I work nights. assistant: I don’t have your email address on file—could you share it so I can send you an update? remembered: Customer Asha reported that order A-1001 arrived broken. She prefers to be contacted via email and works nights, indicating email communication is preferred. Email address has not yet been provided. asha (friday): Which order did I write about, and how should you contact me? assistant: You wrote about order A‑1001, and you’d like us to contact you by email. ravi (friday): Which order did I write about, and how should you contact me? assistant: I’m sorry, but I don’t have any information about the order you mentioned or your preferred contact method.
Monday's chat and Friday's questions
- Monday, first message: nothing was stored for Asha yet, and the agent asked for details instead of inventing any. The grounded prompt is doing its job.
- Monday, second message: it asked for an email address, which it did not have. Each chat submitted the whole thread to the executor with a one-second delay; a newer submit replaces an older one only while it is still waiting, and here the store ended with one memory for Asha.
- remembered: one memory with the broken order, the email preference and the night shifts, plus "Email address has not yet been provided", which the model added from the conversation.
- Friday, Asha, in a new thread: nothing from Monday was in the agent's messages. It searched the store and answered with A-1001 and email.
- Friday, Ravi: the same question found nothing in Ravi's namespace, and the agent said it had no information. That is the failure the overview promised to show: memory exists only for customers the assistant has met.
What the course left out
| Topic | What it is for | Where in the docs |
|---|---|---|
create_memory_searcher | A runnable that writes search queries from a conversation, then searches the store | Memory API reference |
SummarizationNode | The running summary as a node in a LangGraph graph | Summarization guide |
create_multi_prompt_optimizer | Optimizing several agents' prompts from shared feedback | Optimize multiple prompts |
| Memory tools in other agents | Using LangMem's tools from CrewAI or a hand-written Anthropic or OpenAI loop | CrewAI guide |
| Async APIs | ainvoke, asummarize_messages and AsyncPostgresStore for async servers | API reference |
Where to take the assistant next
- Add the profile from Profiles in its own namespace, so the contact channel is a field your code can read.
- Record an episode per resolved ticket, as in Episodic memory, and put the best match in the prompt.
- Move to a persistent store, as in Integrations.
Related
- Previous: Integrations
- See also: LangMem overview
- Reference: Background quickstart
- Ask Asha's Friday question without
saved.result()on Monday and see what the agent finds. - Add a third Monday message, "Text me instead", and check which contact method Friday's answer gives.
- Give the agent
create_manage_memory_toolas well and ask it on Friday to forget the order.
Slow is fine. Stopping is the only problem.