An AI agent calling your microservice is different from a human calling your microservice. The agent might be acting on a forged instruction, outside its delegated scope, or after its authority was revoked. AGP gives every service call a provenance — a signed, policy-cleared action envelope that proves the call was authorized before it was made.
AGP is language and framework agnostic. The pattern works the same whether your services are Python FastAPI, Node.js Express, Go, Java Spring, or anything else. All that's required is that the service — or something in front of it — can make an HTTP call to the AGP verification endpoint.
Choose based on how much control you have over the infrastructure layer:
Verify at the API gateway before any request reaches your services. One integration point covers all services.
Verification middleware inside each service. More granular — each service checks its own required scope.
Service calls the AGP verification endpoint directly in its handler. No infrastructure changes needed.
The agent attaches the AGP action envelope ID as a request header. The gateway calls the AGP verification endpoint before routing. If verification fails, the request is rejected at the edge — your services never see it.
from agp import AGPClient agent = AGPClient("https://your-agp-server", client_id="my-agent", client_secret="...") # Run the AGP pipeline to obtain a signed action envelope with agent.task_session( principal_id="my-agent", requested_outcome="Fetch customer records for Q3 analysis", risk_tier="medium", ) as session: session.bind(sponsoring_entity="analytics-team", accountable_owner="lead@example.com", jurisdiction="EU") session.issue_capability(subject_agent="my-agent", issuer="analytics-team", principal_id="my-agent", permitted_actions=["customer_records.read"]) session.decide(agent_id="my-agent", selected_action="customer_records.read", rationale="Q3 analysis, read-only, no PII export", uncertainty_score=0.05) receipt = session.execute(agent_id="my-agent", tool_id="customer-service", operation={"query": "q3_cohort"}, capability_token_ref=session.capability_token.capability_id) # Call the microservice — attach the action ID as a header import httpx response = httpx.get( "https://api.internal/customer-service/records", params={"query": "q3_cohort"}, headers={"X-AGP-Action-ID": receipt.action_id}, )
Example using nginx with a Lua auth subrequest, or any gateway that supports external auth (Kong, AWS API Gateway, Envoy, Traefik):
-- nginx + lua-resty-http: external auth subrequest access_by_lua_block { local http = require("resty.http") local action_id = ngx.req.get_headers()["X-AGP-Action-ID"] if not action_id then ngx.status = 401 ngx.say('{"error":"X-AGP-Action-ID header required"}') ngx.exit(401) end local httpc = http.new() local res = httpc:request_uri( "https://your-agp-server/agp/action-envelopes/" .. action_id, { method = "GET", headers = { ["Authorization"] = "Bearer " .. agp_token } } ) if res.status ~= 200 then ngx.status = 403 ngx.say('{"error":"AGP envelope invalid or revoked"}') ngx.exit(403) end }
Verification middleware in each service gives you per-route scope enforcement. Different endpoints can require different AGP scopes — a write endpoint requires a write-scoped envelope, a read endpoint accepts a read-scoped one.
# FastAPI dependency — verifies the AGP action envelope on each request import httpx from fastapi import Depends, Header, HTTPException AGP_SERVER = "https://your-agp-server" AGP_TOKEN = "..." # service's own AGP bearer token async def require_agp_envelope( action_id: str = Header(alias="X-AGP-Action-ID"), required_action: str = "*", ): """Verify the AGP action envelope and confirm the action is in scope.""" async with httpx.AsyncClient() as client: r = await client.get( f"{AGP_SERVER}/agp/action-envelopes/{action_id}", headers={"Authorization": f"Bearer {AGP_TOKEN}"}, ) if r.status_code != 200: raise HTTPException(403, detail="AGP envelope invalid or revoked") envelope = r.json() # Verify the action matches what this endpoint expects if required_action != "*" and envelope["tool_id"] != required_action: raise HTTPException(403, detail=f"Envelope scoped to '{envelope['tool_id']}', not '{required_action}'") return envelope # Apply per-route — different endpoints can require different scopes from fastapi import FastAPI app = FastAPI() @app.get("/records") async def get_records( envelope = Depends(lambda: require_agp_envelope(required_action="customer_records.read")) ): # Only reached if envelope is valid and scoped to customer_records.read ... @app.post("/records") async def create_record( envelope = Depends(lambda: require_agp_envelope(required_action="customer_records.write")) ): # Requires a write-scoped envelope — a read envelope is rejected here ...
// TypeScript / Express — AGP envelope middleware import express, { Request, Response, NextFunction } from "express"; const AGP_SERVER = "https://your-agp-server"; function requireAGPEnvelope(requiredAction?: string) { return async (req: Request, res: Response, next: NextFunction) => { const actionId = req.headers["x-agp-action-id"] as string; if (!actionId) { return res.status(401).json({ error: "X-AGP-Action-ID header required" }); } const r = await fetch(`${AGP_SERVER}/agp/action-envelopes/${actionId}`, { headers: { Authorization: `Bearer ${process.env.AGP_TOKEN}` }, }); if (!r.ok) { return res.status(403).json({ error: "AGP envelope invalid or revoked" }); } const envelope = await r.json(); if (requiredAction && envelope.tool_id !== requiredAction) { return res.status(403).json({ error: `Envelope scoped to '${envelope.tool_id}'` }); } res.locals.agpEnvelope = envelope; next(); }; } // Apply per-route const router = express.Router(); router.get("/records", requireAGPEnvelope("customer_records.read"), getRecords); router.post("/records", requireAGPEnvelope("customer_records.write"), createRecord);
The simplest integration: the service handler calls the AGP verification endpoint directly, with no middleware or gateway changes. Good for adding governance to a single high-risk endpoint without broader infrastructure changes.
# Minimal: verify inline in the handler import httpx def approve_payment(vendor: str, amount_usd: float, agp_action_id: str): # Verify the action envelope before doing anything r = httpx.get( f"https://your-agp-server/agp/action-envelopes/{agp_action_id}", headers={"Authorization": f"Bearer {AGP_TOKEN}"}, ) if r.status_code != 200: raise PermissionError("No valid AGP action envelope") envelope = r.json() if envelope["tool_id"] != "approve_payment": raise PermissionError("Envelope not scoped for approve_payment") if envelope.get("operation", {}).get("amount_usd", 0) < amount_usd: raise PermissionError("Amount exceeds what was authorized in the envelope") # All checks pass — proceed _do_approve_payment(vendor, amount_usd)
The AGP reference server is itself a microservice — deploy it alongside your existing services. It has no external dependencies beyond a database for storing governance objects. The Python SDK and TypeScript SDK handle authentication and connection management for your agent-side code.
# docker-compose.yml — AGP alongside your services services: agp-server: image: python:3.12-slim command: uvicorn main:app --host 0.0.0.0 --port 8099 working_dir: /app volumes: [./server:/app] environment: AGP_AUTH_SECRET: ${AGP_AUTH_SECRET} AGP_CLIENT_ID: ${AGP_CLIENT_ID} AGP_CLIENT_SECRET: ${AGP_CLIENT_SECRET} ports: ["8099:8099"] payment-service: build: ./payment-service environment: AGP_SERVER_URL: http://agp-server:8099 AGP_TOKEN: ${PAYMENT_SERVICE_AGP_TOKEN} customer-service: build: ./customer-service environment: AGP_SERVER_URL: http://agp-server:8099 AGP_TOKEN: ${CUSTOMER_SERVICE_AGP_TOKEN}
tool_id. A read-scoped envelope is rejected by a write endpoint — even if the agent presents a valid token. Scope is checked at verification time, not at issuance time.