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

# LangGraph

> Add three Memory tools and transient per-turn recall to an async LangGraph without polluting checkpoints.

> [!WARNING]
>
> Source install only: `powercontext-langgraph==0.0.1` is not published to PyPI. The documented Git URL follows mutable `master`, and the focused package test does not exercise that VCS installation path. Reproducibility is **Not validated**; pin a reviewed commit and its dependencies.

The adapter has three parts: `powercontext_tools()` returns three tools, `PowerContextRecall` prepares model input, and `PowerContextScope` carries per-run configuration. It connects to a separately running PowerContext Server.

## Maturity and installation

| Item | Status |
| - | - |
| Package | Source install only; not PyPI |
| Version | `0.0.1` |
| Runtime | Python `>=3.11,<4.0`; LangGraph `>=1.2,<2` |
| Invocation | **Async only** |
| Mutable-branch reproducibility | **Not validated** |
| Real model-provider round trip in the documented evidence | **Not validated** |

Install the Server and adapter from the reviewed source commit:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
uv tool install --force "powercontext[cli,server] @ git+https://github.com/oceanbase/powercontext.git@3e629eb163ba0ddf2935ce3b3fbaced0f9371977"
uv pip install "powercontext-langgraph @ git+https://github.com/oceanbase/powercontext.git@3e629eb163ba0ddf2935ce3b3fbaced0f9371977#subdirectory=integrations/langgraph"
powercontext server run
```

For a local checkout at the same commit:

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
git -C /path/to/powercontext checkout 3e629eb163ba0ddf2935ce3b3fbaced0f9371977
uv pip install /path/to/powercontext/integrations/langgraph
```

> [!NOTE]
>
> Pinning removes the mutable-branch ambiguity, but the documented verification did not run the upstream VCS-install path or an external-provider round trip. Verify both in your deployment environment.

## Minimum use

The snippet below is the integration seam, not a standalone program: provide your application's initialized chat `model` and LangGraph `my_checkpointer`. To test PowerContext before adding either dependency, write the shared Memory through the API tour and call `PowerContextRecall` in the focused adapter test; the upstream suite does this without an external model provider.

`create_react_agent` already reads the `llm_input_messages` channel written by the recall hook.

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
from langchain_core.messages import HumanMessage
from langgraph.prebuilt import create_react_agent
from powercontext_langgraph import PowerContextRecall, PowerContextScope, powercontext_tools

agent = create_react_agent(
    model,
    tools=powercontext_tools(),
    pre_model_hook=PowerContextRecall(),
    context_schema=PowerContextScope,
    checkpointer=my_checkpointer,
)

result = await agent.ainvoke(
    {
        "messages": [
            HumanMessage(content="What should happen after changing the OpenAPI contract?")
        ]
    },
    context=PowerContextScope(scope_id="project:powercontext-101"),
)
```

> [!WARNING]
>
> Use `ainvoke` or `astream`. Recall and all three tools are async; `invoke` and `stream` cannot execute them.

| Tool | Behavior |
| - | - |
| `powercontext_search` | Searches Memory; default `limit=5`, maximum `20`; no search `mode` argument |
| `powercontext_remember` | Writes one explicit Memory |
| `powercontext_context` | Prepares bounded context for a query |

`powercontext_search` and `powercontext_context` clamp a model-supplied query to 8,192 Python characters. `powercontext_tools()` returns the tools in search, remember, context order.

## Execution flow

```text theme={"theme":{"light":"github-light","dark":"github-dark"}}
latest human message
→ resolve per-run Scope and connection settings
→ prepare_context(query <= 8192 chars, max_bytes)
→ build ordered llm_input_messages
→ model step
→ optional model-requested search / remember / context tool
→ later model steps reuse the per-turn recall cache
```

`PowerContextRecall` is called before each model step, but prepares at most once for the current human turn. A ReAct loop reuses a cache keyed by Scope plus the latest human message ID; when no ID exists, it uses the message text. Including Scope in the key keeps identical tenant prompts separate.

Successful recall produces one leading `SystemMessage`, marked as untrusted historical evidence, followed by the original ordered messages. Empty recall or failure supplies only the original messages.

### Keep model input ephemeral

Recall writes a complete input to `llm_input_messages`. It never appends the recalled block to `messages`, so a checkpointer does not accumulate stale context.

A custom graph must declare that channel, place recall before the model, and read it in the model node:

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
from typing import Annotated

from langchain_core.messages import BaseMessage
from langgraph.graph import START, StateGraph
from langgraph.graph.message import add_messages
from powercontext_langgraph import PowerContextRecall, PowerContextScope
from typing_extensions import TypedDict


class AgentState(TypedDict):
    messages: Annotated[list[BaseMessage], add_messages]
    llm_input_messages: list[BaseMessage]


async def call_model(state: AgentState):
    model_input = state.get("llm_input_messages") or state["messages"]
    response = await model.ainvoke(model_input)
    return {"messages": [response]}


builder = StateGraph(AgentState, context_schema=PowerContextScope)
builder.add_node("recall", PowerContextRecall())
builder.add_node("model", call_model)
builder.add_edge(START, "recall")
builder.add_edge("recall", "model")
graph = builder.compile(checkpointer=my_checkpointer)
```

