This repository is archived and no longer maintained. Development continues at agentmiddleware/agent-middleware. Product site: agentmiddleware.dev. Apache 2.0 (gateway is Fair Source, FSL-1.1)
Agents retry. For consequential tools, a retry with the same key returns the original receipt instead of dispatching again.
Agent Middleware API puts an authorization and transaction boundary in front of MCP tools.
permit → execute → receipt
Same invocation again → same receipt. No second gateway dispatch. No second debit.
A scoped, signed permit says what the agent may do and what it may spend. One
accepted idempotency key buys at most one dispatch to the configured upstream
tool and at most one wallet debit. A replay returns the original result and
receipt; a changed payload under the same key fails closed with an error and no
new receipt. Once a call establishes a valid permit and an executable tool,
terminal outcomes on that path — success, denial, failure, or genuine
ambiguity — are Ed25519-signed receipts you can verify offline. Requests
rejected before that point (permit_required, permit_not_found, unknown
tool) can terminate without one.
This is not a full agent middleware platform, a payment network, an IAM replacement, or a compliance platform. It is a transaction control plane for consequential agent actions, and the supported deployment is vendor-managed and single-tenant.
agent
│ wallet key + permit + idempotency key
▼
┌─────────────────────────────────────────────────┐
│ Agent Middleware API │
│ │
│ permit tools · scope · budget · expiry │
│ policy allow / deny before any debit │
│ idempotency one accepted key, one record │
│ ──────────────────────────────────────────── │
│ dispatch fence claimed before the send │
└────────────────────────┬────────────────────────┘
│ at most one dispatch
▼
upstream MCP tool
│ result, or silence
▼
┌─────────────────────────────────────────────────┐
│ receipt · evidence · wallet audit chain │
│ Ed25519-signed, verifiable without us │
└─────────────────────────────────────────────────┘
The dispatch fence is a gateway record, not proof that the downstream effect
occurred. When a send is claimed and no trustworthy result comes back, the call
is receipted delivery_uncertain and never silently redispatched. See
docs/failure-semantics.md.
main is the v1.3.0 release candidate. The version is unreleased: the tag is
created only from a commit that passes the full release gate.
- Merged: the 2026-10-02 and 2026-10-03 QA remediation — 82 findings
(79 fixes, 3 explicit retirements: #511, #529, #570) — plus single-action
authority and schema 042 (
042_permit_action_binding). - Locally validated, at an earlier commit: full Python suite, fresh
PostgreSQL, browser, and SDK checks, recorded at
079bb72. Later merged commits (including the Jev runtime integration and receipt fixes) were not re-run as one local gate on the merged tree. Evidence: docs/issue-resolution-2026-10-03/. - Not verified: hosted CI on the merged head, any staging or production deployment, live provider and payment behavior, and customer acceptance.
- Single-action issuance is frozen.
POST /v1/action-permitsis not mounted or advertised by the normal application, and the configured upstream has no qualified action binding. Permits remain reusable budget envelopes. - Schema 042 is a one-way boundary. Boot refuses a database whose revision differs from the packaged head. Read docs/schema-042-rollout.md before any deploy; it records prerequisites, not permission to deploy.
Per-release detail: CHANGELOG.md.
Prerequisites: Python 3.11+, uv, and make.
git clone https://github.com/PetrefiedThunder/agent-middleware-api.git
cd agent-middleware-api
make demo-ambiguous-retryAn accounts-payable agent pays invoice INV-4417 — $250.00 to a vendor. The
payout succeeds. The response never reaches the agent. The agent, correctly,
retries.
The same tool runs twice, once without the boundary and once behind it:
WITHOUT a transaction boundary WITH Agent Middleware
PAY-0001 INV-4417 $250.00 PAY-0001 INV-4417 $250.00
PAY-0002 INV-4417 $250.00
2 payouts, $500.00 1 payout, $250.00
1 debit · same receipt on both
retry returned PAY-0001
The payout count is not a claim — it counts executions of the tool body. The
demo then reuses that spent key for a $9,500.00 payout (refused,
idempotency_key_reused, no money moved) and verifies the receipt offline
against the published key set with no credentials.
Then prove the rest of the loop asserts correctly:
make prove-trust-planeThat boots a local instance against a throwaway SQLite database and walks discover, authenticate, authorize, invoke, meter, receipt, audit, govern. It asserts rather than prints: the call charges once, the replay returns the same receipt with no second debit, the audit chain verifies, the out-of-scope call is denied, and a tampered receipt fails verification. It exits non-zero the moment any invariant breaks.
To measure the same fault against a correct baseline instead of a naive one:
make failure-labThat runs the lost-response fault against three integrations of one simulated payment rail — no idempotency key, a correctly used native idempotency key, and the gateway — with downstream effects counted by a record the gateway cannot reach. It reports when the native control already handles the fault (it does, for a client that keeps its key), what the gateway adds and does not add, and the one failure every configuration shares: an agent that restarts with a new key pays twice. Mechanism, report layout, and limits: docs/failure-lab.md.
To drive the loop yourself instead of watching it:
make quickstart # boots a real strict-trust server on 127.0.0.1:8000Then follow docs/quickstart.md: mint your own
wallet-scoped key with no operator and no pre-shared secret, issue yourself a
permit, invoke a governed tool, deliberately try to double-charge and overspend,
and finish holding a signed receipt you verified offline. Every step runs in CI
(make quickstart-check), so the documented path cannot silently rot.
make live-loop-proof (against a running quickstart) writes a handoff bundle to
data/live-loop-proof/ — portable receipts, the issuer key set, and a
VERIFY.md another engineer can follow with no account and no network access to
this server.
| Guarantee | What it means | Proof |
|---|---|---|
| Charge-once under retry | Same idempotency key → same receipt, no second dispatch, no second debit. A changed payload under that key conflicts. | tests/test_adversarial_five_claims.py |
| Budget containment | A permit's max_credits is reserved by one atomic guarded UPDATE; concurrent invocations cannot over-spend it, including on SQLite. |
tests/test_permits.py, postgres_permit_concurrency CI job |
| Interrupted-call accounting | For the configured upstream tool, one persisted chain links the idempotency record, reservation, debit, dispatch attempt, receipt, and audit event. Ambiguity after the send claim is receipted delivery_uncertain, never silently redispatched. Local governed tools have no dispatch state machine and fail closed into manual review. |
docs/failure-semantics.md |
| Offline-verifiable receipts | Ed25519-signed; verifiable with no credentials and no network access to the issuer, using the SDK verifier or any JOSE tooling. | GET /v1/receipts/{id}/portable, /.well-known/jwks.json |
| Authority before money | Out-of-scope, expired, revoked, or tampered permits are denied with a reason code before any charge. When a valid permit was present, that refusal is itself a signed receipt. Unpermitted and unknown-tool calls fail closed without one. | tests/test_adversarial_five_claims.py |
Two optional controls sit beside these guarantees and are not part of them:
- Duplicate guard (
MCP_UPSTREAM_DUPLICATE_GUARD=off|log|enforce, defaultlog).enforcedenies the same arguments under a new idempotency key on the same permit and tool within the window, withduplicate_request_new_key. Detection is scoped to onepermit_id, so it only helps flows that keep the original permit. OnPOST /mcp, a new key without apermit_idmints a fresh permit, which this guard cannot see. See docs/POLICY_ENFORCEMENT.md. - Wallet re-plan guard (
WALLET_REPLAN_GUARD=off|log_only|enforce, defaultlog_only, which never blocks). It keys on the wallet instead of the permit.enforcerefuses identical canonical arguments for the same tool across all of the wallet's permits within the window (wallet_duplicate_request), including the auto-minted permits onPOST /mcp. It also caps wallet credits and calls per window across all permits (wallet_velocity_exceeded). A re-plan that changes any argument value is still a new call, so this narrows the restart-with-a-new-key hole without closing it: at-most-once dispatch, not exactly-once execution. See docs/replan-guard.md. - Jev risk advisory (
JEV_RISK_GUARD=off|log|enforce, defaultoff). A probabilistic check run only after deterministic policy has allowed the call. It never turns a denial into an allow, can be wrong, and fails open on vendor errors. Redacted state leaves the gateway only after operator opt-in. See docs/jev-risk-guard.md.
CI runs the full release gate as one required check (trust_release_gate), so
these claims cannot regress into main unproven.
docs/PROOF_MATRIX.md maps every proof command to the
invariant it asserts — and to what it does not prove.
- Not universal exactly-once. Gateway replay safety makes the remote side effect exactly once only if the upstream tool also honors the forwarded idempotency key.
- Not proof of downstream effect. The dispatch claim is committed before the network send, so it records the authority to send — not that a send happened, and never that the tool acted.
- Not settlement or compliance. An internal credit ledger, not merchant settlement, dispute handling, or a certified compliance record.
- Not an IAM replacement. Wallet isolation is application-layer authorization and query scoping; PostgreSQL RLS and a public multi-tenant isolation guarantee are not implemented.
- Not a transparency log. Receipts verify offline, but nothing is anchored externally, and audit chains are tamper-evident rather than immutable against an administrator who controls both the database and its chain metadata.
- One adapter, one upstream. MCP is the only live governed adapter, and upstream execution is limited to one operator-configured Streamable HTTP server and one exact tool.
- No delegation chains. Permits are reusable budget envelopes; parent delegation containment is not implemented.
- No live single-action issuance. The code and schema 042 are merged, but issuance stays frozen until a binding and rollout are separately reviewed.
- No public SLA, no KMS integration, and no approval for PHI, PCI, or regulated production records.
The full list is SECURITY_LIMITATIONS.md; read it with TRUST_MODEL.md and docs/threat-model.md before a production evaluation. Attacking a documented limit? Start with docs/security-review-kit.md, which is the rules of engagement for an external reviewer: which target to attack, how to mint your own credentials instead of asking for production secrets, and what turns an observation into a finding.
Autonomous clients fetch, in order:
GET /.well-known/agent.json— canonical bootstrap and product boundaryGET /llms.txt— agent-oriented prose and vocabularyGET /mcp/tools.json— registered tools, permit requirements, exact pricingGET /openapi.json— the canonical API contract
Before assuming real side effects, read GET /health/dependencies — HTTP 200
alone does not mean every dependency is ready. Health never replaces
authentication or permit checks.
Protected routes use X-API-Key. On a production-like deployment there is
no self-serve key mint: an operator provisions a wallet-scoped key and
transfers it through a secure channel
(docs/partner-api-key-bootstrap.md). The
self-provisioning the quickstart above uses is local-only — a production-like
deployment refuses to boot with it enabled
(docs/static-dev-api-keys.md).
curl -sS -X POST "$API_URL/mcp/messages" \
-H "X-API-Key: $AGENT_KEY" \
-H "Content-Type: application/json" \
-d "{
\"jsonrpc\": \"2.0\",
\"id\": \"request-1\",
\"method\": \"tools/call\",
\"params\": {
\"name\": \"$TOOL_ID\",
\"arguments\": {\"input\": \"hello\"},
\"mcpContext\": {
\"wallet_id\": \"$WALLET_ID\",
\"permit_id\": \"$PERMIT_ID\",
\"idempotency_key\": \"invoke-1\"
}
}
}"POST /mcp/messages and the REST invoke route POST /mcp/tools/{id}/invoke
are what the supported pilot runs on — both marked deprecated in the OpenAPI
contract, both fully supported, and /mcp/messages is the only JSON-RPC
surface in a default local run. POST /mcp is the standards-compliant
Streamable HTTP endpoint on the official MCP SDK. It ships off
(ENABLE_STANDARD_MCP_ENDPOINT, default false) and the production runbook
(docs/deploy-railway.md) requires it to stay off,
because it mints a short-lived, single-tool permit on the caller's behalf and
generates an idempotency key when the client sends none. It becomes the
preferred path for new integrations once an operator enables it. Both run the
same governed permit → meter → receipt path.
An agent holding no authority can ask a human for it with
POST /v1/permit-requests. Full HTTP sequence:
docs/golden-path.md.
export MCP_UPSTREAM_ENABLED=true
export MCP_UPSTREAM_URL=https://mcp.partner.example/mcp
export MCP_UPSTREAM_TOOL_NAME=partner.write
export MCP_UPSTREAM_PUBLIC_TOOL_ID=partner.notes.write
export MCP_UPSTREAM_BEARER_TOKEN=... # secret manager only
export MCP_UPSTREAM_CREDITS_PER_CALL=7.5On startup the gateway discovers that exact tool and refuses readiness if configuration, connectivity, or discovery validation fails. Production requires a public HTTPS URL. The live checklist and failure semantics are in docs/partner-first-tool-runbook.md.
The typed AgentMiddlewareClient covers discovery, permits, governed
invocation, receipt verification, and evidence retrieval, and surfaces
idempotency conflicts and delivery uncertainty as explicit errors. CI builds
wheels and sdists on Python 3.10–3.12 and attaches them to python-sdk-v*
releases. It is not published to PyPI, and there is no TypeScript package —
install from this repository:
python -m pip install -e './b2a_sdk[dev]'Offline verification is deliberately dependency-minimal: b2a-verify-receipt
checks a signed receipt against a published key set with only cryptography
installed. See b2a_sdk/README.md.
The supported path is the repository Dockerfile on Railway, vendor-managed and
single-tenant: one project, API service, PostgreSQL database, Redis instance,
public origin, signing key, and bootstrap-admin set per customer. Deployments
must not share runtime services, databases, signing material, or operator
credentials. Production requires TRUST_MODE_ENABLED=true,
ENABLE_PROOF_SURFACES=false, and durable Postgres state; the application
refuses unsafe combinations at boot. Schema changes go through
alembic upgrade head.
Full SOP: docs/deploy-railway.md.
| Path | Role |
|---|---|
app/trust/ |
Protocol-neutral trust facade and governed adapter boundary |
app/routers/mcp.py |
Governed MCP orchestration |
app/services/ |
Permits, receipts, keys, billing, audit, idempotency, upstream MCP |
app/db/, migrations/ |
Schema and Alembic history |
tests/, scripts/ |
Product and adversarial tests; proofs and release gates |
b2a_sdk/ |
Python trust SDK (historical package name; the product is Agent Middleware) |
wrappers/, framework_integrations/ |
LangChain/CrewAI/AutoGen/OpenAI examples; source-only, unpublished |
awi_sdk/, site/ |
Frozen proof surface; static discovery site |
Broader agent features in this repository are frozen proof surfaces, not
product. Production-like deployments must set ENABLE_PROOF_SURFACES=false;
startup refuses a production configuration that enables them. Inventory and
unfreeze rules: docs/PROOF_SURFACES.md.
docs/README.md is the documentation index. The paths that matter most:
| You want to | Read |
|---|---|
| Decide whether this boundary fits | WEDGE.md |
| Run the loop yourself | docs/quickstart.md |
| Govern one real internal tool | docs/partner-first-tool-runbook.md |
| Understand retry, crash, and ambiguity outcomes | docs/failure-semantics.md |
| Measure the lost-response fault against a native-idempotency baseline | docs/failure-lab.md |
| Attack it | docs/security-review-kit.md, docs/invariant-attack-report.md |
| Check what is proven vs. claimed | docs/PROOF_MATRIX.md |
| See the hardening record | docs/tech-debt-remediation-plan.md (complete; a historical record, not a backlog) |
| Contribute | CONTRIBUTING.md |
Product site: https://www.agentmiddleware.dev · API: https://api.thisisatest.tech
The core is source-available under the
Business Source License 1.1: read it, run it, self-host it, modify
it, and redistribute it, but do not offer it to third parties as a competing
hosted service before its change date, when each version converts to MIT. The
SDKs, framework integrations, wrappers, and examples are
MIT. LICENSING.md has the directory map
and the commercial-license contact.