LangGraph / LangChain
pip install "attestlayer[langgraph]"
from attest.adapters import langgraph as attest_lg
mapping = {"send_email": {"system": "gmail", "verb": "send", "target": "to", "readers": {"gmail": gmail}},
"update_deal": {"system": "hubspot", "verb": "update", "target": "deal_id"}}
tools = attest_lg.wrap_tools([send_email, update_deal], at, mapping=mapping) # before building the graph
graph = create_react_agent(model, tools)
attest_lg.wrap(graph, at, mapping=mapping) # or patch an existing graph's ToolNode(s) in place
agent = create_agent(model, tools, middleware=[attest_lg.AttestMiddleware(at, mapping=mapping)]) # langchain ≥ 1
Unmapped tools are inferred from their name. Refusals and rejections come back to the model as an error
ToolMessage so the run continues; the ledger records them either way.
Pausing the graph
from attest.gate.interrupt import InterruptGate
at = Attest(gate=InterruptGate())
graph = create_react_agent(model, tools, checkpointer=MemorySaver())
out = graph.invoke(state, config) # out["__interrupt__"][0].value is the confirm request
graph.invoke(Command(resume={"status": "approved", "approver": "ram"}), config) # or "rejected", or edits
See examples/langgraph_agent.py for an end-to-end run yielding two verified entries.