> ## Documentation Index
> Fetch the complete documentation index at: https://docs.qwedai.tech/llms.txt
> Use this file to discover all available pages before exploring further.

# QWED A2A deployment for zero-trust agent gateways

> Deploy the QWED A2A zero-trust FastAPI gateway with persistent ES256 signing keys, JWKS discovery, monitoring, and CI/CD integration.

## Deployment architecture

```mermaid theme={null}
flowchart TB
    subgraph Agents["Agent Ecosystem"]
        A1["Agent A"]
        A2["Agent B"]
        A3["Agent C"]
    end

    subgraph Gateway["QWED A2A Gateway"]
        LB["Load Balancer"]
        subgraph Instances["FastAPI Instances"]
            I1["Instance 1"]
            I2["Instance 2"]
        end
    end

    subgraph Observability["Observability"]
        S["Sentry"]
        M["Metrics"]
    end

    A1 --> LB
    A2 --> LB
    A3 --> LB
    LB --> I1
    LB --> I2
    I1 -.-> S
    I2 -.-> S
    I1 -.-> M
    I2 -.-> M
```

***

## Prerequisites

Before starting the gateway, generate a persistent ECDSA P-256 signing key and expose it as `QWED_A2A_SIGNING_KEY_PEM`. Attestation JWTs are signed with this key; without it the gateway fails closed (`HTTP 503`) on every request.

```bash theme={null}
openssl ecparam -name prime256v1 -genkey -noout \
  | openssl pkcs8 -topk8 -nocrypt \
  > qwed_a2a_signing_key.pem

export QWED_A2A_SIGNING_KEY_PEM="$(cat qwed_a2a_signing_key.pem)"
export QWED_A2A_DEPLOYMENT_ID="prod-us-east-1"
export QWED_A2A_TRUSTED_AGENTS="procurement-agent,treasury-agent"
export QWED_A2A_API_KEYS='{"key-abc123": "procurement-agent", "key-def456": "treasury-agent"}'
```

<Warning>
  Every replica in the same logical deployment must load the **same** PEM. Different keys across replicas would produce different `kid`s, and attestations issued by one instance would fail verification on another. Store the PEM in your secrets manager and inject it at runtime.
</Warning>

***

## FastAPI application

Create your production entrypoint:

```python theme={null}
# main.py
from fastapi import FastAPI
from qwed_a2a.protocol.endpoints import router, wellknown_router

app = FastAPI(
    title="QWED A2A Gateway",
    description="Zero-trust verification interceptor for A2A communication",
    version="0.1.0",
)

app.include_router(router)
app.include_router(wellknown_router)  # Exposes /.well-known/jwks.json

@app.on_event("startup")
async def startup():
    """Initialize interceptor on startup."""
    from qwed_a2a.protocol.endpoints import configure_interceptor
    from qwed_a2a.protocol.schema import InterceptorConfig

    config = InterceptorConfig(
        enable_financial_verification=True,
        enable_code_verification=True,
        enable_logic_verification=True,
    )
    configure_interceptor(config)

@app.on_event("shutdown")
async def shutdown():
    """Release the engine thread pool."""
    from qwed_a2a.protocol.endpoints import get_interceptor

    get_interceptor().close()
```

The shutdown hook calls `close()` to release the interceptor's engine thread pool. See [Engine execution budget](/a2a/interceptor#engine-execution-budget).

### Request size limits

The router enforces `max_payload_size_bytes` itself, so no middleware is needed:

* `POST /a2a/intercept` returns `413` for bodies larger than `max_payload_size_bytes` plus a 64 KiB envelope allowance, before buffering or parsing the body.
* An invalid `Content-Length` header returns `400`.

Keep a body limit at your reverse proxy too, as an outer layer of defense. For nginx, set `client_max_body_size` slightly above `max_payload_size_bytes` plus 64 KiB:

```nginx theme={null}
# 1 MiB default payload + 64 KiB envelope allowance, rounded up
client_max_body_size 2m;
```

