# Agent with memory
## 1. Checkpointer
```python
from langchain.agents import create_agent
from langgraph.checkpoint.memory import InMemorySaver
checkpointer = InMemorySaver()
agent = create_agent(
model="openai:gpt-5.5",
tools=[...],
checkpointer=checkpointer,
)
```
InMemorySaver is for dev. SqliteSaver (`langgraph-checkpoint-sqlite`) for local durable runs, PostgresSaver (`langgraph-checkpoint-postgres`) for production.
## 2. Thread ids
Every invoke or stream passes a thread id in the config. The thread is the conversation; same id resumes, new id starts fresh.
```python
config = {"configurable": {"thread_id": "user-123"}}
result = agent.invoke({"messages": [{"role": "user", "content": "Hi"}]}, config)
```
## 3. Custom state
Extend AgentState with a TypedDict for your own memory fields, and pass it as `state_schema`. Static per-run data goes through `context=` with `context_schema=`.
```python
class CustomState(AgentState):
user_name: str
```
## 4. Write memory from tools
Tools receive a ToolRuntime and can return a Command that updates state:
```python
from langchain.tools import tool, ToolRuntime
from langgraph.types import Command
from langchain.messages import ToolMessage
@tool
def update_user_info(runtime: ToolRuntime) -> Command:
# look up the user, then update state
return Command(update={
"user_name": "John Smith",
"messages": [ToolMessage(content="updated", tool_call_id=runtime.tool_call_id)],
})
```
## Rules
- No checkpointer means no memory, even with a thread id. Both are required.
- One thread id per conversation. Sharing a thread id across users mixes their histories.
- Prefer returning state updates from tools over stuffing everything into messages; messages are the expensive memory.