# AEVS — Agent Execution Verification System > AEVS produces tamper-evident, cryptographically signed receipts for every tool call your AI agent makes. Receipts are HMAC-signed, hash-chained, KMS-anchored, and independently verifiable by anyone holding a receipt's `reference_id`. Integration is two lines of code with no changes to your tools, agents, or LLM setup. By Fetch.ai. This file is self-contained: a coding agent can integrate AEVS into a supported framework and verify receipts using only the instructions below. Supported frameworks: LangChain / LangGraph and MCP (Model Context Protocol). Python 3.10+. Current SDK version: 0.2.2. The integration is always the same three steps regardless of framework: 1. `aevs.configure(api_key=..., agent_id=...)` — set credentials (call before enable). 2. `aevs.enable()` — patch the framework's tool dispatch; every tool call now produces a receipt. 3. Run the agent normally, then `aevs.flush()` and `aevs.disable()` before exit. ## Prerequisites - Python 3.10 or higher. - Credentials from the dashboard at https://aevs.fetch.ai (sign up, register an agent): - **API key** — format `aevs_sk__` (HMAC v1) or `aevs_sk2__` (ECDSA v2). The hex secret is at least 32 hex chars. Auth version is auto-detected from the prefix. - **Agent ID** — a canonical UUID with dashes, e.g. `550e8400-e29b-41d4-a716-446655440000`. - Credentials may instead be supplied via env vars `AEVS_API_KEY`, `AEVS_AGENT_ID`, and optionally `AEVS_RECEIPT_VISIBILITY`. Explicit params win over env vars. - If credentials are missing/invalid the SDK logs a warning and runs in **no-op mode** — the agent keeps working, receipts just are not recorded. AEVS never crashes your agent. ## Install ```bash pip install aevs # core only pip install aevs[langchain] # LangChain / LangGraph (requires langchain-core >= 0.2) pip install aevs[mcp] # MCP (requires mcp >= 1.20) ``` You may combine extras, e.g. `pip install "aevs[langchain,mcp]"`. ## Universal quickstart ```python import aevs aevs.configure( api_key="aevs_sk_myKeyId_a1b2c3d4...", # or set AEVS_API_KEY agent_id="550e8400-e29b-41d4-a716-446655440000", # or set AEVS_AGENT_ID ) aevs.enable() # auto-detects installed frameworks # ... run your agent / tools as usual; every tool call is now recorded ... refs = aevs.get_reference_ids(clear=True) # reference_ids to verify receipts for r in refs: print(r["tool_name"], r["reference_id"]) aevs.flush() # push buffered receipts to the backend now aevs.disable() # unpatch, final flush, shut down ``` `aevs.enable(frameworks=[...])` restricts patching to specific adapters: `"langchain"`, `"mcp"`. Omit `frameworks` to auto-detect everything installed. Multiple adapters can be active at once; AEVS deduplicates so the same tool call is never recorded twice. ## LangChain / LangGraph integration `aevs.enable()` patches `BaseTool.invoke` and `BaseTool.ainvoke`, so every `@tool` function and `BaseTool` subclass is intercepted automatically. Minimal, fully runnable with just `pip install aevs[langchain]` (no LLM/API key needed) — calling a tool directly produces a receipt: ```python import aevs from langchain_core.tools import tool @tool def add(a: int, b: int) -> int: """Add two numbers.""" return a + b aevs.configure(api_key="aevs_sk_...", agent_id="550e8400-...") # or set env vars aevs.enable(frameworks=["langchain"]) add.invoke({"a": 6, "b": 7}) # intercepted -> one receipt for ref in aevs.get_reference_ids(clear=True): print(ref["tool_name"], ref["reference_id"]) aevs.flush() aevs.disable() ``` Full LLM agent (additionally requires `pip install langchain langchain-openai` and an `OPENAI_API_KEY`): ```python import asyncio import aevs from langchain_openai import ChatOpenAI from langchain.agents import create_agent from langchain_core.tools import tool @tool def get_weather(city: str) -> str: """Get weather for a city.""" return f"Sunny, 25C in {city}" async def main(): aevs.configure(api_key="aevs_sk_...", agent_id="550e8400-...") aevs.enable(frameworks=["langchain"]) agent = create_agent(ChatOpenAI(model="gpt-4o-mini"), [get_weather]) response = await agent.ainvoke({"messages": [("user", "weather in London?")]}) for ref in aevs.get_reference_ids(clear=True): print(ref["tool_name"], ref["reference_id"]) aevs.flush() aevs.disable() asyncio.run(main()) ``` LangGraph notes: - All tool calls inside one compiled-graph execution (`create_agent` / `StateGraph` -> `CompiledStateGraph`) share one `invocation_id` (UUID v4). Separate `invoke`/`ainvoke`/`stream`/`astream` calls get different IDs; subgraphs inherit the parent's; direct `tool.invoke()` with no graph has `invocation_id = None`. - To map a specific tool call back to its receipt, use `aevs.get_reference_id(tool_message.tool_call_id)`. ## MCP (Model Context Protocol) integration `aevs.enable(frameworks=["mcp"])` patches `mcp.ClientSession.call_tool` (async only). ```python import asyncio import aevs from mcp import ClientSession, StdioServerParameters from mcp.client.stdio import stdio_client async def main(): aevs.configure(api_key="aevs_sk_...", agent_id="550e8400-...") aevs.enable(frameworks=["mcp"]) params = StdioServerParameters(command="python", args=["my_mcp_server.py"]) async with stdio_client(params) as (read, write): async with ClientSession(read, write) as session: await session.initialize() result = await session.call_tool("search", arguments={"query": "AI news"}) print(result) for ref in aevs.get_reference_ids(clear=True): print(ref["tool_name"], ref["reference_id"]) aevs.flush() aevs.disable() asyncio.run(main()) ``` MCP notes: - For SSE servers use `mcp.client.sse.sse_client` instead of `stdio_client`. - Binary content (images/audio) is never stored raw — the receipt records a SHA-256 hash and byte count instead. - Optional correlation IDs: `session.call_tool(..., meta={"run_id": "...", "parent_run_id": "..."})`. - If `langchain-mcp-adapters` bridges MCP tools into LangChain, AEVS auto-deduplicates so calls are recorded once. ## Configuration options Pass to `aevs.configure(...)`. Only `api_key` and `agent_id` are required. - `api_key` (required) — `aevs_sk__`. Falls back to `AEVS_API_KEY`. - `agent_id` (required) — canonical UUID with dashes. Falls back to `AEVS_AGENT_ID`. - `receipt_visibility` — `"private"` (default), `"public"`, or `"proof_only"`. Falls back to `AEVS_RECEIPT_VISIBILITY`. See visibility modes below. - `base_url` — default `https://api.aevs.fetch.ai/v1`. Change only for a self-hosted backend. - `signing_timeout_ms` — HTTP timeout for submission (default `2000`). - `float_handling` — `"decimal_string"` (default) or `"raise"`; `float_precision` — decimals (default `6`). - `max_payload_bytes` — max receipt payload (default `1048576` = 1 MB); larger inputs/outputs are truncated. - `buffer_path` — SQLite buffer file (default `~/.aevs/buffer.db`). - `max_buffer_records` — default `10000`; oldest pending receipt is evicted with a gap marker when full. - `drain_interval_ms` — background flush interval (default `5000`). - `max_reference_entries` — in-memory reference IDs kept (default `1000`, FIFO). Validation: invalid API key or agent ID -> no-op mode (warning logged). Reconfiguring while enabled is not allowed — call `disable()` first. Non-loopback `http://` base URLs trigger a security warning (use `https://`). ## Receipt visibility modes Set via `receipt_visibility` in `configure()`. Controls what payload data is included in each receipt; a separate per-agent dashboard toggle controls whether receipts are listed on the public explorer. - `"public"` — full inputs/outputs stored and visible to anyone with the `reference_id`. Best for demos and shared audits. - `"private"` (default) — full inputs/outputs stored but only your account can view them; public verify/explorer show `[REDACTED]`. Tool name, status, timing, signatures, and hash chain remain public. - `"proof_only"` — inputs/outputs stripped to `null` before submission; the SDK still records `input_hash` / `output_hash` (SHA-256) so you can prove specific data was processed without it ever leaving your host. Best for regulated data. ## Verifying receipts (show users their receipts are real) Every tool call yields a receipt with a unique **`reference_id`** (client-generated, available immediately in code) and a **`receipt_id`** (backend-assigned after storage). Either ID can be searched. Verification requires **no authentication**. 1. **From your code** — get IDs to share or store: ```python refs = aevs.get_reference_ids(clear=True) # each: {"seq", "tool_name", "reference_id", "run_id", "tool_call_id"} ref_id = aevs.get_reference_id("framework-run-id-or-tool-call-id") # single lookup, or None ``` 2. **Explorer (human-friendly)** — open https://explorer.aevs.fetch.ai and search by `reference_id` or `receipt_id` to see tool name, timing, status, chain, and (per visibility) payloads. 3. **Verify API (programmatic, no auth)**: ``` GET https://api.aevs.fetch.ai/v1/receipts/verify/ ``` Returns receipt metadata, hash-chain status, and KMS signature info. 4. **Public receipts API (no auth; only agents with explorer listing enabled)**: ``` GET /v1/public/receipts # list (pagination + filters) GET /v1/public/receipts/stats # system-wide counts GET /v1/public/receipts/lookup?q= # by receipt_id or reference_id GET /v1/public/receipts/{receipt_id} # full detail GET /v1/public/receipts/{receipt_id}/verify-kms # verify ECDSA P-256 KMS signature ``` What verification proves: authenticity (signed with a valid key), integrity (unmodified), ordering (valid position in the hash chain), timing, and `chain_status` (`anchor`, `linked`, `mismatch`, `gap`, `broken`, `unverified`). It proves the SDK recorded the call and its reported result — not that an external system actually performed the action. ## Core API surface - `aevs.configure(**options)` — set config; must precede `enable()`. - `aevs.enable(frameworks=None)` — start intercepting (auto-detect or list of `"langchain"`/`"mcp"`). - `aevs.disable()` — unpatch, final flush, shut down. - `aevs.flush()` — synchronous flush of buffered receipts (returns `None`; some may stay buffered if backend is unreachable). - `aevs.get_reference_ids(clear=False)` — list of receipt reference entries for the session. - `aevs.get_reference_id(lookup_id)` — `reference_id` for a `run_id`/`tool_call_id`, or `None`. - `aevs.clear_reference_ids()` — drop the reference registry. - `aevs.get_session_id()` — current session UUID, or `None` when disabled. - `aevs.is_healthy(threshold=3)` — `False` after N consecutive local-buffer write failures (does not check credentials/backend). - `aevs.__version__` — installed SDK version string. ## Key behaviors a coding agent should rely on - Receipts are buffered locally (encrypted SQLite) and drained by a background thread, so the agent is never blocked and survives crashes and network outages. A backend outage does NOT trigger no-op mode — receipts queue and flush when connectivity returns. - Tool errors are recorded (`status: "error"`, plus `error` text) and the original exception still propagates to your framework; AEVS never swallows tool errors. - Always call `aevs.flush()` (and ideally `aevs.disable()`) before a short-lived script exits, or recent receipts may not be sent yet. - One tool call = one receipt; receipts in a session are hash-chained for tamper evidence. ## Optional — deeper documentation These are supplementary; the sections above are sufficient to integrate and verify. Full source and docs: https://github.com/fetchai/AEVS-sdk - Getting Started: https://github.com/fetchai/AEVS-sdk/blob/main/docs/01-getting-started.md - Core Concepts (receipts, hash chains, sessions, invocation IDs): https://github.com/fetchai/AEVS-sdk/blob/main/docs/02-core-concepts.md - Configuration (all options): https://github.com/fetchai/AEVS-sdk/blob/main/docs/03-configuration.md - LangChain / LangGraph: https://github.com/fetchai/AEVS-sdk/blob/main/docs/04-langchain-integration.md - MCP: https://github.com/fetchai/AEVS-sdk/blob/main/docs/05-mcp-integration.md - Receipt Verification: https://github.com/fetchai/AEVS-sdk/blob/main/docs/06-receipt-verification.md - Security & Privacy: https://github.com/fetchai/AEVS-sdk/blob/main/docs/07-security-and-privacy.md - API Reference: https://github.com/fetchai/AEVS-sdk/blob/main/docs/08-api-reference.md - Troubleshooting: https://github.com/fetchai/AEVS-sdk/blob/main/docs/09-troubleshooting.md - Runnable examples: https://github.com/fetchai/AEVS-sdk/tree/main/examples - Get credentials: https://aevs.fetch.ai - Explorer: https://explorer.aevs.fetch.ai