Integrations: LangChain & LangGraph¶
BoundFlow governs the runtime — versioning, cost caps, approvals, metrics, rollback. It composes with the LangChain ecosystem at two different layers:
- LangChain supplies the model — use any provider LangChain can talk to.
- LangGraph supplies orchestration — author a stateful agent graph that runs inside a governed workflow.
The rule that governs both: BoundFlow governs the model calls that flow through its agent loop (ctx.run_agent). Anything that calls a raw model directly runs outside that boundary.
Any provider via LangChain¶
LangChainLlmClient wraps any tool-calling LangChain chat model, so a BoundFlow agent runs on any provider (Anthropic, OpenAI, Google, Bedrock, …) with governance unchanged:
from langchain_anthropic import ChatAnthropic # or ChatOpenAI, ChatVertexAI, ...
from boundflow import BoundFlowWorker
from boundflow.langchain_client import LangChainLlmClient
worker = BoundFlowWorker(llm=LangChainLlmClient(ChatAnthropic(model="claude-haiku-4-5")))
Pass a factory — LangChainLlmClient(lambda name: ChatAnthropic(model=name)) — to let SetModel policies choose the model per run. Install with pip install "boundflow[langchain]".
The model must support tool calling and report usage
BoundFlow drives tools plus a submit_result tool for structured output, so a
non-tool-calling model can't complete a run. Token usage is what cost governance is
priced on: if a provider reports no usage, the run fails loud (a platform failure
that interrupts the workflow) rather than running uncosted and escaping its cost caps.
Runnable example: boundflow.examples.langchain_adapter.
LangGraph inside a governed workflow¶
LangGraph is an in-process library for authoring stateful agent graphs — nodes, conditional edges, loops. It runs when you call graph.ainvoke(...), so it lives happily inside a BoundFlow workflow handler. The composition that keeps governance intact: the graph's nodes run their reasoning through ctx.run_agent, so LangGraph owns the control flow while BoundFlow governs each agent step and the workflow as a whole.
Thread ctx into the nodes with a closure (build the graph per run) — ctx is a live object, so keep it out of the graph state (state should stay plain data).
from typing import TypedDict
from langgraph.graph import END, START, StateGraph
class State(TypedDict, total=False):
route: str
article: str
def build_graph(ctx):
async def triage(state):
out = await ctx.run_agent(triager) # governed agent call
return {"route": out.output["route"]}
async def research(state):
out = await ctx.run_agent(researcher) # governed agent call
ctx.add_context("notes", out.output["notes"]) # feed the writer downstream
return {}
async def write(state):
out = await ctx.run_agent(writer) # governed agent call
return {"article": out.output["article"]}
g = StateGraph(State)
g.add_node("triage", triage); g.add_node("research", research); g.add_node("write", write)
g.add_edge(START, "triage")
# LangGraph does the routing; BoundFlow governs whatever each branch runs.
g.add_conditional_edges("triage", lambda s: s["route"],
{"deep": "research", "quick": "write"})
g.add_edge("research", "write"); g.add_edge("write", END)
return g.compile()
@worker.workflow("langgraph_pipeline", version=1)
async def pipeline(ctx):
ctx.add_context("topic", "...")
graph = build_graph(ctx) # the graph lives in the workflow
final = await graph.ainvoke({})
return Complete()
Every reasoning step goes through ctx.run_agent, so each is cost-capped, call-limited, metered, and traced — and the whole pipeline is one versioned, rollback-able workflow. LangGraph contributes exactly what it's best at: the branching and state machine between governed steps.
The boundary is per node
Governance applies to calls made through ctx.run_agent. If a node calls a raw
LangChain model directly, that call is invisible to BoundFlow — its cost and token
use never enter the ledger, so cost-based policies can't see it. Route reasoning
through ctx.run_agent to keep it governed.
add_context accumulates for the whole operation
Context added via ctx.add_context is not cleared between ctx.run_agent calls —
it builds up across the operation. Add each distinct input once (as the example
does) rather than re-adding the same key in a loop.
Runnable example: boundflow.examples.langgraph_workflow.
When to reach the other way (BoundFlow agent as a tool)¶
If your app is already a LangGraph/LangChain agent and you only want to pull in one governed BoundFlow agent as a step, you can invoke a workflow from a node with the ControlPlaneClient (invoke_workflow → poll get_request_info → branch on run_outcome). Note that invoking a workflow is fire-and-observe: it returns the run's outcome, not the agent's output payload — so this fits governed actions (gated, cost-capped side effects), not returning a generated value back into the graph. For "run my graph under governance," prefer LangGraph inside the workflow, above.