> For the complete documentation index, see [llms.txt](https://docs.kyvvu.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.kyvvu.com/integrations/langchain.md).

# LangChain / LangGraph

**What you'll learn:** How to instrument LangChain and LangGraph agents using the `KyvvuLangChainHandler` callback handler.

***

## Setup

The same handler works for both LangChain and LangGraph — LangGraph uses `langchain_core` callbacks under the hood.

```python
from kyvvu import Kyvvu
from kyvvu.schemas import Environment, RiskClassification
from kyvvu.integrations.langchain import KyvvuLangChainHandler

kv = Kyvvu(
    api_key="KvKey-...",
    api_url="https://platform.kyvvu.com",
    agent_key="finance-agent",
    environment=Environment.DEVELOPMENT,
    risk_classification=RiskClassification.LIMITED,
)
kv.register_agent(
    name="Finance Agent",
    purpose="Stock ticker lookup and portfolio analysis for internal use",
    metadata={"framework": "langchain", "tools": ["search"]},
)

handler = KyvvuLangChainHandler(kv)
result = agent.invoke(query, config={"callbacks": [handler]})
```

The handler is a pure adapter. It doesn't manage identity or registration — the same `Kyvvu()` + `register_agent()` pattern as the decorator integration.

## Callback event mapping

The handler translates LangChain callback events to Kyvvu Behaviors. The built-in `langchain` template defines these mappings (rules are first-match-wins; order matters):

| LangChain callback                | Kyvvu step\_type | Verb   | Notes                                                                                                                          |
| --------------------------------- | ---------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------ |
| `on_chain_start` (top-level)      | `task.start`     | —      | Opens a new task; also emits a `step.message` GET for the user input                                                           |
| `on_chain_end` (top-level)        | `task.end`       | —      | Closes the task; emits a `step.message` POST for the response                                                                  |
| `on_chain_error` (top-level)      | `task.error`     | —      | Chain raised an exception — task failed                                                                                        |
| `on_agent_finish`                 | `task.end`       | —      | Agent produced its final answer — closes the task                                                                              |
| `on_message_in`                   | `step.message`   | `GET`  | User input received at chain start                                                                                             |
| `on_message_out`                  | `step.message`   | `POST` | Response sent to the user at chain end                                                                                         |
| `on_llm_start`                    | `step.model`     | `POST` | Completion-style LLM call; model name in `properties.model.name`                                                               |
| `on_chat_model_start`             | `step.model`     | `POST` | Chat-style LLM call; model name in `properties.model.name`                                                                     |
| `on_retriever_start`              | `step.resource`  | `GET`  | Read-only retrieval; `properties.target.resource_type = retriever`                                                             |
| `on_tool_start` (read-like name)  | `step.resource`  | `GET`  | Name matches `search`/`fetch`/`read`/`get`/`list`/`query`/`retrieve`/`lookup`/`find`/`scan`                                    |
| `on_tool_start` (write-like name) | `step.resource`  | `POST` | Name matches `write`/`send`/`post`/`create`/`update`/`delete`/`remove`/`execute`/`run`/`call`/`patch`/`push`/`submit`/`upload` |
| `on_tool_start` (unclassified)    | `step.resource`  | `GET`  | Default when no name pattern matches                                                                                           |
| `on_custom_event`                 | `step.unknown`   | —      | Application-defined `adispatch_custom_event`                                                                                   |

Tool verbs are derived from the tool's **name pattern**: read-like names map to `GET`, write-like names to `POST`, and anything else falls back to `GET`. Each event goes through the template system; a custom template can override any of these mappings.

## Nested chain resolution

LangChain/LangGraph agents often produce deeply nested chain callbacks. The handler resolves nested chains to keep the behavioral trace readable:

* Top-level chain events produce Behaviors.
* Deeply nested internal chains are collapsed unless they represent distinct logical steps.
* Tool calls within chains are always surfaced.

## Block propagation

When a policy blocks a step during a LangChain callback:

1. The handler receives the `KyvvuBlockedError`.
2. With `raise_error=True` (default), the error propagates up through the LangChain callback chain, halting execution.
3. The agent code can catch `KyvvuBlockedError` at the top level.

```python
from kyvvu import KyvvuBlockedError

handler = KyvvuLangChainHandler(kv)
try:
    result = agent.invoke(query, config={"callbacks": [handler]})
except KyvvuBlockedError as exc:
    print(f"Policy blocked: {exc}")
```

## Task lifecycle

The handler automatically manages task lifecycle:

* A task starts on the first callback event.
* The task ends when the top-level chain completes.
* If the chain raises an exception, `error_task()` is called.

## Attaching Kyvvu attributes to tools: two layers

Out of the box, the built-in `langchain` template gives every step a correct `step_type` and `verb`. To drive richer policies you often want extra properties on a step — a data classification, a target system, a trust level. There are two first-class ways to attach them, and they compose.

### Org-wide behavior template (base layer)

A **behavior template** is a shared YAML file (`.btmpl` / `.yaml`) that maps tools by name-pattern to Kyvvu properties. Point the handler at it with the `KV_TEMPLATE_LOCATION` environment variable or the `template=` constructor kwarg. Because it lives in one file, it is the natural home for **fleet-wide policy**: one team owns it, every agent that loads it inherits the same baseline, and you can classify tools you don't own without touching their code.

```python
from kyvvu_engine.templates import BehaviorTemplate
from kyvvu.integrations.langchain import KyvvuLangChainHandler

template = BehaviorTemplate.from_path("org_langchain_template.yaml")
kv = Kyvvu(api_key="...", agent_key="finance-agent", template=template)
handler = KyvvuLangChainHandler(kv)
```

The template rule below classifies any tool named `get_invoice` as financial data. It matches on the tool name and sets the same property a tool could set about itself:

```yaml
rules:
  - id: invoice_tool
    description: Invoice lookups handle financial data
    match:
      event: "on_tool_start"
      name_pattern: "^get_invoice$"
    behavior:
      step_type: "step.resource"
      verb: "GET"
      step_name: "{{ name }}"
      properties:
        data:
          classification: financial
```

Rules are first-match-wins and fall through to the built-in `langchain` template for any event the org template doesn't match.

### Per-tool `metadata["kyvvu"]` (local override)

A tool can also declare its own Kyvvu attributes, co-located with the tool definition, under the reserved `metadata["kyvvu"]` key. The value is shaped exactly like a step's `properties`:

```python
from langchain_core.tools import Tool

get_invoice = Tool(
    name="get_invoice",
    func=fetch_invoice,
    description="Fetch an invoice by ID",
    metadata={"kyvvu": {"data": {"classification": "financial"}}},
)
```

This produces the same `data.classification: financial` property as the org template rule above — but it travels with the tool, which is ideal for tools **you own** or for local nuance the fleet template shouldn't have to know about. The handler forwards `metadata["kyvvu"]` for tools, retrievers, and models (`on_tool_start`, `on_retriever_start`, `on_llm_start` / `on_chat_model_start`).

### Precedence

When both layers set a property, **tool metadata deep-merges over the template-matched properties**: the tool's value wins on any leaf conflict, and sibling keys from the template are preserved. So in the example above, if the org template classified `get_invoice` as `internal` and the tool declared `financial`, the emitted step carries `financial`. A deduped runtime warning is logged whenever a tool overrides a template-set property, so silent divergence is visible in your logs.

### When to use which

* **Org template** — fleet-wide policy, consistent classification across many agents, and tools you don't own (third-party or vendor tools). One file, one owner.
* **Per-tool metadata** — tools you own, or a local nuance that belongs next to the tool definition. Wins on conflict, so it's also the escape hatch when a specific tool needs to diverge from the baseline.

### Framework tags

Framework `tags` on a run are forwarded into `properties.tags`, minus internal LangChain/LangGraph bookkeeping tags (those prefixed `seq:`, `graph:`, or `langsmith:`). With no metadata and no tags, step properties are unchanged from the template match.

## LangGraph-specific notes

LangGraph agents use the same handler. Pass via `config`:

```python
from langgraph.graph import StateGraph

graph = StateGraph(...)
# ... build graph ...
app = graph.compile()

handler = KyvvuLangChainHandler(kv)
result = app.invoke(input_data, config={"callbacks": [handler]})
```

The behavioral trace stays flat — there are no node-transition pseudo-steps. Tool calls within nodes appear as `step.resource` events, and (with `KyvvuLangGraphHandler`) each step records its originating node under `properties.langgraph.node_name`.

### Dedicated LangGraph handler

For richer graph-aware tracing, use `KyvvuLangGraphHandler` (`kyvvu.integrations.langgraph`), a subclass of `KyvvuLangChainHandler` that adds two things:

* **Node metadata** — injects `properties.langgraph.node_name` from the callback metadata, so the trace records which graph node each step ran in.
* **Interrupt mapping** — maps LangGraph's `GraphInterrupt` (raised by `interrupt()`) to a `step.gate` behavior, so human-in-the-loop breakpoints are governable by gate policies.

```python
from kyvvu.integrations.langgraph import KyvvuLangGraphHandler

handler = KyvvuLangGraphHandler(kv)
result = app.invoke(input_data, config={"callbacks": [handler]})
```

The shared `KyvvuLangChainHandler` still works for LangGraph; the dedicated handler is opt-in when you want node metadata or HITL gate mapping.

### Parallel tool calls within a step

When the LLM emits multiple `tool_use` blocks and the runtime dispatches them concurrently (e.g., `asyncio.gather` in LangGraph), Kyvvu sees N sibling tool calls under the same parent `run_id`. The behavioral trace timestamps them in completion order, not invocation order.

**Path policies operate across steps, not within them.** A policy like "must call X before Y" is well-defined when X and Y are in different steps (X in step 1, Y in step 2). It is ill-defined when X and Y are dispatched concurrently within the same step — their ordering is non-deterministic. Policy authors should specify ordering constraints across steps, not between parallel calls within one.

**Kyvvu evaluates the path; it does not serialise the dispatcher.** If a policy requires sequential execution, the developer must enforce it in the runtime — sequential awaits, or a single-tool-per-step constraint in the agent prompt. Kyvvu records what happened; it does not control dispatch order.

***

## Integration status

| Framework                 | Status    | Notes                                                                                                                    |
| ------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------ |
| LangChain                 | Available | Full support via `KyvvuLangChainHandler`.                                                                                |
| LangGraph                 | Available | Shared `KyvvuLangChainHandler`, or `KyvvuLangGraphHandler` for node metadata and `GraphInterrupt` → `step.gate` mapping. |
| CrewAI                    | Available | Native support via `KyvvuCrewAIListener` (`kyvvu.integrations.crewai`), wired to the CrewAI event bus.                   |
| Microsoft AutoGen         | Planned   | Native adapter in development.                                                                                           |
| Microsoft Semantic Kernel | Planned   | Native adapter in development.                                                                                           |

Interested in a framework not listed here? Contact us at <support@kyvvu.com>.

***

## Next steps

* [Python Decorator](/integrations/decorator.md) — alternative integration for custom Python agents
* [Writing a New Integration](/integrations/custom.md) — build a handler for another framework
* [Templates](/core-concepts/templates.md) — customize the event-to-Behavior mapping
