This lesson shows how to extend AgentState, write state from a tool, then read state through ToolRuntime so an Agent can retain user preferences in the same thread.
Runtime context is temporary information supplied by the caller. Agent state is working memory maintained by the Agent during execution. Once a checkpointer is configured, later calls with the same thread_id can keep reading state.
| Dimension | Runtime Context | Agent State |
|---|---|---|
| Who provides it | The caller passes it in | The Agent and tools update it at runtime |
| Persistent? | Not persistent by default | Saved per thread through a checkpointer |
| Lesson example | ColourContext(favourite_colour="green") | favourite_colour written into thread state |
The notebook defines CustomState, adding a favourite_colour field on top of the default AgentState.
from langchain.agents import AgentState
class CustomState(AgentState):
favourite_colour: strAgentState already contains the message fields an Agent needs. Extending it with business fields avoids breaking the Agent's default message flow.
The tool does not freely mutate outer variables. Instead, it returns Command(update=...). Here it updates favourite_colour and appends a ToolMessage indicating the tool succeeded.
from langchain.tools import tool, ToolRuntime
from langgraph.types import Command
from langchain.messages import ToolMessage
@tool
def update_favourite_colour(favourite_colour: str, runtime: ToolRuntime) -> Command:
"""Update the favourite colour of the user in the state once they've revealed it."""
return Command(
update={
"favourite_colour": favourite_colour,
"messages": [
ToolMessage(
"Successfully updated favourite colour",
tool_call_id=runtime.tool_call_id,
)
],
}
)This is the standard way to return state updates from a tool in a LangGraph/LangChain Agent. It is clearer than hidden side effects and easier for the checkpointer to record.
To save state across calls, the Agent needs a checkpointer. The notebook uses the in-memory InMemorySaver, which is suitable for a course demo.
from langgraph.checkpoint.memory import InMemorySaver
agent = create_agent(
deepseek_model(),
tools=[update_favourite_colour],
checkpointer=InMemorySaver(),
state_schema=CustomState,
)
response = agent.invoke(
{"messages": [HumanMessage(content="My favourite colour is green")]},
{"configurable": {"thread_id": "1"}},
)The checkpointer separates memory by thread. The same thread_id can read state written earlier; a different thread gets separate memory.
The tool that reads state gets values from runtime.state. If the current thread has not written the relevant field yet, it returns an explicit fallback message.
@tool
def read_favourite_colour(runtime: ToolRuntime) -> str:
"""Read the favourite colour of the user from the state."""
try:
return runtime.state["favourite_colour"]
except KeyError:
return "No favourite colour found in state"
agent = create_agent(
deepseek_model(),
tools=[update_favourite_colour, read_favourite_colour],
checkpointer=InMemorySaver(),
state_schema=CustomState,
)When the user states a preference, the model calls the write tool. When the user later asks about the preference, the model calls the read tool. In the same thread, this becomes durable Agent memory.
favourite_colour, instead of a vague large dictionary.Command(update=...) and include the required ToolMessage.InMemorySaver is fine during development; production needs a persistent checkpointer.