The app can launch Mira over HTTP and reuse the durable clerk rail
The new POST /app/agents/{agent_key} route admits Wave-1 Mira launches, resolves the default matter chat session, optionally attaches an existing socket, applies the same perimeter and consent gates as WebSocket launch, and enqueues onto the existing clerk-run queue only when the run is actually admitted.
What the new route does
POST /app/agents/mira | +-- validate HTTP request DTO +-- optionally attach socketId to an existing ws connection +-- resolve default Mira session for (firm, matter, user) +-- build internal agent_key = mira_matter_chat +-- reserve run id and apply perimeter + consent gates +-- on admit: enqueue to clerk rail with ADR-124 trace context +-- on block: persist terminal blocked truth, no queue write
The public key is still mira; the adapter translates it to the internal mira_matter_chat rail contract before enqueue so the worker path and run persistence stay unchanged.
Shared launch core and transport boundary
| Concern | Owner after Story-2840 |
|---|---|
| HTTP parsing and response DTO | router_agents.py |
| Queue selection and enqueue helper | agent_launch_core.py |
| Run-id reservation and blocked-run truth | agent_launch_core.py |
| Mira default-session resolution | agent_launch_core.py |
| Socket attach for HTTP launches | agent_launch_core.py |
| Existing WebSocket seams | router_ws.py delegates to the shared core without breaking test patch points |
This keeps the transport adapters thin while preserving one launch policy for consent gating, perimeter gating, blocked-run persistence, and clerk-rail routing.
Accepted and rejected launch outcomes
| Input condition | Result |
|---|---|
agent_key = mira, valid matter launch, admitted gates | 202 accepted; run reserved; queued on mira_matter_chat |
| Known socket id owned by the caller | Socket is attached and the run can stream onto it |
| Unknown or foreign socket id | 404 via pramaan.agent_launch.socket_not_found |
| Consent missing or perimeter rejects | 202 with blocked truth in agents.agent_run; no queue write |
Any non-mira key on this route | 404 via pramaan.agent_launch.agent_not_found |
Observability note
ADR-124 trace propagation now covers this HTTP launch path too. The shared queue client injects W3C trace context into SQS message attributes, and the HTTP route wraps the enqueue in the same producer span shape used by WebSocket launch so traces render the app-adapter to clerk-worker edge consistently.
Acceptance evidence
| Focused unit tests | tests/unit/test_router_agents.py proves accepted enqueue and blocked truth responses for the new HTTP adapter. |
|---|---|
| WebSocket regression coverage | tests/unit/test_router_ws.py stays green after the shared-core extraction, proving the existing launch rail behavior is preserved. |
| DB-backed route evidence | tests/integration/test_app_agents_launch.py covers socket attach, blocked truth, and unknown-socket rejection when DB env is present. |
| OpenAPI contract | scripts/generate_openapi.py updates the committed route and DTO snapshot. |
Scope
This Story adds the Wave-1 HTTP launch entrypoint and the shared launch core only. It does not add a new queue, new worker, new cloud capability, or any compatibility bridge to a legacy launch rail. The existing clerk queue remains the only durable egress path.
Governing issue: pramaan-spec#2840.