Reading only `state["messages"]` silently discards recalled context. Writing the block back to `messages` defeats the checkpoint boundary.

## Scope and configuration

Pass `PowerContextScope` on every invocation in multi-tenant services. Per-run values for Scope, base URL, token, and timeout override their environment settings.

```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
scope = PowerContextScope(
    scope_id="tenant:acme:project:api",
    base_url="https://context.example.com",
    token="opaque-server-token",
    timeout=8.0,
)

result = await agent.ainvoke(state, context=scope)
```

| Setting | Default |
| - | -: |
| `POWERCONTEXT_LANGGRAPH_BASE_URL` | `http://127.0.0.1:8000` |
| `POWERCONTEXT_LANGGRAPH_TOKEN` | unset |
| `POWERCONTEXT_LANGGRAPH_SCOPE_ID` | derived or error |
| `POWERCONTEXT_LANGGRAPH_TIMEOUT` | `10` seconds |
| `POWERCONTEXT_LANGGRAPH_MAX_BYTES` | `8000`; accepted range `512`–`32768` bytes |

Scope precedence is:

```text theme={"theme":{"light":"github-light","dark":"github-dark"}}
PowerContextScope(scope_id=...)
→ POWERCONTEXT_LANGGRAPH_SCOPE_ID
→ normalized network Git origin
→ MissingScopeError
```

There is no local-path fallback. HTTP(S), SSH, Git, and SCP-like network remotes are normalized without credentials. A local path or Windows drive path is not a remote. An overlong explicit Scope is replaced by a bounded SHA-256 identifier.

The token convention is a bare token without `Bearer `. The adapter does not strictly validate that syntax, so reject malformed configuration before deployment. `repr=False` reduces accidental display; it is not a secret vault. The shared `PowerContextClient` rejects plaintext HTTP to non-loopback hosts unless the caller supplies an explicitly trusted transport.

## Why there is no BaseStore

> [!NOTE]
>
> LangGraph `BaseStore` requires `GetOp`, caller-keyed `PutOp` upsert/delete, and `SearchOp`. Only search maps cleanly to PowerContext Memory. PowerContext assigns Memory identity and versions on the Server; it does not provide caller-key get, put, or delete semantics.

A search-only store can pass graph assembly and then fail when another node issues get or put. This adapter uses nodes and tools and leaves `compile(store=...)` alone.

## Failure, privacy, and Scope boundary

Automatic recall is fail-open. Missing Scope, malformed configuration, validation errors, authentication failures, timeouts, and Server errors skip the recall block and let the graph continue with its original messages. HTTP 401/403 is logged as configuration trouble, not treated as empty Memory.

Explicit tools do not turn failures into empty success and do not raise through the graph. They return short model-visible strings for unavailable or invalid requests, allowing the model to retry or continue. A real search miss has a separate `(no matching PowerContext memory)` result.

> [!WARNING]
>
> Recalled content can contain old user or model text. It is evidence, not a current instruction, identity proof, or tool authorization. Keep it on `llm_input_messages`. Scope selects a data partition; derive it from an authenticated tenant/project mapping when isolation matters.

The adapter performs no automatic capture. Explicit `powercontext_remember` still persists model-selected text, so apply host tool permissions and a retention policy.

## Currently unsupported or not validated

| Item | Status |
| - | - |
| Synchronous recall or tools | **Unsupported** |
| Automatic Source capture or flush | **Unsupported** |
| `BaseStore` implementation | **Unsupported** by design |
| Handoff and Candidate Review | **Unsupported** |
| Experience and Skill operations | **Unsupported** |
| Reproducible install from mutable `master` | **Not validated** |
| Real external-provider round trip in the documented evidence | **Not validated** |

## Completion checklist

* The Server runs separately, and the source-installed adapter is pinned to a reviewed commit.
* The graph uses `ainvoke` or `astream`.
* `PowerContextRecall` runs before the model, and a custom model node reads `state.get("llm_input_messages") or state["messages"]`.
* Checkpointer inspection confirms recalled blocks never enter persisted `messages`.
* Every invocation receives the intended Scope; a cross-Scope test proves identical prompts stay isolated.
* `MAX_BYTES` is between 512 and 32,768, long queries are expected to be clamped at 8,192 characters, and remote transport uses HTTPS.
* With the Server unavailable, recall lets the graph continue; explicit tool failures remain model-visible and distinct from an empty search.
* Tool permission and retention policy cover explicit Memory writes.
* Unsupported and Not validated items remain distinct in deployment notes.

> [!TIP]
>
> Run the model-free recall inspection first. Add a real provider only after Scope, Server reachability, transient state, and failure behavior are observable.

Continue with [LangChain](https://powercontext.mintlify.site/en/frameworks/langchain.md) to compare per-request middleware recall and optional completed-turn Source capture.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.