# ACT + ECT + MCP + LangGraph — end-to-end PoC A working demonstration of `draft-nennemann-act-01` (Agent Context Token) and `draft-nennemann-wimse-ect-01` (Execution Context Token) in a realistic agent stack: * a **LangGraph** ReAct agent driven by a local **Ollama** LLM; * talking over **MCP** streamable-HTTP to a FastMCP server; * every request carries an **ACT** mandate, a per-call **ECT**, and an **RFC 9421** HTTP signature with the `wimse-aud` parameter from `draft-ietf-wimse-http-signature-03`; * the server rejects any request where ACT / ECT / HTTP-signature / capability / body-hash binding fails; * a verifier CLI replays the run's ledger, re-runs the two refimpls, and prints the resulting DAG. ## Why this exists The two drafts (ACT and ECT) claim to fit together — ACT giving the lifecycle (mandate → execution record) and ECT giving the per-call execution context on the wire. This PoC proves the claim end to end: the same refimpls that ship in `workspace/packages/{act,ect}/` are the only crypto/verification layer used here. There is no token forgery shortcut. ## Requirements * `uv` ([install](https://docs.astral.sh/uv/)) * Local Ollama with a chat model pulled (`qwen3:8b` by default) * Python 3.11+ (uv will fetch one if missing) ## Run the demo ```bash ./demo.sh ``` This script: 1. Syncs deps (uv installs the sibling `ietf-act` and `ietf-ect` packages in editable mode, plus `mcp`, `langgraph`, `langchain-ollama`, `langchain-mcp-adapters`, `fastapi`, …). 2. Launches the MCP server on `127.0.0.1:8765`. 3. Runs the agent (`poc-agent`) against it with a canned research task. 4. Runs the verifier (`poc-verify`) over the ledger the agent emitted. 5. Shows the last five server-audit entries. Expected tail of output: ``` mandate verified jti=64f5ec87 ects verified n=7 (tool-calls=2, session=5) record verified jti=64f5ec87 status=completed ect-dag wellformed every pred is the mandate or a prior ECT Run === mandate 64f5ec87 task='Search for quantum entanglement, …' iss=user sub=agent aud=mcp-server cap=['mcp.session.initialize', 'mcp.session.list_tools', 'mcp.session.other', 'mcp.search', 'mcp.summarize'] Tool-call ECT DAG: ect 73af4cd3 exec_act=mcp.search pred=['64f5ec87'] ect 0e3ffa01 exec_act=mcp.summarize pred=['64f5ec87', '73af4cd3'] ACT Phase 2 record: jti=64f5ec87 exec_act=mcp.summarize status=completed pred=[] inp_hash=… out_hash=… ``` The mandate jti and the record jti are identical — this is ACT §3.2: the Phase 2 token records the same task it started as. The tool-call ECT DAG captures the per-HTTP-request ordering. ## Architecture ``` user ──(mints ACT mandate)──► agent │ │ create_react_agent(ChatOllama, tools) ▼ ┌──────────────────────────────┐ │ LangGraph ReAct loop │ │ LLM decides tools to call │ │ │ │ langchain-mcp-adapters │ │ ↑ streamable_http session │ └──────────┬───────────────────┘ │ httpx.AsyncClient with event hooks │ ┌───────────▼─────────────┐ │ on_request: │ │ - mint ECT(inp_hash) │ │ - RFC 9421 sign │ │ - attach Authorization │ │ + Wimse-ECT + sig │ └───────────┬──────────────┘ │ POST /mcp │ ┌───────────▼──────────────┐ │ FastMCP streamable-http │ │ + ActEctAuthMiddleware │ │ verifies: │ │ ACT mandate │ │ ECT │ │ HTTP-signature │ │ inp_hash == body │ │ exec_act in cap │ │ ECT.iss == sub │ │ then dispatches tool │ └──────────────────────────┘ ``` ## Files * `src/poc/keys.py` — ES256 keys for the three PoC identities (`user`, `agent`, `mcp-server`). * `src/poc/tokens.py` — thin wrappers around `ietf-act` and `ietf-ect` that fix the PoC's shape. * `src/poc/http_sig.py` — minimal RFC 9421 signer/verifier covering `@method`, `@target-uri`, `content-digest`, `wimse-ect`, with the `wimse-aud` metadata parameter from http-signature-03. * `src/poc/server.py` — FastMCP server with ACT + ECT + signature middleware. Writes `keys/server-audit.jsonl`. * `src/poc/agent.py` — LangGraph + Ollama agent. Writes `keys/ledger.jsonl` — one mandate, N ECTs, one final ACT record. * `src/poc/verify_cli.py` — ledger verifier, prints the DAG. * `tests/` — pytest suite (see below). ## Non-goals * No real LLM API costs (Ollama is local). * No distributed SCITT anchoring — server audit log is a plain JSONL. * No Go-side client in this PoC; Python ↔ Python. Go refimpl lives in `workspace/drafts/ietf-wimse-ect/refimpl/go-lang/`. * The PoC uses a single mandate per run and a single Phase 2 record (ACT §3.2 jti preservation). If you want a multi-record DAG of ACT tasks, you'd need to exercise the delegation machinery (`act.delegation.create_delegated_mandate`) — this PoC does not, to keep the wire story tight. ## Tests ```bash uv run pytest ``` Covers: * Minting + round-trip verification of each token type. * HTTP-signature round-trip. * Middleware rejection paths (missing headers, wrong audience, stolen ECT on a mutated body). * End-to-end: launches an in-process server via httpx's ASGI transport and runs the agent's token-injection hooks over it (no Ollama required — uses a fake LLM).