Plan — Mira Case Cockpit: surface dropped state, per-stage actions, live data
Written 2026-07-15. Self-contained: assumes zero prior context. Intended to be filed as a
GitHub issue on byteseek/Mira and picked up by an implementing agent.
Context (why)
The dashboard at dashboard/ (see dashboard/HANDOFF.md) is a read-only mirror of Mira's
research cases. It shows where a case sits (board stage + cockpit: route, readiness ladder,
expectation map, evidence bars, artifacts) but gives the operator nothing to do from there.
Mira itself is not a state store — it is a set of loops/skills (actions) in loops/ and
skills/. So the dashboard today drops the parts that make Mira Mira, and offers no way to act
on a card. Ben reviewed it and named three gaps to close:
- Surface dropped state — the cockpit throws away thesis-ledger, decision-log, refresh
triggers, expectation-map surprise columns, and evidence conflict detail.
- Per-stage action menus — each stage has specific legitimate next moves (real Mira loops).
The dashboard should offer them, turning the mirror into a launcher.
- Live data connector — every case is
connector: none. A real tools/mira_data Futu/IBKR
connector already exists; wire its output in.
Architecture decision: stay static for v1 (Python build step → data/cases.json → vanilla JS,
no running backend), matching Mira's stdlib-only convention (tools/mira_pipeline_sync.py,
tools/mira_data). Per-stage actions copy the exact Mira prompt/command to paste into an
agent; live prices are folded in at build time from existing private/futu|ibkr/ snapshots.
Leave a clean, documented seam so a small local backend can later execute actions and fetch live
prices on click. Do not build the backend in v1.
Hard invariant: the dashboard is read-only over cases/. build_data.py must never write into
case files. (This is why parsing helpers were copied out of mira_pipeline_sync.py rather than
imported — that module writes frontmatter at import time.)
Files touched
dashboard/build_data.py — new parsing (ledger, decision-log, refresh radar, conflict detail,
live snapshot fold). Stdlib only.
dashboard/app.js — new cockpit panels, expectation-map columns, per-stage action menu +
copy-to-clipboard, board overdue badge, live-data rendering.
dashboard/style.css — styles for new panels, action popover/modal, badges.
dashboard/index.html — minor (popover/modal container if needed).
dashboard/HANDOFF.md — document the live-snapshot fetch step and the backend seam.
Feature 1 — Surface dropped state
build_data.py (new pure helpers, wired into build_case())
parse_ledger(case_dir) → read thesis-ledger.md if present. Extract:
- the
- key: value metadata block (state, last_updated, selected_framework,
selected_overlays, stale_after) with the same line-anchored regex style as fm_get;
- the first paragraph under
## Current Thesis;
## Key Assumptions items, and any kill_criteria / reversal_condition lines.
Return a dict or None. (Sample shape confirmed in cases/aapl-2026-04/thesis-ledger.md.)
parse_decision_log(case_dir) → load_csv_rows("decision-log.csv"); keep
date, research_object, decision_type, basis, risk, required_followup. Return list or None.
(Header confirmed in cases/aapl-2026-04/decision-log.csv.)
- Refresh radar — add to each case:
refresh_status ∈ overdue | due_soon | ok | unknown and days_to_stale (int|null),
computed by extracting the first DATE_RE match from the stale_after frontmatter field and
comparing to datetime.date.today() (allowed — this is the build step, not the sandboxed
research workflow). due_soon = within 14 days.
must_refresh_if and kill_criteria from README frontmatter when present.
- Evidence conflict detail — in
build_evidence(), alongside conflict_count, add
conflicts: a short list of {claim, conflict_status, source_id} for rows whose
conflict_status ∉ {none, ""}. Cap at ~10 to keep JSON small.
- Expectation map already passes full CSV rows — no builder change; the extra columns
(consensus_proxy, evidence_status, next_check) are surfaced in the frontend.
app.js (new/updated cockpit panels)
- Thesis Ledger panel —
state badge (stale/active), current-thesis text, kill/reversal
criteria, key assumptions, framework + overlays. Empty-state when no ledger.
- Decision Log panel — compact timeline: date · decision_type · basis (truncated) · followup.
- Expectation Map — extend
renderExpectationMap() to show consensus_proxy and next_check
(and evidence_status as a chip). This is the "where's the surprise vs consensus" view.
- Refresh chip in the case header — red "overdue Nd" / amber "due in Nd" from
refresh_status.
- Evidence panel — make
conflict_count expandable to list evidence.conflicts.
- Board cards — add a small overdue dot/badge when
refresh_status === "overdue".
Feature 2 — Per-stage action menus (launcher)
Add a STAGE_ACTIONS config in app.js: pipeline_stage → [{ label, kind, template }].
kind ∈ prompt | shell | open (this typing is the backend seam — a future server executes by
kind; v1 copies template to clipboard via navigator.clipboard.writeText). template is a
function of the case (interpolates slug / ticker / label / loop path).
Stage → actions (sourced from AGENT_QUICKSTART.md routing table + loops/):
| Stage |
Actions (each yields a real Mira prompt/command) |
idea |
Route it → loops/analysis-routing.md · Promote to Queued |
queued |
Run first-pass loops/research-loop.md (paste task card for this case) |
intake |
Confirm route · pick framework/overlay · run the routed skill |
researching |
Continue loop · complete expectation-map.csv · run gates |
package_complete |
Review → refresh / extend / archive · loops/monitoring-loop.md · loops/thesis-update-loop.md · check actionability-bridge.md |
needs_refresh |
Refresh now → scripts/check_updates.sh then loops/monitoring-loop.md / loops/event-delta-loop.md (show the stale-trigger date) |
archived |
Reference only · reopen as new case |
Card-level actions (any stage): Open memo / evidence-log / expectation-map (copy path or
file:// link) · Copy next-step prompt (from next_action) · Copy case folder path
cases/<slug>/.
UI: clicking an action opens a small popover/modal showing the exact text with a Copy button;
no execution in v1.
Feature 3 — Live data connector (static snapshot-fold + seam)
build_data.py
load_live_snapshot(ticker) → look under private/futu/<TICKER>* and private/ibkr/<TICKER>*
for the newest emitted snapshot JSON. Implementation step: read tools/mira_data/emit.py and
tools/mira_data/adapters/ to match the exact emitted JSON shape (last price, as-of timestamp,
source id). Return {price, as_of, source, connector} or None.
build_live_data() — when a snapshot exists, set connector to futu_opend_local /
ibkr_gateway_local, fill real price / as_of / freshness (compare as-of to today), else keep
the honest connector: "none" default. Never fabricate a price.
app.js
renderLiveData() — when connector !== "none", show live price / as_of / source / freshness
with a live-vs-stale badge; keep the current honest empty state otherwise.
Operator step (document in HANDOFF.md)
PYTHONPATH=tools python -m mira_data fetch futu_market_price US.<TICKER> --out private/futu/<TICKER>
python3 dashboard/build_data.py # re-fold the snapshot into cases.json
private/ is gitignored — snapshots stay local. No account ids / positions enter tracked files
(per docs/futu-private-data.md, docs/ibkr-private-data.md).
Backend seam (v2, documented not built)
A thin dashboard/server.py (stdlib http.server) exposing /api/live/<slug> (shells
mira_data fetch) and /api/run (executes a stage action by kind). Frontend prefers these
endpoints when reachable, falls back to static JSON / clipboard otherwise. The kind typing on
stage actions and the connector field already make this a drop-in.
Verification
python3 dashboard/build_data.py → runs clean, 36+ cases, no writes into cases/
(git status cases/ clean after run). Spot-check aapl-2026-04 in data/cases.json: ledger
fields, decision-log rows, refresh_status, expectation-map extra columns, conflicts list.
- Serve:
python3 -m http.server 8420 --directory dashboard, open in browser. Confirm:
- Cockpit shows Thesis Ledger + Decision Log panels for
aapl-2026-04.
- Expectation map shows consensus_proxy + next_check.
needs_refresh cases past their stale date show an overdue badge on board + cockpit.
- Each card/stage shows an action menu; clicking an action copies the correct Mira prompt.
- Live panel: drop a sanitized test snapshot into
private/futu/AAPL, re-run build, confirm
the live price renders; then remove the test snapshot.
- Re-run
build_data.py; confirm data/cases.json still gitignored and cases/ untouched.
Out of scope (v1)
- The local backend server (seam only).
- Drag-to-change-stage / any write-back into case frontmatter (stage is owned by
mira_pipeline_sync.py, not the browser).
- The
mira_pipeline_sync.py regex fix (tracked separately in HANDOFF.md).
Filing
After approval, file this as a GitHub issue on byteseek/Mira via gh issue create (title:
"Case Cockpit: surface dropped state, per-stage actions, live data"), body = this plan.
byteseek/Mira is a public repo — confirm that's the right target before filing.
Plan — Mira Case Cockpit: surface dropped state, per-stage actions, live data
Written 2026-07-15. Self-contained: assumes zero prior context. Intended to be filed as a
GitHub issue on
byteseek/Miraand picked up by an implementing agent.Context (why)
The dashboard at
dashboard/(seedashboard/HANDOFF.md) is a read-only mirror of Mira'sresearch cases. It shows where a case sits (board stage + cockpit: route, readiness ladder,
expectation map, evidence bars, artifacts) but gives the operator nothing to do from there.
Mira itself is not a state store — it is a set of loops/skills (actions) in
loops/andskills/. So the dashboard today drops the parts that make Mira Mira, and offers no way to acton a card. Ben reviewed it and named three gaps to close:
triggers, expectation-map surprise columns, and evidence conflict detail.
The dashboard should offer them, turning the mirror into a launcher.
connector: none. A realtools/mira_dataFutu/IBKRconnector already exists; wire its output in.
Architecture decision: stay static for v1 (Python build step →
data/cases.json→ vanilla JS,no running backend), matching Mira's stdlib-only convention (
tools/mira_pipeline_sync.py,tools/mira_data). Per-stage actions copy the exact Mira prompt/command to paste into anagent; live prices are folded in at build time from existing
private/futu|ibkr/snapshots.Leave a clean, documented seam so a small local backend can later execute actions and fetch live
prices on click. Do not build the backend in v1.
Hard invariant: the dashboard is read-only over
cases/.build_data.pymust never write intocase files. (This is why parsing helpers were copied out of
mira_pipeline_sync.pyrather thanimported — that module writes frontmatter at import time.)
Files touched
dashboard/build_data.py— new parsing (ledger, decision-log, refresh radar, conflict detail,live snapshot fold). Stdlib only.
dashboard/app.js— new cockpit panels, expectation-map columns, per-stage action menu +copy-to-clipboard, board overdue badge, live-data rendering.
dashboard/style.css— styles for new panels, action popover/modal, badges.dashboard/index.html— minor (popover/modal container if needed).dashboard/HANDOFF.md— document the live-snapshot fetch step and the backend seam.Feature 1 — Surface dropped state
build_data.py(new pure helpers, wired intobuild_case())parse_ledger(case_dir)→ readthesis-ledger.mdif present. Extract:- key: valuemetadata block (state, last_updated, selected_framework,selected_overlays, stale_after) with the same line-anchored regex style as
fm_get;## Current Thesis;## Key Assumptionsitems, and anykill_criteria/reversal_conditionlines.Return a dict or
None. (Sample shape confirmed incases/aapl-2026-04/thesis-ledger.md.)parse_decision_log(case_dir)→load_csv_rows("decision-log.csv"); keepdate, research_object, decision_type, basis, risk, required_followup. Return list orNone.(Header confirmed in
cases/aapl-2026-04/decision-log.csv.)refresh_status∈overdue | due_soon | ok | unknownanddays_to_stale(int|null),computed by extracting the first
DATE_REmatch from thestale_afterfrontmatter field andcomparing to
datetime.date.today()(allowed — this is the build step, not the sandboxedresearch workflow).
due_soon= within 14 days.must_refresh_ifandkill_criteriafrom README frontmatter when present.build_evidence(), alongsideconflict_count, addconflicts: a short list of{claim, conflict_status, source_id}for rows whoseconflict_status∉ {none, ""}. Cap at ~10 to keep JSON small.(
consensus_proxy,evidence_status,next_check) are surfaced in the frontend.app.js(new/updated cockpit panels)statebadge (stale/active), current-thesis text, kill/reversalcriteria, key assumptions, framework + overlays. Empty-state when no ledger.
renderExpectationMap()to showconsensus_proxyandnext_check(and
evidence_statusas a chip). This is the "where's the surprise vs consensus" view.refresh_status.conflict_countexpandable to listevidence.conflicts.refresh_status === "overdue".Feature 2 — Per-stage action menus (launcher)
Add a
STAGE_ACTIONSconfig inapp.js:pipeline_stage → [{ label, kind, template }].kind∈prompt | shell | open(this typing is the backend seam — a future server executes bykind; v1 copies
templateto clipboard vianavigator.clipboard.writeText).templateis afunction of the case (interpolates slug / ticker / label / loop path).
Stage → actions (sourced from
AGENT_QUICKSTART.mdrouting table +loops/):idealoops/analysis-routing.md· Promote to Queuedqueuedloops/research-loop.md(paste task card for this case)intakeresearchingexpectation-map.csv· run gatespackage_completeloops/monitoring-loop.md·loops/thesis-update-loop.md· checkactionability-bridge.mdneeds_refreshscripts/check_updates.shthenloops/monitoring-loop.md/loops/event-delta-loop.md(show the stale-trigger date)archivedCard-level actions (any stage): Open memo / evidence-log / expectation-map (copy path or
file://link) · Copy next-step prompt (fromnext_action) · Copy case folder pathcases/<slug>/.UI: clicking an action opens a small popover/modal showing the exact text with a Copy button;
no execution in v1.
Feature 3 — Live data connector (static snapshot-fold + seam)
build_data.pyload_live_snapshot(ticker)→ look underprivate/futu/<TICKER>*andprivate/ibkr/<TICKER>*for the newest emitted snapshot JSON. Implementation step: read
tools/mira_data/emit.pyandtools/mira_data/adapters/to match the exact emitted JSON shape (last price, as-of timestamp,source id). Return
{price, as_of, source, connector}orNone.build_live_data()— when a snapshot exists, setconnectortofutu_opend_local/ibkr_gateway_local, fill realprice/as_of/ freshness (compare as-of to today), else keepthe honest
connector: "none"default. Never fabricate a price.app.jsrenderLiveData()— whenconnector !== "none", show live price / as_of / source / freshnesswith a live-vs-stale badge; keep the current honest empty state otherwise.
Operator step (document in
HANDOFF.md)private/is gitignored — snapshots stay local. No account ids / positions enter tracked files(per
docs/futu-private-data.md,docs/ibkr-private-data.md).Backend seam (v2, documented not built)
A thin
dashboard/server.py(stdlibhttp.server) exposing/api/live/<slug>(shellsmira_data fetch) and/api/run(executes a stage action bykind). Frontend prefers theseendpoints when reachable, falls back to static JSON / clipboard otherwise. The
kindtyping onstage actions and the
connectorfield already make this a drop-in.Verification
python3 dashboard/build_data.py→ runs clean, 36+ cases, no writes intocases/(
git status cases/clean after run). Spot-checkaapl-2026-04indata/cases.json: ledgerfields, decision-log rows,
refresh_status, expectation-map extra columns,conflictslist.python3 -m http.server 8420 --directory dashboard, open in browser. Confirm:aapl-2026-04.needs_refreshcases past their stale date show an overdue badge on board + cockpit.private/futu/AAPL, re-run build, confirmthe live price renders; then remove the test snapshot.
build_data.py; confirmdata/cases.jsonstill gitignored andcases/untouched.Out of scope (v1)
mira_pipeline_sync.py, not the browser).mira_pipeline_sync.pyregex fix (tracked separately inHANDOFF.md).Filing
After approval, file this as a GitHub issue on
byteseek/Miraviagh issue create(title:"Case Cockpit: surface dropped state, per-stage actions, live data"), body = this plan.
byteseek/Mirais a public repo — confirm that's the right target before filing.