Mira mirrors Vega's golden skeleton on purpose (RFD-AGT-007 §1.1): same broker-owned control plane, same agent_event_stream.v1 wire, same fail-closed entrypoint, same dedicated Logfire lane. The one true architecture (ADR-110) does not get re-invented per agent — a new agent is Vega's shell with a new soul bolted on. So this PR is almost entirely mechanism copied + retargeted (MIRA_* env, pramaan-agents-mira service, mira logger/key), and a small amount of genuinely new soul: Mira's identity, persona, and her two typed input contracts.
input shapes and the command output shape are frozen. Landing the contracts first — with a runtime that fail-closes honestly on every route — unblocks that parallel work while Stories B (converse) and C (command) fill in the engines behind the same frozen doors.| File | Kind | What it is |
|---|---|---|
| provider.py | copied | Byte-identical to Vega: ZDR_SETTINGS (zdr + data_collection:deny + usage:include) pinned in ONE place, build_model. OpenRouter is the only egress (ADR-120). |
| observability.py | retargeted | Dedicated Logfire lane (Bug-1294 two-lane rule) + AmbientIdentitySpanProcessor. Retargeted: agent.key=mira, gen_ai.agent.name=Mira, service.name=pramaan-agents-mira. No baggage, no content. |
| secrets_bootstrap.py | retargeted | The one fenced boto3/Secrets Manager read (MIRA_REGION default). Fail-safe, only-if-unset. |
| config.py | retargeted | Role loader over llm.yaml, env override MIRA_<ROLE>_MODEL. Roles: converse / proposer[] / critic / synthesizer. Adds the conversation route's own tight deadline (MIRA_CONVERSE_ATTEMPT_TIMEOUT_S). |
| events.py | copied | The frozen agent_event_stream.v1 builders — no per-agent dialect. |
| runtime.py | scaffold | The Bug-1370 boundary watchdog (real), around a handle_request that validates the envelope + per-intent input, then emits a typed not_implemented tail (behaviour is B/C). |
| main.py | retargeted | AgentCore @app.entrypoint: secrets → Logfire → fail closed on missing input AND intent → stream JSONL. |
| identity.py · SOUL.md · assets/ | soul | Mira's persona: MIRA = AgentIdentity(key="mira", display_name="Mira", role_line="MATTER EXPERT", …), the voice (composed into the synthesizer only), the vendored avatar. |
| models.py · frame_contract.py | soul | The two typed inputs + the conversation turn; the vendored frozen FrameOutput reused as command output. |
intent# broker invoke envelope (ADR-110 §3): {firm_id, matter_id, agent_run_id, trace…, intent, input} main.py # fail closed if 'input' OR 'intent' missing — no default route, no demo fallback └► runtime.stream_invoke # Bug-1370 watchdog (real shell B/C build on) └► handle_request # parse BEFORE run.started ├─ intent="converse" ─► MiraConverseInput → model_id_for("converse") # the fast model └─ intent="command" ─► MiraCommandInput → model_id_for("synthesizer") # Vega's panel seat # Story-A response: run.started → run.failed{failure_kind:"not_implemented"} (engines land in B/C)
intent, not just input: Vega is ambient — every invoke is a build, so it needs only input. Mira has two routes with opposite cost/latency shapes; a route the caller did not choose must never be guessed. A missing or unknown intent raises before any event (mirrors Vega's input contract), so a malformed invoke errors loudly instead of silently running the wrong — possibly expensive — panel. Story-D wires the dispatch to the real engines; the fail-closed frame is already here.Mira's command output is Vega's FrameOutput, unchanged (RFD §4) — she invents no frame shape. But each agent is an independently-deployable CodeZip with no cross-agent runtime import (ADR-110 §6), and the members' top-level module names collide (both have a models). So the contract is vendored at source into frame_contract.py, exactly as mechanism is copied today — the shared-agent-code spike folds these into agents/_shared/ later.
agents/framer/models.py. Its committed vocabulary artifact schemas/frame_output.v1.fields.json — the same one pramaan-functions codegens against (ADR-153) — is read by tests/test_frame_contract_custody.py, which fails CI if Mira's vendored copy diverges by even one field. A duplicate with a guard is not drift; a duplicate without one is.| Type | Route | Shape |
|---|---|---|
| MiraConverseInput | talk | matter_id, current_frame: FrameOutput, counsel_message, history: [MiraMessage], matter_meta?. READ-ONLY; the frame IS the compressed matter context (small, fast prompt). |
| MiraConverseTurn | talk | markdown + optional suggested_command{scope, text} — the "Do it" escalation chip (RFD §2.3). |
| MiraCommandInput | do | command, scope (a FrameOutput field name or "brief"), current_frame, base_frame_version? (the stale-base race key, RFD §6), optional documents. |
| FRAME_SCOPES | do | The closed command vocabulary — frozenset(FrameOutput.model_fields) | {"brief"}, derived from the frozen contract so it can never drift from it. Enforced in code in Story-C. |
No conversation engine (Story-B) and no command engine / panel wiring (Story-C) — handle_request emits a typed not_implemented until they land. No turn.delta streaming builder yet (arrives with the converse route). No manual (T3) spans — conversation is a single model call, not a registry-listed domain op (ADR-124 §10). No model-catalog.yaml — Mira mirrors Vega's per-role llm.yaml by design (RFD §3), so it shares Vega's catalog exemption until it ships to AgentCore. Read-only truth boundary preserved: Mira writes no canonical frame — she proposes, counsel promotes.
# offline, no key cd agents/mira && uv run --package mira pytest # 30 passed uv run pytest tests # 151 passed (governance: catalog exemption, egress, agent-name, custody) uv run ruff check agents/ tests/ # clean uv lock # workspace resolves — mira added
Tests pin: the persona fields + MIRA.avatar_path resolving to the vendored PNG · identity single-sourced with observability · models_for/model_id_for raising on an unset role (no hardcoded default) · ZDR_SETTINGS present · the two input contracts + the scope vocabulary · the fail-closed envelope (missing/unknown intent, missing agent_run_id, malformed input — all raise before run.started) · the boundary watchdog shipping a typed terminal on a wedged brain · the vendored-frame custody gate against framer's committed artifact.