Skip to Content
EngineeringDecisions (ADRs)ADR 0013 Console protocol: streaming turns, a stateless tool server and per-role run tokens

ADR 0013: Console protocol: streaming turns, a stateless tool server and per-role run tokens

Status
Accepted
Date
Deciders
Founder

Context

Phase 1 ran agents as single request/response jobs polled every two seconds, with the container reading only bootstrapped material and no way for an agent to call platform data. Phase 2 needs a chat console with streamed answers, approval gates that pause a run, and agents that read and propose through the platform under the user’s permissions. The managed agent runtime’s network mode is PUBLIC (verified in infra/terraform/modules/agentcore-runtime/main.tf), so containment cannot rely on the network; a managed MCP gateway with policy exists but its general availability is not stated.

Decision

  • A second invocation mode: start with phase = console_turn returns a streamed envelope (one SSE event per runtime event; the terminal event carries job state). Legacy phases keep the poll path; the worker falls back to status polling if a stream ends early.
  • agent_events stays the durable log; the API tails it with LISTEN/NOTIFY and serves GET P/threads/{thread_id}/stream as server-sent events with id = seq and a 15 s keepalive.
  • One platform tool server inside the API at POST /api/v1/agent-tools/mcp, MCP Streamable HTTP run stateless (no session id), advertising revision 2025-06-18. It is the deterministic enforcement point: role and plan allowlists, live permission re-validation, approved-hash checks and budgets.
  • Six run tokens per run (console, evidence, plans, review, progress, reader): RS256 JWTs minted by the worker with a worker-only private key (trovensa/<env>/worker-run-token-key); the API verifies with app-run-token-jwks. The container holds no vendor credentials and its only configured endpoint is the tool server.
  • The mcp Python dependency is pinned >=1.30,<2 in api and agent (verified 2026-09-14: pip install mcp now installs 2.x with a renamed server API; the stateless FastMCP options belong to the 1.x line); the client forces the MCP-Protocol-Version header to the contract revision and never keys state on a session id.
  • Vendor MCP servers are reference-only until their protocol revision and audit attribution are verified; integrations go through versioned REST APIs.

Consequences

  • The runtime image, the worker and the API change together (contract section 10.7); a fake MCP server keeps container and API tests offline.
  • The maximum duration of one streamed runtime response and session behaviour after max_lifetime are measured and recorded in CLAIMS-REGISTER.md before R1b ships.
  • Retrieved text reaches only the reader role; write proposals are hashed at proposal time and verified at approval.

Alternatives considered

  • Managed gateway with policy as the enforcement point: not stated as generally available; kept as R2 defence in depth by publishing the same OpenAPI to it.
  • WebSocket end to end: the ingress and the API pods would need new configuration and sticky sessions; SSE over the existing ALB with reconnect from Last-Event-ID is sufficient for interactive turns.
  • Direct API-to-runtime invocation for interactive turns: two invocation paths and a token issuer in the API pod; rejected for the pilot.

Follow-ups

  • VPC network mode with an egress allow-list when available (R2).
  • Publish the tool server’s OpenAPI to the managed gateway with mirrored policy rules (R2).
Last updated on