See [Request size limits](/a2a/interceptor#request-size-limits) for library-level enforcement.

***

## Docker

```dockerfile theme={null}
# Dockerfile
FROM python:3.12-slim

WORKDIR /app

COPY pyproject.toml .
COPY src/ src/

RUN pip install --no-cache-dir .
RUN pip install --no-cache-dir uvicorn

COPY main.py .

EXPOSE 8000

CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
```

Build and run:

<CodeGroup>
  ```bash Docker theme={null}
  docker build -t qwed-a2a-gateway .
  docker run -p 8000:8000 qwed-a2a-gateway
  ```

  ```bash Docker Compose theme={null}
  # docker-compose.yml
  services:
    qwed-a2a:
      build: .
      ports:
        - "8000:8000"
      environment:
        - SENTRY_DSN=${SENTRY_DSN}
        - QWED_LOG_LEVEL=INFO
      restart: unless-stopped
      healthcheck:
        test: ["CMD", "curl", "-f", "http://localhost:8000/.well-known/jwks.json"]
        interval: 30s
        timeout: 5s
        retries: 3
  ```
</CodeGroup>

***

## Available endpoints

| Endpoint | Method | Auth | Description |
| - | - | - | - |
| `/a2a/intercept` | POST | `X-API-Key` header (required) | Primary verification gateway — accepts `AgentMessage`, returns `VerificationVerdict` |
| `/a2a/health` | GET | None | Service health check with version |
| `/a2a/metrics` | GET | None | Aggregated intercept metrics |
| `/.well-known/jwks.json` | GET | None | Public signing key in JWKS format for external attestation verification |

### Gateway authentication

`POST /a2a/intercept` requires a per-agent API key in the `X-API-Key` header. Keys are configured through `QWED_A2A_API_KEYS`, a JSON object mapping each API key to the agent ID it authenticates:

```bash theme={null}
export QWED_A2A_API_KEYS='{"key-abc123": "procurement-agent", "key-def456": "treasury-agent"}'
```

```bash theme={null}
curl -X POST http://localhost:8000/a2a/intercept \
  -H "Content-Type: application/json" \
  -H "X-API-Key: key-abc123" \
  -d '{
    "sender_agent_id": "procurement-agent",
    "receiver_agent_id": "treasury-agent",
    "payload_type": "financial_transaction",
    "payload": {"data": {"claimed_total": 100.00, "line_items": [{"amount": 100.00, "quantity": 1}]}}
  }'
```

The authentication is fail-closed:

* A missing or unknown `X-API-Key` returns `401 Invalid or missing API key.`
* If `QWED_A2A_API_KEYS` is unset, malformed, or contains no usable entries, every request returns `401 API authentication is not configured for this service.` A deployment without configured keys denies all traffic.
* Agent IDs in the map are normalized to the `AgentMessage` contract (trimmed, max 256 characters, no control characters). Entries that violate it are dropped and their keys denied.

The key's agent ID is the identity the gateway trusts. Trust-boundary evaluation, rate limiting, telemetry, and the attestation JWT all bind to the authenticated agent. If the body's `sender_agent_id` names a different agent, the gateway logs a warning and overrides it with the authenticated identity.

**Key rotation.** The gateway reads `QWED_A2A_API_KEYS` fresh on every request, so rotating keys takes effect without a restart. Update the environment variable (for example through your secrets manager) and old keys stop matching immediately.

### Health check response

```json theme={null}
{
  "status": "healthy",
  "service": "qwed-a2a",
  "version": "0.1.0"
}
```

### Metrics response

```json theme={null}
{
  "total_intercepts": 15420,
  "total_verified": 4950,
  "total_forwarded": 4950,
  "total_blocked": 180,
  "total_unverifiable": 5420,
  "total_heuristic_pass": 4830,
  "total_errors": 40,
  "average_latency_ms": 12.5,
  "block_rate": 0.0117,
  "by_engine": {
    "finance_guard": 5000,
    "code_guard": 3180,
    "logic_guard": 1780,
    "passthrough": 5420,
    "error_handler": 40
  },
  "by_sender": {
    "procurement-agent": 9800,
    "build-agent": 5620
  }
}
```

`total_forwarded` is a deprecated alias of `total_verified` (same count), kept so existing dashboards keep working. Migrate to `total_verified`; the alias will be removed in a future minor release.

### JWKS endpoint

`GET /.well-known/jwks.json` returns the current signing public key so downstream services can verify attestation JWTs without an out-of-band key exchange.

```json theme={null}
{
  "keys": [
    {
      "kty": "EC",
      "crv": "P-256",
      "x": "…base64url…",
      "y": "…base64url…",
      "kid": "did:qwed:a2a:local#key-<sha256-fingerprint>",
      "use": "sig",
      "alg": "ES256"
    }
  ]
}
```

The `kid` is derived from a SHA-256 fingerprint of the public key, so it remains stable across restarts as long as `QWED_A2A_SIGNING_KEY_PEM` is unchanged. When the PEM is missing or invalid, the endpoint responds with `HTTP 503 Signing key unavailable` — use this as a readiness signal for orchestrators and load balancers.

See [Crypto attestations](/a2a/crypto-attestations) for the full JWT structure and verification flow.

***

## Environment variables

| Variable | Description | Default |
| - | - | - |
| `QWED_A2A_SIGNING_KEY_PEM` | Unencrypted PKCS#8 EC P-256 private key used to sign attestation JWTs. **Required** — the service fails closed if unset. | *(none)* |
| `QWED_A2A_DEPLOYMENT_ID` | Stable identifier shared by all replicas in the same logical deployment. **Required** — import fails without it. | *(none)* |
| `QWED_A2A_TRUSTED_AGENTS` | Trust allowlist. Comma-separated agent IDs (unrestricted) or a JSON array of scoped [trust entries](/a2a/trust-boundary#scoped-trust-grants) with `allowed_receivers`, `allowed_payload_types`, and `valid_until`. | *(none — deny all)* |
| `QWED_A2A_API_KEYS` | JSON object mapping per-agent API keys to agent IDs, e.g. `{"key-abc123": "procurement-agent"}`. Read on every request, so rotation needs no restart. **Required for `/a2a/intercept`** — with no usable keys the endpoint returns `401` for every request. | *(none — deny all)* |
| `QWED_A2A_TRUSTED_ISSUERS` | JSON object mapping peer issuer IDs to `{"deployment_id": ..., "jwks": ...}` for [cross-deployment attestation verification](/a2a/crypto-attestations#cross-deployment-verification-trusted-issuers). Re-read on every verification, so rotation needs no restart. | *(none — only self-issued tokens verify)* |
| `SENTRY_DSN` | Sentry error tracking DSN | *(disabled)* |
| `QWED_LOG_LEVEL` | Logging level (`DEBUG`, `INFO`, `WARNING`, `ERROR`) | `INFO` |

***

## Monitoring

### Sentry integration

QWED A2A includes built-in Sentry integration for error tracking:

```python theme={null}
import sentry_sdk

sentry_sdk.init(
    dsn="https://your-dsn@sentry.io/project",
    traces_sample_rate=0.1,
    environment="production",
)
```

### Structured logging

All intercepts are logged with structured fields:

```text theme={null}
INFO  A2A Intercept [a2a_trace_001] VERIFIED -> engine=finance_guard (12.3ms)
INFO  A2A Intercept [a2a_trace_002] BLOCKED -> engine=code_guard (3.1ms)
WARN  Trust boundary violation: Sender 'rogue-agent' is globally blocked
```

***

## CI/CD integration

### GitHub Actions

```yaml theme={null}
# .github/workflows/a2a-tests.yml
name: A2A Verification Tests

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - run: pip install -e ".[dev]"
      - run: pytest tests/ -v --tb=short
```

### Mergify auto-merge

```yaml theme={null}
# .mergify.yml
pull_request_rules:
  - name: Auto-merge when CI passes
    conditions:
      - check-success=test
      - check-success=CodeQL
    actions:
      merge:
        method: squash
```

***

## Integration with QWED ecosystem

<CardGroup cols={2}>
  <Card title="QWED Core" icon="shield-check" href="/intro">
    A2A uses the same verification principles as the core QWED engine — deterministic, symbolic, and provable.
  </Card>

  <Card title="QWED MCP" icon="plug" href="/mcp/overview">
    MCP provides tool-level verification. A2A provides agent-to-agent communication verification. They complement each other.
  </Card>

  <Card title="QWED Finance" icon="building-columns" href="/finance/overview">
    The A2A finance guard uses the same Decimal arithmetic patterns as QWED Finance, adapted for inter-agent payloads.
  </Card>

  <Card title="Agent Specification" icon="scroll" href="/specs/agent">
    The QWED-Agent spec defines trust levels and budget enforcement. A2A implements the verification gateway described in the spec.
  </Card>
</CardGroup>


## Related topics

- [QWED A2A zero-trust protocol for agent-to-agent verification](/a2a/overview.md)
- [Zero-trust boundary for A2A agent communication](/a2a/trust-boundary.md)
- [QWED A2A architecture for agent verification](/a2a/architecture.md)
- [QWED A2A quick start for agent-to-agent verification](/a2a/quickstart.md)
- [Changelog](/changelog.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.