Design: HTTPGateway¶
Status: Implemented — v0.4 Author: Jeryn Mathew Varghese Last updated: 2026-04
Motivation¶
Civitas agents communicate exclusively through the message bus. External clients — mobile apps, browsers, microservices, CLI tools — speak HTTP, HTTP/2, HTTP/3, or gRPC. Without a bridge, every integration requires custom glue code outside the supervision tree.
HTTPGateway is a supervised AgentProcess (or GenServer) that binds to a port, translates inbound protocol traffic into Civitas messages, routes them to the appropriate agent or GenServer, and returns the reply as a protocol response. The agent handling the request has no knowledge of HTTP — it only sees call() / cast() messages, exactly as if another agent sent them.
What this is not: a full application framework. HTTPGateway is a thin, protocol-aware edge process in the supervision tree. Business logic lives in agents and GenServers behind it.
Protocol support matrix¶
| Protocol | Transport | Multiplexing | TLS | Civitas mapping |
|---|---|---|---|---|
| HTTP/1.1 | TCP | No (keep-alive only) | Optional | call / cast |
| HTTP/2 | TCP | Yes (streams) | Required (ALPN) | call / cast / server-push |
| HTTP/3 | QUIC (UDP) | Yes (streams, 0-RTT) | Built-in | call / cast |
| gRPC | HTTP/2 | Yes | Optional (insecure mode) | Unary → call, streaming → cast chain |
All four share the same routing and message-translation layer. Only the network I/O layer differs.
Architecture¶
HTTPGateway is started as a child of any Supervisor. It owns the three server coroutines internally and manages their lifecycle within its on_start / on_stop hooks.
Request-to-message mapping¶
URL routing convention¶
POST /agents/{name} → call(name, body) # synchronous, returns reply
POST /agents/{name}/cast → cast(name, body) # fire-and-forget, returns 202
GET /agents/{name}/state → call(name, {__op__: state})
POST /broadcast → broadcast(body)
Custom route tables are supported via topology YAML (see below).
HTTP → Message translation¶
| HTTP concept | Civitas Message field |
|---|---|
URL path segment {name} |
recipient |
| Request body (JSON) | payload |
X-Civitas-Type header |
type (defaults to "http.request") |
X-Correlation-ID header |
correlation_id |
traceparent header (W3C) |
trace_id, parent_span_id |
| HTTP method | determines call vs cast (POST=call, PUT=cast) |
Message → HTTP response translation¶
| Message reply field | HTTP concept |
|---|---|
payload (dict) |
JSON response body |
payload.error present |
HTTP 400 |
| No handler registered | HTTP 404 |
| Timeout | HTTP 504 |
| Unhandled exception | HTTP 500 |
HTTP/1.1 and HTTP/2¶
Server: uvicorn with the [standard] extra — ASGI server built on two C-backed components:
- uvloop — drop-in replacement for the asyncio event loop, wrapping libuv (the C event loop powering Node.js). Delivers 2–4× throughput over the default asyncio loop.
- httptools — HTTP/1.1 parser built on llhttp (C). Used by uvicorn for HTTP/1.1 request parsing.
HTTP/2 is negotiated via ALPN on TLS connections — uvicorn handles this transparently when TLS is configured.
Windows compatibility
uvloop uses libuv APIs that are not available on Windows. The gateway installs uvloop on Linux/macOS and silently falls back to the default asyncio event loop on Windows:
Interface: Standard ASGI app. HTTPGateway implements __call__(scope, receive, send) internally, run by uvicorn.
from civitas.gateway import HTTPGateway
gateway = HTTPGateway(
name="api",
host="0.0.0.0",
port=8080,
tls_cert="certs/server.crt", # enables HTTP/2 via ALPN
tls_key="certs/server.key",
request_timeout=30.0,
)
HTTP/2 specifics:
- Stream multiplexing is handled by uvicorn — each stream maps to one Civitas call()
- Server push is not exposed in v1 (deferred — requires explicit push directives from agents)
- pip install civitas[http] installs uvicorn[standard] (pulls in uvloop + httptools automatically)
HTTP/3 / QUIC¶
Server: aioquic — pure-Python QUIC and HTTP/3 implementation.
HTTP/3 runs over QUIC — a multiplexed, encrypted transport over UDP. Key benefits over HTTP/2: - 0-RTT connection resumption — repeat clients connect with zero round-trip handshake - Head-of-line blocking eliminated — lost UDP packets don't stall other streams - Connection migration — clients can change IP (mobile handoff) without reconnecting
QUIC requires TLS — there is no plaintext QUIC. Certificate required.
gateway = HTTPGateway(
name="api",
host="0.0.0.0",
port=8080,
port_quic=8443, # separate UDP port for QUIC
tls_cert="certs/server.crt",
tls_key="certs/server.key",
enable_http3=True, # adds Alt-Svc header to HTTP/1.1 and HTTP/2 responses
)
The Alt-Svc: h3=":8443" header is automatically injected into HTTP/1.1 and HTTP/2 responses, advertising the QUIC endpoint to clients that support it.
Required extra: pip install civitas[http3] (installs aioquic>=1.0)
Why aioquic (and what's next)¶
aioquic is the established Python QUIC library and adequate for most deployments. The QUIC ecosystem is maturing fast — quiche (Cloudflare's QUIC implementation in Rust) exposes Python bindings via quiche-python. When those bindings stabilise for production use, civitas[http3] will transparently upgrade to the Rust backend for near-native QUIC performance. No API changes to HTTPGateway are needed — only the underlying driver swaps.
gRPC¶
gRPC uses HTTP/2 as transport and Protocol Buffers (protobuf) as the serialization format. Civitas exposes a generic reflection service so clients don't need pre-generated stubs for basic use.
Default: grpclib (pure Python async)¶
Server: grpclib — pure Python async gRPC, no C extension required. Integrates cleanly with asyncio and requires no native build tooling.
Install: pip install civitas[grpc] (installs grpclib>=0.4 + protobuf>=4)
Production: grpcio (C core)¶
For high-throughput deployments, grpcio (Google's official Python gRPC library) uses a C core for serialization, compression, and connection management — significantly faster under load.
Install: pip install civitas[grpc-fast] (installs grpcio>=1.62 + protobuf>=4)
When grpcio is present, HTTPGateway automatically uses it. When only grpclib is present, it falls back gracefully. Both expose the same HTTPGateway API.
# Same API regardless of grpclib vs grpcio backend
gateway = HTTPGateway(
name="api",
host="0.0.0.0",
port=8080,
grpc_port=50051,
)
Generic service (no proto required)¶
// Automatically served by HTTPGateway
service CivitasGateway {
rpc Call(Request) returns (Response); // → call()
rpc Cast(Request) returns (google.protobuf.Empty); // → cast()
rpc Stream(Request) returns (stream Response); // → call() + stream reply chunks
}
message Request {
string recipient = 1;
string type = 2;
bytes payload_json = 3; // JSON-encoded payload
string correlation_id = 4;
string trace_parent = 5;
}
message Response {
bytes payload_json = 1;
string error = 2;
}
RPC → Civitas mapping¶
| gRPC RPC type | Civitas operation | Notes |
|---|---|---|
| Unary | call() |
One request, one reply |
| Client streaming | Accumulate → call() |
All client frames collected, then one call |
| Server streaming | call() → stream chunks |
Agent returns {"chunks": [...]} |
| Bidirectional streaming | cast() per frame |
No correlation; fire-and-forget per message |
Custom proto stubs (optional)¶
For typed APIs, agents can expose a .proto definition. The gateway loads it at startup and maps each RPC method to a Civitas agent name:
# topology.yaml
gateway:
grpc:
proto_dir: protos/
services:
- proto: protos/assistant.proto
service: AssistantService
agent: assistant # routes all RPCs to this agent name
Topology YAML¶
supervision:
name: root
strategy: ONE_FOR_ONE
children:
- name: api
type: http_gateway
module: civitas.gateway
class: HTTPGateway
config:
host: "0.0.0.0"
port: 8080
port_quic: 8443
enable_http3: true
tls_cert: !ENV GATEWAY_TLS_CERT
tls_key: !ENV GATEWAY_TLS_KEY
request_timeout: 30.0
grpc:
enabled: true
proto_dir: protos/
routes:
- path: /v1/chat
agent: assistant
method: POST
mode: call
- path: /v1/notify
agent: notifier
method: POST
mode: cast
- name: assistant
type: agent
module: myapp.agents
class: AssistantAgent
- name: notifier
type: gen_server
module: myapp.services
class: NotifierService
Implementation plan¶
Phase 1 — HTTP/1.1 + HTTP/2 (v0.4)¶
civitas/gateway/__init__.py—HTTPGateway(AgentProcess)civitas/gateway/asgi.py— ASGI app, request translation, response mappingcivitas/gateway/router.py— route table (path → agent name, mode)- uvicorn runner inside
on_start()/on_stop()with uvloop install (Linux/macOS only) - TLS config from topology YAML / env vars
civitas[http]extra:uvicorn[standard]>=0.30(pulls in uvloop + httptools)
Phase 2 — HTTP/3 / QUIC (v0.4)¶
- Add
port_quicconfig option toHTTPGateway - Run aioquic QUIC server alongside uvicorn on the UDP port
- Auto-inject
Alt-Svcheader into HTTP/1.1 and HTTP/2 responses civitas[http3]extra:aioquic>=1.0- Document quiche-python upgrade path for when Rust bindings stabilise
Phase 3 — gRPC (v0.5)¶
civitas/gateway/grpc.py— genericCivitasGatewayservice; detect grpcio vs grpclib.protofile bundled with the package- Optional custom proto loading from
proto_dir - RPC → message type mapping (unary, client-streaming, server-streaming, bidirectional)
civitas[grpc]extra:grpclib>=0.4,protobuf>=4civitas[grpc-fast]extra:grpcio>=1.62,protobuf>=4
Phase 4 — Advanced (v0.5+)¶
- gRPC reflection service (clients introspect available services without .proto files)
- HTTP/2 server push
- WebSocket upgrade (maps to long-lived
cast()sessions) - Rate limiting via
RateLimiterGenServer (built-in integration) - Request authentication middleware (JWT, API key, mTLS client certs)
- Evaluate quiche-python (Rust QUIC) as drop-in replacement for aioquic
What HTTPGateway does NOT do¶
- No business logic — it translates and routes. All logic lives in agents/GenServers.
- No load balancing — route to one agent by name. Load balancing is a supervisor/registry concern.
- No request queuing — requests that exceed
request_timeoutreturn 504. Queuing is the agent's responsibility. - No schema validation — payloads are forwarded as-is. Validation belongs in
handle_call().
Dependencies summary¶
| Extra | Installs | Enables | Backend |
|---|---|---|---|
civitas[http] |
uvicorn[standard]>=0.30 |
HTTP/1.1 + HTTP/2 | C (uvloop + httptools) |
civitas[http3] |
aioquic>=1.0 |
HTTP/3 / QUIC | Pure Python (Rust upgrade path via quiche) |
civitas[grpc] |
grpclib>=0.4, protobuf>=4 |
gRPC | Pure Python async |
civitas[grpc-fast] |
grpcio>=1.62, protobuf>=4 |
gRPC (high throughput) | C core (Google) |
All can be installed together: pip install civitas[http,http3,grpc] or pip install civitas[http,http3,grpc-fast]
Open questions¶
| # | Question | Notes |
|---|---|---|
| Q1 | Should HTTPGateway be a GenServer subclass or AgentProcess? |
Lean AgentProcess — it uses self.call() / self.cast() on the bus, not handle_call itself |
| Q2 | How should streaming gRPC map to the bus? | cast() per frame is simplest; bidirectional may need a dedicated session agent |
| Q3 | Should the generic gRPC service use JSON or msgpack for payload? |
JSON — maximises client interoperability without requiring Civitas SDK |
| Q4 | TLS termination — in-process or via sidecar (Envoy, nginx)? | Both; in-process for simplicity, sidecar for production mTLS |
| Q5 | How do WebSocket upgrades map to the bus? | Long-lived session — each WebSocket frame → cast(); close event → call() teardown |
| Q6 | Should route tables support middleware chains (auth, rate-limit, logging)? | Yes, via pluggable middleware list in topology YAML — spec separately |
| Q7 | When should civitas[grpc-fast] become the default? |
When grpcio's asyncio support matures fully — currently requires careful loop management |
Acceptance criteria¶
-
HTTPGatewaystarts as a child of anySupervisor - HTTP/1.1 requests route correctly to named agents via
call()andcast() - HTTP/2 multiplexed streams each map to independent Civitas calls
- HTTP/3 QUIC endpoint advertised via
Alt-Svcheader - gRPC unary RPC maps to
call()and returns correct protobuf response -
request_timeoutreturns HTTP 504 / gRPC DEADLINE_EXCEEDED - TLS config loaded from
settings/ topology YAML / env vars -
HTTPGatewaystops cleanly onSupervisorshutdown — drains in-flight requests - Topology YAML
type: http_gatewaysupported bycivitas topology validate -
civitas topology showdisplays gateway nodes with protocol annotations - uvloop installed and active on Linux/macOS; asyncio fallback on Windows
- grpcio detected and preferred over grpclib when
civitas[grpc-fast]is installed - ≥ 20 unit tests (routing, translation, timeout, error mapping)
- ≥ 5 integration tests (real HTTP client → gateway → agent → response)
- Documented with examples for all four protocols