---
title: "LangGraph: hosted tools and human approval"
description: "Call an Anlyon hosted action from a LangGraph node, keep provider credentials outside the graph, and recover by invocation ID."
---

LangGraph manages graph execution. Anlyon executes the named external action. As of 2026-09-30, a LangGraph `interrupt()` runs in your process and does not hold credentials. It does not approve a pending Anlyon invocation.

## Prerequisites

Complete the [quickstart](/quickstart) in a test environment. Define the hosted template action `refund-order` with `charge` and `amount` inputs and require approval. Configure the provider secret using an operator credential outside the graph. The graph runtime needs an Anlyon agent credential with `actions:invoke` and `actions:read`. It does not need the provider key or `approvals:decide`.

The following is a node integration pattern, not a complete persisted graph application. Install `anlyon` in your application and supply the prepared action. The host application must persist the operation key with the business request before running this node.

```python
import os
from typing import TypedDict
from anlyon import Client


class RefundState(TypedDict):
    charge: str
    amount: int
    operation_key: str
    invocation_id: str
    status: str


def submit_refund(state: RefundState) -> dict[str, str]:
    with Client(api_key=os.environ["ANLYON_AGENT_KEY"]) as agent:
        result = agent.actions.invoke(
            "refund-order",
            {"charge": state["charge"], "amount": state["amount"]},
            idempotency_key=state["operation_key"],
        )
        if result.data is None:
            raise RuntimeError("No invocation returned; recover with the same request identity")
        return {
            "invocation_id": result.data["id"],
            "status": result.data["status"],
        }


def read_refund(state: RefundState) -> dict[str, str]:
    with Client(api_key=os.environ["ANLYON_AGENT_KEY"]) as agent:
        result = agent.actions.invocation(state["invocation_id"])
        if result.data is None:
            raise RuntimeError("Invocation unavailable; do not submit a new refund")
        return {"status": result.data["status"]}
```

Register these functions as nodes in your `StateGraph`. Initialize the state from trusted application data, use a durable checkpointer for restart recovery, and persist the returned invocation ID. After submission, return pending work to your application or resume at the read node. Do not repeatedly submit new operations while waiting.

## Approval is not completion

| Status | Application behavior |
| --- | --- |
| `pending_approval` | Show the pending review. An authorized person decides in Anlyon |
| `running` | Read the same invocation later |
| `succeeded` | For this template action, report the recorded provider HTTP result |
| `unknown` | Request reconciliation. Do not generate another refund |
| `failed` | Inspect evidence and use the documented explicit retry path if appropriate |
| `denied` or `expired` | Stop. Do not resubmit to bypass the decision |

A resume payload such as `approved: true` is not an Anlyon decision. Authenticate reviewers in the application and keep decision credentials out of the graph. Never let model output supply reviewer authority.

## Restarts and interrupts

Check LangGraph's [interrupt documentation](https://docs.langchain.com/oss/python/langgraph/interrupts) for which code runs again when a graph resumes. Keep external writes out of code that runs before an interrupt. Once submitted, prefer reading the saved invocation. If submission was interrupted before its ID was persisted, the same template request and idempotency key recover its current state while the invocation exists.

For a new execution after a confirmed failure, follow [explicit retry and resolution](/execution/outcomes), including `retryOf`. Do not replace an unknown operation's key to force a new call.

## Governed actions

For an action with an adapter or a declaration, pass `operation_key` and retain `effectId`. An adapter action takes the adapter's input schema. Follow [governed-effect recovery](/execution/governed-effects), not the template invocation resolution path. Anlyon's current [beta](/beta) is not for critical production workloads.

See [framework and architecture choices](https://anlyon.com/guides/langgraph-tool-approval-credentials), [approval policies](/trust-control/approvals), and [idempotency](/execution/idempotency).
