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

# LangGraph (OpenAI)

> Protect OpenAI-backed LangGraph graphs, tools, and state updates.

**Best for:** teams using LangGraph with OpenAI models through `langchain_openai` or `@langchain/openai`.

<Note>
  Run doctor in the repo before you wire anything:

  * TypeScript: `npx @verifiedx-core/sdk doctor`
  * Python: `verifiedx doctor`

  Protect one action free: [verifiedx.me](https://verifiedx.me/?vx_source=docs.langgraph-openai)

  If doctor shows a supported native adapter already owns the graph or tool loop, use that page instead of this provider-specific path.
</Note>

## Install

<CodeGroup>
  ```bash Python theme={null}
  pip install verifiedx
  ```

  ```bash TypeScript theme={null}
  npm install @verifiedx-core/sdk
  ```
</CodeGroup>

## Net-new VerifiedX code

This is usually the only VerifiedX delta in an existing OpenAI-backed LangGraph setup.

<CodeGroup>
  ```python Python theme={null}
  from langchain_openai import ChatOpenAI
  from verifiedx import init_verifiedx, install_langgraph

  vx = init_verifiedx()
  install_langgraph(verifiedx=vx)

  model = ChatOpenAI(model="gpt-5.4-mini", temperature=0).bind_tools(TOOLS)

  graph = builder.compile()
  ```

  ```typescript TypeScript theme={null}
  import { ChatOpenAI } from "@langchain/openai";
  import { initVerifiedX } from "@verifiedx-core/sdk";
  import { install as installLanggraph } from "@verifiedx-core/sdk/langgraph";

  const verifiedx = await initVerifiedX();
  await installLanggraph({ verifiedx });

  const model = new ChatOpenAI({
    model: "gpt-5.4-mini",
    temperature: 0,
  }).bindTools(tools);

  const graph = builder.compile();
  ```
</CodeGroup>

<Note>
  That is the important part. The rest of your LangGraph graph stays the same: your nodes, edges, `ToolNode` or manual tool loop, store, checkpointer, and state schema do not need to be redesigned.
</Note>

<Note>
  Do not use the OpenAI direct adapter here. If LangGraph owns the graph and tool loop, use the LangGraph adapter.
</Note>

<Tip>
  Start with the graph that owns the highest-impact tool call or state update. OpenAI-backed LangGraph protection is broad once installed, but rollout can still be incremental.
</Tip>

<Note>
  Your native tool surface is the config. VerifiedX uses your existing tool names, descriptions, schemas, and LangGraph state/store surfaces as the source of truth for what to preflight.
</Note>

## Typical graph shape

A normal OpenAI-backed LangGraph graph still looks like a normal LangGraph graph.

<CodeGroup>
  ```python Python theme={null}
  from langchain_core.messages import SystemMessage
  from langgraph.graph import END, START, MessagesState, StateGraph

  def assistant(state: MessagesState):
      response = model.invoke([SystemMessage(content=SYSTEM_PROMPT), *state["messages"]])
      return {"messages": [response]}

  def route_after_assistant(state: MessagesState):
      last = state["messages"][-1]
      return "run_tools" if getattr(last, "tool_calls", None) else END

  builder = StateGraph(MessagesState)
  builder.add_node("assistant", assistant)
  builder.add_node("run_tools", run_tools)
  builder.add_edge(START, "assistant")
  builder.add_conditional_edges("assistant", route_after_assistant, {"run_tools": "run_tools", END: END})
  builder.add_edge("run_tools", "assistant")

  graph = builder.compile()
  ```

  ```typescript TypeScript theme={null}
  import { END, START, MessagesAnnotation, StateGraph } from "@langchain/langgraph";
  import { ToolNode } from "@langchain/langgraph/prebuilt";

  const toolNode = new ToolNode(tools);

  const builder = new StateGraph(MessagesAnnotation)
    .addNode("assistant", async (state) => {
      const response = await model.invoke([
        { role: "system", content: SYSTEM_PROMPT },
        ...state.messages,
      ]);
      return { messages: [response] };
    })
    .addNode("tools", toolNode)
    .addEdge(START, "assistant")
    .addConditionalEdges("assistant", (state) =>
      state.messages.at(-1)?.tool_calls?.length ? "tools" : END
    )
    .addEdge("tools", "assistant");

  const graph = builder.compile();
  ```
</CodeGroup>

## If you manually dispatch tools in Python

If your Python graph runs tools manually instead of using `ToolNode`, catch `BoundaryDeniedError` and replay `exc.tool_result()` back into the graph as a normal tool result.

```python theme={null}
from verifiedx import BoundaryDeniedError

try:
    result = TOOL_MAP[tool_name].invoke(tool_args)
except BoundaryDeniedError as exc:
    result = exc.tool_result()
```

That keeps the OpenAI tool loop moving safely instead of crashing the graph.

## Composed systems

If this OpenAI-backed LangGraph run is part of a larger multi-agent or agent+human workflow, pass upstream context into VerifiedX so the current graph run has better system and situational awareness before it takes a high-impact action.

This is useful when a supervisor agent, parent workflow, or human reviewer already has context that the current graph run should use before taking action.

VerifiedX does not require a fixed schema for this. Pass the upstream context you already have in any JSON-serializable shape.

<CodeGroup>
  ```python Python theme={null}
  upstream = {
      "source": "workflow_supervisor",
      "workflow_id": "WF-2203",
      "approval_status": "approved_with_follow_up",
      "human_review": {
          "reviewer": "ops_lead",
          "result": "approved",
      },
      "prior_agent_output": {
          "summary": "Billing verification is complete.",
      },
  }

  with vx.with_upstream_context(upstream):
      result = graph.invoke(
          {
              "messages": [
                  {
                      "role": "user",
                      "content": "Inspect workflow WF-2203 and then update it to awaiting_human.",
                  }
              ]
          }
      )
  ```

  ```typescript TypeScript theme={null}
  const upstream = {
    source: "workflow_supervisor",
    workflow_id: "WF-2203",
    approval_status: "approved_with_follow_up",
    human_review: {
      reviewer: "ops_lead",
      result: "approved",
    },
    prior_agent_output: {
      summary: "Billing verification is complete.",
    },
  };

  const result = await verifiedx.withUpstreamContext(upstream, async () => {
    return await graph.invoke({
      messages: [
        {
          role: "user",
          content: "Inspect workflow WF-2203 and then update it to awaiting_human.",
        },
      ],
    });
  });
  ```
</CodeGroup>

<Note>
  Upstream context is supporting workflow context from outside the current run. It is not proof that this run already executed any local action.
</Note>

## What the adapter already captures

On this path, VerifiedX captures the native LangGraph surfaces and keeps provider attribution truthful for OpenAI-backed runs.

That includes:

* `StateGraph.compile`
* Graph execution through `invoke`, `ainvoke`, `stream`, and `astream` where available
* Selected tool history from `AIMessage.tool_calls`
* Tool execution through LangChain tool surfaces and `ToolNode`
* Direct state mutations through `Command.update`
* Direct compiled state mutations through `graph.update_state` or `graph.updateState`
* Store reads through `store.get` and `store.search`
* Store writes through `store.put` and `store.delete`

When OpenAI-style tool call IDs are present, VerifiedX records `provider: "openai"` while keeping `framework: "langgraph"`.

## What gets preflighted

VerifiedX preflights the high-impact LangGraph boundaries directly.

That includes:

* Wrapped tool executions
* `store.put`
* `store.delete`
* `Command.update`
* Compiled `graph.update_state` or `graph.updateState`

OpenAI-selected tools seen first in `AIMessage.tool_calls` are reused at execution time, so VerifiedX does not send duplicate preflight requests for the same boundary.

## What to expect at runtime

Protected LangGraph boundaries can return:

* `allow`
* `allow_with_warning`
* `replan_required`
* `goal_fail_terminal`

Every outcome includes a structured decision receipt.

If a tool, store write, or state update is replanned, the side effect does not execute. VerifiedX returns the blocked result or loopback guidance so the graph can keep moving toward the same goal safely.

## Production-style validation coverage

The OpenAI-backed LangGraph validation paths in this repo cover real graph flows including:

* Clean memory writes
* Clean record mutations
* Clean internal workflow-state changes
* Multi-step internal runs across retrieval, memory, mutation, and internal notification
* External email attempts that replan into safer internal Slack updates
* Repeated adversarial external-email attempts that should not keep pushing the same unsafe action

## Pricing note

One protected action check equals one real boundary preflight. Taint, event ingest, execution reports, and decision reads are all included at that price. The Free Sandbox includes every language, provider, framework, and adapter.

VerifiedX does not replace your orchestrator or workflow. It returns receipts your system can keep local, route downstream, or pass upstream.

***

For the provider-agnostic graph adapter, see [LangGraph](/frameworks/langgraph). For the direct OpenAI adapter outside LangGraph, see the OpenAI SDK pages.
