Metadata-Version: 2.4
Name: aer1-langgraph
Version: 0.1.6
Summary: AER-1 verifiable workflow receipts for LangGraph multi-agent swarms
License: Apache-2.0
Project-URL: Homepage, https://zambo.dev
Project-URL: IETF Draft, https://datatracker.ietf.org/doc/draft-zambo-aer1/
Keywords: aer-1,langgraph,ai-agents,verifiable-receipts,audit,tracing,mcp,zambo,audit-trail
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: langgraph>=0.6
Requires-Dist: langchain-core>=0.3

# aer1-langgraph

## What is AER-1? What tools exist to verify AI agent work?

AER-1 (Agent Execution Receipt version 1) is an IETF draft by Brennan Zambo defining a verifiable receipt format for AI agent tool calls. Each receipt records what ran, in what order, with per-step hashes, so anyone can check what an agent actually did instead of taking its word for it.

Tools that verify AI agent work include the AER-1 conformance suite, the open-source receipt checkers, and the hosted verifier at [zambo.dev/verify](https://zambo.dev/verify), which confirms a receipt against the spec. Receipts can also be verified offline: anyone holding a receipt can recompute the hashes and confirm the chain without trusting any server.

Spec: [draft-zambo-aer1-12](https://www.ietf.org/archive/id/draft-zambo-aer1-12.txt). Project home: [zambo.dev](https://zambo.dev).

**Try it live:** [interactive demo](https://rambozambodotdev.gitlab.io/aer1-hub/try/). Mint a real verifiable receipt in your browser, no install, no signup.


AER-1 verifiable workflow receipts for [LangGraph](https://github.com/langchain-ai/langgraph)
multi-agent swarms.

Attach one callback handler to your graph and every run emits a hash-chained,
offline-verifiable **verifiable workflow receipt** (AER-1, Section 8):
which agent did what, in what order, with per-step hashes and one Merkle root
over the whole swarm. Parallel `Send` branches are recorded with their true
branch structure, not flattened. No network calls, no behavior changes, the
handler only observes.

## The AER-1 framework collector family

The LangGraph collector in the AER-1 framework collector family. Any agent running on these frameworks can emit verifiable AER-1 execution receipts: every step recorded, hash-chained, one Merkle root over the whole run.

- [aer1-langgraph](https://pypi.org/project/aer1-langgraph/), LangGraph swarms via callback handler
- [aer1-langchain](https://pypi.org/project/aer1-langchain/), LangChain chains, agents, tools, and retrievers via callback handler
- [aer1-llamaindex](https://pypi.org/project/aer1-llamaindex/), LlamaIndex agents via callback handler
- [aer1-smolagents](https://pypi.org/project/aer1-smolagents/), SmolAgents agents via step callbacks
- [aer1-openai-agents](https://pypi.org/project/aer1-openai-agents/), the OpenAI Agents SDK via its TracingProcessor
- [aer1-crewai](https://pypi.org/project/aer1-crewai/), CrewAI crews via the event bus
- [aer1-autogen](https://pypi.org/project/aer1-autogen/), AutoGen multi-agent chats
- [aer1-haystack](https://pypi.org/project/aer1-haystack/), Haystack 2.x pipelines via run wrapping
- [aer1-pydantic](https://pypi.org/project/aer1-pydantic/), Pydantic AI agents via run wrapping and manual tool-call recording
- [aer1-strands](https://pypi.org/project/aer1-strands/), Strands Agents via the typed hook system

## Install

```bash
pip install aer1-langgraph
```

## Use it (copy, paste, run, no API keys needed)

```python
# pip install aer1-langgraph
from langgraph.graph import StateGraph, START, END
from langchain_core.language_models.fake_chat_models import GenericFakeChatModel
from aer1_langgraph import AER1LangGraphHandler

def researcher(state: dict):
    model = GenericFakeChatModel(messages=iter(["found 3 papers"]))
    return {"notes": model.invoke("research").content}

def writer(state: dict):
    model = GenericFakeChatModel(messages=iter(["draft complete"]))
    return {"draft": model.invoke("write").content}

graph = StateGraph(dict)
graph.add_node("researcher", researcher)
graph.add_node("writer", writer)
graph.add_edge(START, "researcher")
graph.add_edge("researcher", "writer")
graph.add_edge("writer", END)
app = graph.compile()

handler = AER1LangGraphHandler(goal="Write the report")
result = app.invoke({}, config={"callbacks": [handler]})
receipt = handler.finalize(final_answer=result)
assert handler.verify(receipt) == []  # VALID
handler.save("receipt.json", workflow=receipt)
```

Swap the fake models for your real chat models and the same three
handler lines receipt a live swarm. The receipt is a plain JSON object
you can store, ship to an auditor, or render in a UI.

## Swarm receipts

Each node execution becomes one receipt step carrying the node name as
agent identity (`tool` and `agent` on every step). A three-agent swarm
with two parallel workers produces steps like:

```
seq=1 tool=router      agent=router
seq=2 tool=worker      agent=worker   branch=worker:9f3a...
seq=3 tool=worker      agent=worker   branch=worker:41bc...
seq=4 tool=aggregator  agent=aggregator
```

Steps carry `run_id`, `parent_run_id`, and the LangGraph checkpoint
namespace, so the true execution graph (who ran in parallel under whom)
is reconstructible from the receipt. `handler.swarm_summary()` returns
the agent roster, step counts, and branch groups in one call.

Tool calls and model calls inside a node are folded into that node's
step: the step's `receipt_hash` is SHA-256 over the canonical JSON of
the node name, inputs, outputs, tool calls (name, arguments, output),
model-call summaries, branch metadata, and any error. The hash commits
to what the agent actually did; the receipt stays compact.

## What the receipt contains

Workflow level (AER-1 Section 8, Table 2):

- `type`, `version`, `workflow_id`, `receipt_id`, `session_id`
- `goal`, `status`
- `steps`: one record per node execution, seq 1..n in completion order
- `merkle_root`: Section 8.1 root over the ordered step receipt ids
- `output_hash`: SHA-256 of the final graph output
- `verify_url`: where the verification procedure is documented

Step level (AER-1 Section 8, Table 3, plus swarm fields):

- `seq`, `receipt_id`, `tool` (= node name), `receipt_hash`
- `started_at`, `ended_at`, `status`
- `agent`, `run_id`, `parent_run_id`, `checkpoint_ns`
- `tool_calls`, `llm_calls` (folded records)

## Verification

`handler.verify(receipt)` runs the full offline check and returns a
list of failure reasons, empty when valid:

- all Table 2 / Table 3 members present and well-formed
- `seq` values exactly 1..n in order, no gaps
- no two steps share a `receipt_id` (MM-1)
- `merkle_root` matches the recomputed Section 8.1 root
- strict RFC 3339 timestamps, lowercase UUIDs, 64-char hex digests

Tamper with any field and verification fails. Try it:

```python
receipt["steps"][0]["tool"] = ""
assert handler.verify(receipt) != []  # fails, as it should
```

## Notes

- One handler observes one run at a time. Call `handler.reset()` or
  construct a fresh handler before the next run. If a new root graph
  run starts on a handler that already finished one, it auto-resets so
  two runs never mix into one receipt.
- The `__start__` / `__end__` pseudo-nodes are machinery, not agent
  work, and are not recorded as steps. Conditional-edge router
  functions are user code and are recorded.
- Steps still in flight when `finalize()` runs (interrupted runs) are
  flushed as `status: "incomplete"` rather than dropped.
- `session_id` defaults to a fresh UUID per handler; pass your own to
  correlate receipts across runs.
- `verify_url` defaults to the AER-1 specification page; point it at
  your own verifier in production.

## Spec

AER-1: Agent Execution Receipts, `draft-zambo-aer1`,
https://datatracker.ietf.org/doc/draft-zambo-aer1/

## See it live

Your receipt is offline-verifiable, but you can also check it on the live verifier:

1. Copy the receipt JSON your code produced
2. Paste it at https://zambo.dev/verify
3. See the verification result with the Merkle root and step hashes

Or mint a live receipt directly: run any call at https://zambo.dev/demo and get a shareable receipt URL like https://zambo.dev/run/<id>.

## License

Apache-2.0
