paybondpaybond
Sign in

LangGraph agent spend controls

LangGraph agent spend controls — ToolNode awrapToolCall hooks, spend verify, and automatic evidence via @paybond/langgraph. TypeScript and Python parity.

Paybond integrates with LangGraph at the ToolNode boundary — spend verify runs before side-effecting tools execute, then auto-evidence fires after success. Model inference stays on your provider client; Paybond guards paid and side-effecting tool calls only.

TypeScript and Python parity — use the tab matching your kit below.

Install

Install

npm install @paybond/langgraph @langchain/langgraph @langchain/core

Import from @paybond/langgraph

import {
  createPaybondLangGraphHooks,
  paybondAwrapToolCall,
} from "@paybond/langgraph";
  • Equivalent subpath on the core package: `@paybond/kit/langgraph` — use `@paybond/kit` when you need multiple adapters in one app.
  • Python: `paybond agent demo langgraph smoke` requires the optional `langgraph` extra (`langchain-core` + `langgraph`). Base `pip install paybond-kit` and plain `pipx run --spec paybond-kit paybond …` do not include them — use `pip install "paybond-kit[langgraph]"`, `pipx inject paybond-kit langgraph langchain-core` (when base paybond-kit is already installed), or `pipx run --spec 'paybond-kit[langgraph]' paybond …`.

One-liner (sandbox): paybond.instrument({ policy, framework: "langgraph", tools, sandbox: true }) or paybond.agent({ policy, framework: "langgraph", tools }) returns awrapToolCall / createToolNode hooks. Production: omit sandbox and call instrumented.bind() per session.

TypeScript

import { Paybond } from "@paybond/kit";

const paybond = await Paybond.open({ apiKey: process.env.PAYBOND_API_KEY! });

const { awrapToolCall, createToolNode } = await paybond.instrument({
  policy: "./paybond.policy.yaml", // or preset id "travel"
  framework: "langgraph",
  tools: [bookHotelTool, searchWebTool],
});

const node = createToolNode!(tools, { awrapToolCall });

Python

from paybond_kit import Paybond

paybond = await Paybond.open(api_key=os.environ["PAYBOND_API_KEY"])

result = await paybond.instrument(
    policy="./paybond.policy.yaml",
    framework="langgraph",
    tools=[book_hotel_tool, search_web_tool],
)

node = result.create_tool_node(tools)

Already bound a run? Use createPaybondLangGraphHooks(run) / create_paybond_langgraph_hooks(run) — LangGraph does not wrap tools in place via wrapTools.

See Agent middleware for run binding, registry rules, and tenant isolation.

Advanced / manual wiring

When you need step-by-step control over registry and bind:

  1. Bind a PaybondAgentRun with a tool registry (sandbox bootstrap or production attach).
  2. Register side-effecting tools on the registry so spend resolvers read tool args (for example price_cents from request.tool_call["args"]).
  3. Wrap ToolNode with Paybond hooks so authorization and evidence run on every side-effecting call.

Python

from langgraph.prebuilt import ToolNode
from paybond_kit.langgraph_hooks import paybond_awrap_tool_call

node = ToolNode(
    tools,
    awrap_tool_call=paybond_awrap_tool_call(run),
)

Legacy paybond_awrap_tool_call_capability(binding) is deprecated (emits DeprecationWarning); use paybond_awrap_tool_call(run) with a bound tool registry.

TypeScript

import { paybondAwrapToolCall, paybondToolNode } from "@paybond/kit/langgraph";

// Option A: hook only (when LangGraph JS exposes awrapToolCall on ToolNode)
const node = new ToolNode(tools, {
  awrapToolCall: paybondAwrapToolCall(run),
});

// Option B: convenience factory (works today — subclasses ToolNode)
const node = paybondToolNode(tools, run);

paybondLangGraphToolSpendGuard is a deprecated alias for manual guardTool wiring; migrate to @paybond/kit/langgraph or per-tool guardTool only for single-handler escape hatches.

Approval holds vs hard denials

When Paybond returns an approval hold, surface it to operators, approve in the tenant console, then retry with the same operation, amount, metadata, and approvalToken. Hard denials must not execute the tool.

See Agent integrations — approval holds for the full retry pattern.

Policy-as-code

Load paybond.policy.yaml before bind so registry and intent alignment stay reviewable in Git:

paybond agent run bind --policy-file paybond.policy.yaml --format json

See Agent policy-as-code and Policy hot-reload for long-lived graphs that need runtime policy updates without re-binding intents.

Scaffold and smoke

The smoke command exercises authorize → execute → auto-evidence without a live LLM.

Python optional extra: paybond agent demo langgraph smoke imports LangGraph and LangChain. Base pip install paybond-kit — and plain pipx run --spec paybond-kit paybond … — does not install those dependencies. Install the langgraph extra first:

pip install "paybond-kit[langgraph]"

Global install via pipx (quote [extras] on zsh):

pipx install 'paybond-kit[langgraph]'
# If base paybond-kit is already installed:
pipx inject paybond-kit langgraph langchain-core

One-shot via pipx (no global install). The package is paybond-kit; the CLI is paybond — use --spec on pipx run, not on pipx install:

pipx run --spec 'paybond-kit[langgraph]' paybond agent demo langgraph smoke \
  --operation paid-tool \
  --requested-spend-cents 100 \
  --evidence-preset cost_and_completion \
  --format json

When the extra is missing, the Python CLI exits with an install hint instead of a ModuleNotFoundError traceback. See Python quickstart — pipx for global CLI install patterns.

paybond init agent-middleware --framework langgraph --out paybond-langgraph.ts

paybond agent demo langgraph smoke \
  --operation paid-tool \
  --requested-spend-cents 100 \
  --evidence-preset cost_and_completion \
  --format json

Example apps: examples/paybond-kit-langgraph-python, examples/paybond-kit-langgraph-typescript.