> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sovseal.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Stateful LangGraph Agents with sovseal Memory Nodes

> Hook sovseal-sdk's store/recall into LangGraph's StateGraph to inject recalled memory at node entry and persist new context at node exit across agent runs.

<style>
  {`
      main, article, .prose {
        margin-left: 2.5cm !important;
        margin-right: 2.5cm !important;
      }
      `}
</style>

sovseal plugs directly into LangGraph's state-passing architecture. By calling `recall` at the entry of a graph node and `store` at the exit, your agent carries relevant context from previous runs into every new execution — without relying on LangGraph's built-in checkpoint mechanism or any external database daemon. `sovseal-sdk`'s `store`/`recall` talk to the local ONEBRAIN engine over framed IPC, so recall adds no network latency to your graph.

## Installation

```bash theme={null}
pip install langgraph langchain-openai sovseal-sdk python-dotenv
```

## State graph with recall-on-entry and store-on-exit

The example below defines a `StateGraph` with a single chatbot node. On entry, the node calls `recall` to fetch the top-3 relevant memories and injects them into the system prompt. On exit, it calls `store` to persist the latest interaction. No MCP server or subprocess is spawned — `AgentStateClient` talks to the native host directly.

```python agent.py theme={null}
import os
import asyncio
from typing import Annotated, TypedDict, List
from dotenv import load_dotenv
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langchain_openai import ChatOpenAI
from langchain_core.messages import SystemMessage, HumanMessage, AIMessage
from sovseal import AgentStateClient, EngineUnavailableError

load_dotenv()

client = AgentStateClient()  # store/recall need no endpoint/api_key


# 1. Define the shared state structure
class AgentGraphState(TypedDict):
    messages: Annotated[List[HumanMessage | AIMessage], add_messages]
    user_id: str


# 2. Build the chatbot node with memory injection
async def query_chatbot(state: AgentGraphState):
    messages = state["messages"]
    last_query = messages[-1].content

    # Recall-on-entry: fetch the top-3 relevant memories
    try:
        hits = client.recall(last_query, top_k=3)
    except EngineUnavailableError:
        hits = []  # fail open — proceed without recalled context
    context_string = "\n".join(h["text"] for h in hits)

    # Inject recalled context into the system prompt
    system_prompt = (
        "You are a personalized assistant. "
        "Use the following context from previous interactions:\n"
        f"{context_string}"
    )

    llm = ChatOpenAI(model="gpt-4o")
    full_messages = [SystemMessage(content=system_prompt)] + messages
    response = await llm.ainvoke(full_messages)

    # Store-on-exit: persist the current exchange
    try:
        client.store(
            f"User asked: '{last_query}' — "
            f"Assistant responded: '{response.content}'"
        )
    except EngineUnavailableError:
        pass  # fail open — engine not installed/running

    return {"messages": [response]}


# 3. Compile the graph
workflow = StateGraph(AgentGraphState)
workflow.add_node("chatbot", query_chatbot)
workflow.add_edge(START, "chatbot")
workflow.add_edge("chatbot", END)

app = workflow.compile()


# 4. Run the graph
async def run():
    inputs = {
        "messages": [HumanMessage(content="I am building a state continuity system.")],
        "user_id": "alice_dev",
    }
    config = {"configurable": {"thread_id": "alice_dev"}}

    async for event in app.astream(inputs, config):
        for value in event.values():
            print("Chatbot:", value["messages"][-1].content)


if __name__ == "__main__":
    asyncio.run(run())
```

## How the pattern works

| Phase      | sovseal call                    | Purpose                                                                                                      |
| ---------- | ------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| Node entry | `client.recall(query, top_k=3)` | Pull up to `top_k` semantically relevant memories and inject them into the system prompt before the LLM call |
| Node exit  | `client.store(content)`         | Persist the user query and agent response so future runs can recall this exchange                            |

<Note>
  `store`/`recall` require the native host launcher at `~/.sovseal/native-host/run.sh`. Run `npx -y @sovseal/mcp-server` once to provision it — see [SDK Reference: Overview](/sdk-reference/overview). Initialize `AgentStateClient()` once at module level, as shown above, rather than per node invocation — the native-host subprocess is spawned lazily on first call and reused across calls.
</Note>
