1.2 — RunsManager: multi-run engine keyed on the append-only runs/index - #70
Conversation
The three test gaps the RunsManager review flagged: - RunRegistry.backfill: fills ONLY missing metadata (first-registration wins), no-throw on unknown runId — mutation-verified (dropping the missing-only guard fails it). - RunRegistry.all(): returns copies — a mutated snapshot can't corrupt registry state. - cross-run PULSE routing: a background run's pulse RECORD is gated on selection (not just iter) — never reaches the inspector while another run is selected. - stale-warming: selecting a run whose FINISHED landed inside the ≤700ms poll window shows completion, NOT warming (no terminal-badge inversion) — mutation-verified (reverting the disk re-check fails it). extension 111 tests (+8), repo green (amico-run 47, schema 34); typecheck clean.
|
Status: drafted temporarily alongside #68/#72 while we close out a hands-on finding from live testing. The finding was environmental (stale lab Julia env — DirectTrajOpt 0.9.6 installed vs pinned 0.9.7 — crashed solves before pulse emission; full detail in the #72 comment). No defect found in the RunsManager during the live pass: index-driven discovery, per-run tracking, and completion registration all behaved. Env fix applied + verified; will mark ready after a fresh end-to-end pass. |
|
✅ Ready again. End-to-end verification green after the env fix (detail on #72): live solve discovered via the index tail, tracked to completion, FINISHED-keyed, fidelity registered. No changes needed in this PR. |
|
Context note (run-dir contract / #64) — folding into the full review. Traced how this stack relates to the coming pre-solve
Two things to confirm as part of the review (non-blocking):
Stack posture, for reference: #68 (Scheduler) and #72 (Inspector — protocol-only, zero file reads) are correctly contract-blind, so they need no changes when #64 lands. #78's corpus is the producer, so that's where we'll add "emit + assert |
|
Thanks for tracing this against #64 — both confirmed:
And agreed on the #78 corpus being the producer-side guard — the emitter's directive grammar makes "emit + assert |
jack-champagne
left a comment
There was a problem hiding this comment.
Static/design pass over the multi-run engine (read runs_manager.ts + run_registry.ts + the ingestRunDir reader end to end). Nothing here contests the live verification — index-driven discovery, per-run tracking, and FINISHED-keyed completion are sound and you've confirmed them end to end. Findings are edge-case robustness + one design seam:
- #1 (auto-follow, :269) is the one worth settling before #72 — it's a design decision #72 inherits, not a 1.2 bug. Inline.
- The rest are narrow-trigger or cleanup: torn-FINISHED retry asymmetry at discovery (:224), a latent stale-fidelity gap in
markFinished, double-ingest on discovery, and thereadTerminal/ingestRunDirduplication. All non-blocking. - Checked the "index line lands before the run dir exists" case (the :216 skip) — non-issue: amico-run writes the run dir + manifest before appending the index line (
local_executor.ts:52-60, "manifest FIRST"), so that skip can't fire for the shipping executor. Worth preserving that ordering if another executor is ever added.
No merge-blocker — I'd just want #1 answered before this becomes #72's base.
|
|
||
| // Auto-follow: a newly REGISTERED live run is by definition the newest | ||
| // start (index lines append in creation order) — β latest-follow parity. | ||
| this.selectRun(runId); |
There was a problem hiding this comment.
[direction] registerRun ends with an unconditional this.selectRun(runId), and selectRun only bails when the run is already selected (:171) — so any newly-registered live run auto-steals selection. Fine for 1.2 (no user selection yet; only visible if a real solve's index line lands in the same ~700ms tick as the demo's explicit selectRun). But this is the seam #72 builds on: once users can pick a run, selected is written by two intents and auto-follow always wins — a background solve starting yanks the view off the run the user deliberately opened, the exact bug class this PR fixes, reintroduced at the selection layer.
Ask: add a "pinned" notion now (auto-follow only when nothing is explicitly selected, or only the first live run) so #72 isn't retrofitting sticky-selection onto a latest-follow mechanism. Not a 1.2 blocker — cheaper to settle here than in #72.
There was a problem hiding this comment.
Settled here (5353251), designed as you framed it: explicit selectRun PINS the selection (demo command today, 1.3's user clicks tomorrow); auto-follow — a newly-registered live run taking the view — only applies while nothing is pinned. selected is still written by two intents, but the explicit intent now always wins; a background solve registering can no longer steal the view. β latest-follow parity is preserved for the never-clicked case (pin starts false). Mutation-verified: forcing follow = true reds the pin test. #72 rebased on top — its activate() seam inherits sticky selection instead of retrofitting it.
| if (finishedAtDiscovery) { | ||
| // Terminal at discovery: record it (status/fidelity for the registry) but | ||
| // render nothing and never re-pop the promote prompt (β launch parity). | ||
| const t = this.readTerminal(runDir); |
There was a problem hiding this comment.
[minor] The finished-at-discovery branch calls readTerminal once and registers with no pipeline. If readTerminal returns undefined (FINISHED present but torn/invalid), the run sticks at status:undefined forever — nothing revisits it. The live path handles exactly this: checkFinished bails with if (!t) return; // torn/invalid FINISHED — next tick retries (:327).
Trigger is narrow (a torn FINISHED caught during launch replay of an already-finished run) and invisible today (nothing reads status until 1.3), so not a blocker — but mirror the live-path retry: don't finalize when readTerminal is incomplete, let a later pass re-read. (The promote suppression here is intended β launch-parity, not part of this.)
There was a problem hiding this comment.
Fixed (5353251): the finished-at-discovery branch now finalizes only when readTerminal returns whole. A torn/invalid FINISHED falls through to the live path — pipeline attaches, and checkFinished's existing next-tick retry owns it (one retry mechanism, not two). Promote stays suppressed (terminal-at-discovery is a launch replay regardless of the torn write), and no "warming" either — the warming check is disk-keyed and FINISHED exists. Test stages a mid-write FINISHED (status = "comp), asserts phase stays live with no undefined-status record, then completes it and asserts status+fidelity land with promote still suppressed.
| if (r.latestIter === undefined || iter > r.latestIter) r.latestIter = iter; | ||
| } | ||
|
|
||
| markFinished(runId: string, status: RunStatus, fidelity?: number): void { |
There was a problem hiding this comment.
[minor] markFinished has no phase guard and overwrites fidelity only if defined (:92) — re-marking ("failed") after ("completed", 0.999) leaves status:"failed" with a stale fidelity:0.999. Safe today: the sole caller completeRun guards rec.phase === "finished" (runs_manager.ts:336). But this is a public registry method the 1.3 consumers will touch. Ask: guard phase inside markFinished too, or document that callers must.
There was a problem hiding this comment.
Guarded inside markFinished itself (5353251): if (!r || r.phase === "finished") return — first terminal wins, so a stray re-mark can't leave status:"failed" beside a stale fidelity. completeRun's own guard stays (it also gates teardown/logging), but the registry no longer relies on callers being polite — it's public surface for the 1.3 consumers, as you said. Unit test pins the re-mark no-op.
| // to the inspector only if this run is (still) selected — at registration | ||
| // it never is; the display replay below covers it. | ||
| let logBytes = 0; | ||
| try { logBytes = ingestRunDir(runDir, this.pipelineSink(p), this.opts.promoteThreshold ?? 0.99); } |
There was a problem hiding this comment.
[minor] Newly-discovered live runs get ingested twice: this pipeline replay (full run.log parse) then the fall-through selectRun → display replay (:178) re-parses the whole run.log again — two full reads back-to-back per run, scaling with log size × runs drained in one tick. The pipeline replay's only durable outputs are the log byte-offset and the armed pulse-meta. Worth collapsing to one pass, or having the display replay reuse the pipeline's offset.
There was a problem hiding this comment.
Collapsed to one pass (5353251): the auto-follow decision moves before the registration replay, so the single pipelineSink ingest both seeds state (offset, armed meta, iter high-water) and feeds the display — displaySink now backs only explicit selection replays. Test asserts displaySink is never constructed during discovery while the replayed history still reaches the inspector. (On the rebased #72 the same single pass fans runId-tagged into the run's pane, and selectRun skips re-ingest entirely for runs with a live pipeline.)
| /** FINISHED (+ result.toml fidelity) with the same validation + say-why | ||
| * logging as β (S4: a present-but-invalid result.toml is named, not | ||
| * silently dropped). */ | ||
| private readTerminal(runDir: string): { status: RunStatus; fidelity?: number } | undefined { |
There was a problem hiding this comment.
[minor] readTerminal re-implements the FINISHED-validate → status → result.toml-validate → fidelity → say-why sequence that ingestRunDir already runs for the live path. Schema delegation is clean on both sides (validateFinished/validateResult, as you noted on the #64 thread) — it's the orchestration that's duplicated, so a contract change (new FINISHED field, the coming formulation.toml) has to be edited in both or finished-at-discovery diverges from live-completed. Worth folding this onto the reader (e.g. a terminal-only mode of ingestRunDir).
There was a problem hiding this comment.
Folded onto the reader (5353251): new readTerminalState(runDir, onInvalidResult?) in run_dir_reader.ts owns the FINISHED-validate → status → result.toml-validate → fidelity → say-why sequence ONCE; ingestRunDir and the manager's readTerminal both delegate (the manager passes its channel as the say-why callback, so S4 logging is unchanged on both paths). A FINISHED-field change or #64's formulation.toml is now a one-place edit — and this is exactly where validateFormulation will slot in.
6831d01 to
f303949
Compare
The three test gaps the RunsManager review flagged: - RunRegistry.backfill: fills ONLY missing metadata (first-registration wins), no-throw on unknown runId — mutation-verified (dropping the missing-only guard fails it). - RunRegistry.all(): returns copies — a mutated snapshot can't corrupt registry state. - cross-run PULSE routing: a background run's pulse RECORD is gated on selection (not just iter) — never reaches the inspector while another run is selected. - stale-warming: selecting a run whose FINISHED landed inside the ≤700ms poll window shows completion, NOT warming (no terminal-badge inversion) — mutation-verified (reverting the disk re-check fails it). extension 111 tests (+8), repo green (amico-run 47, schema 34); typecheck clean.
…ished guard, single-pass discovery, one terminal orchestration Addresses jack-champagne's static/design pass, one commit per nothing — all five findings land together because #1/#4 reshape the same registerRun path: #1 (design, the #72 seam): explicit selectRun PINS the selection; auto-follow (newest registered live run, β latest-follow parity) only applies while nothing is pinned. A background solve starting can no longer yank the view off a run the user deliberately opened. Mutation-verified. #2: a FINISHED that is present but torn/invalid at discovery no longer finalizes as status:undefined-forever — the run falls through to the live path, whose checkFinished re-reads next tick (the retry the live lane already had). Promote stays suppressed (launch replay). No warming either (disk-checked: FINISHED exists). #3: RunRegistry.markFinished guards phase itself (first terminal wins) — a stray re-mark can't leave status:"failed" beside a stale fidelity. The guard now lives on the public surface, not only in completeRun. #4: discovery ingests the run dir ONCE. Auto-follow assigns selection BEFORE the registration replay, so the single pipelineSink pass both seeds state and feeds the display through routeIter/routePulse's selection gate — displaySink now backs only explicit selection replays. #5: the FINISHED→status→result.toml→fidelity orchestration lives ONCE, in run_dir_reader.readTerminalState (say-why callback preserved via the manager's channel); ingestRunDir and RunsManager.readTerminal both delegate, so a contract change (e.g. #64's formulation.toml) is edited in one place. Also rebased onto main (#68 Scheduler, #77 vsix-gate, #79 permission grant). 118 extension tests pass; typecheck + build clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…er → executor → run-dir → RunsManager → inspector (#61) Two corpus fixtures (transmon X, cavity displacement — distinct telemetry profiles: 2×8 vs 1×6, 4 vs 3 iters) + test/corpus/fake-julia, a node stand-in the executor spawns exactly like julia (last-argv script, cwd=runDir). It reads each fixture's AMICODE_SMOKE directive and emits the template's telemetry grammar (PULSE_META / ITER / PULSE → run.log via the executor's tail) with a small inter-iter delay so the LIVE tail path is exercised, then writes a schema-conformant result.toml. Zero Julia/Piccolo cost: full chain in ~0.5s. The end-to-end test pins what the unit suites can't — that the pieces AGREE: - Scheduler (#56) lifecycle is strictly serial (B starts only after A's finished event) and satisfies RunsManager's structural seam; - the executor's run-dir writes (run.toml/index/run.log/result.toml/FINISHED) are exactly what the manager's tailer/registry read back (fidelity + iter high-water land in the registry); - telemetry reaches the inspector runId-keyed per run with no cross-tagging (asserted by per-run record dims, completion fidelity, iter records). Runs in the regular vitest suite → already in CI's fast job; #62 promotes it to a named required gate. Mutation-verified: a wrong result.toml fidelity reds the registry + completion assertions. Branch note: contains merge of rchari/56-scheduler (the corpus drives the real Scheduler); diff collapses once #68/#70/#72 land. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ns/index (#57) Replaces β's single-run RunsRootWatcher. Discovery now tails `runs/index` (amico-run's appendIndex TSV) instead of following the `latest` symlink — `latest` keeps being written (frozen contract) but a second concurrent solve no longer yanks tracking off the first mid-flight: every run WITHOUT a FINISHED gets its own pipeline (replay → run-dir watch → run.log tail) and is tracked to completion. - run_registry.ts (pure, vscode-free): parseIndexLine (tolerant of torn/blank lines — the tail heals) + RunRegistry (idempotent by runId; iter high-water; FINISHED-keyed terminal state). - log_tailer.ts: LogTailer extracted verbatim from file_watcher.ts — reused for every run.log AND the index (both append-only). - runs_manager.ts: per-run pipelines with per-run PulseStream/SinkDedup; runId-gated routing — the SELECTED run drives the single-run Inspector + StatusBar (selection auto-follows the newest started run, β latest-follow parity; `selectRun` is 1.3's seam), while completions + the promote-once prompt fire for EVERY run, selected or not. Completion keys on FINISHED (never result.toml presence). Poll backstop + idempotent consumers as before. attachScheduler consumes the #56 lifecycle (structural SchedulerLike so this is independent of #68's merge): `started` registers + selects immediately. - extension.ts: RunsManager replaces the watcher; replayDemo now renders via EXPLICIT selection (pokeDiscovery + selectRun) since a finished-at-discovery run registers quietly (idle-at-launch parity) — promote stays suppressed. - file_watcher.ts deleted (superseded); its statemachine tests ported to runs_manager.test.ts (idle-on-finished, warming→FINISHED-keyed completion, #66 pulse tail routing, replay-seeded meta) + new multi-run coverage: concurrent runs both tracked with background completion/promote, re-select replay without promote re-pop, missing-dir index lines tolerated, the Scheduler seam, and the demo-replay explicit-selection path. 15 new tests; repo green (extension 103, amico-run 47, schema 34); typecheck + build clean. Closes #57. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ata backfill, watch guards Adversarial review (0 must-fix, 2 should-fix) — applied: - selectRun re-checks DISK for FINISHED before posting warming (β parity): registry phase can be ≤700ms stale, and warming-after-completion inverted the terminal badge — real once 1.3's user-driven selectRun lands. - RunRegistry.backfill: a scheduler-registered run (runId+runDir only) gains createdAt/scriptPath when its index line lands — the 1.3 trees would otherwise see undefined metadata on every scheduler-launched run. - fs.watch 'error' listeners on all three watcher sites (root, per-run dir, tailer) — an unhandled FSWatcher error is an uncaught host exception; the poll backstop keeps things live. - RunRegistry.all() returns copies (1.3 callers can't mutate registry state); honest header comment on the transient replay/tail re-delivery window. - test/log_tailer.test.ts (review gap — the tailer is now load-bearing for discovery): torn-line carry-over, truncation re-read, startOffset contract, poke self-attach. 19 tests green, typecheck clean. Still owed next session (review nits, test-only): cross-run PULSE routing gate test, backfill unit test, stale-warming pin test; inspector pendingPulse reset on selection switch is deferred to 1.3. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The three test gaps the RunsManager review flagged: - RunRegistry.backfill: fills ONLY missing metadata (first-registration wins), no-throw on unknown runId — mutation-verified (dropping the missing-only guard fails it). - RunRegistry.all(): returns copies — a mutated snapshot can't corrupt registry state. - cross-run PULSE routing: a background run's pulse RECORD is gated on selection (not just iter) — never reaches the inspector while another run is selected. - stale-warming: selecting a run whose FINISHED landed inside the ≤700ms poll window shows completion, NOT warming (no terminal-badge inversion) — mutation-verified (reverting the disk re-check fails it). extension 111 tests (+8), repo green (amico-run 47, schema 34); typecheck clean.
…ished guard, single-pass discovery, one terminal orchestration Addresses jack-champagne's static/design pass, one commit per nothing — all five findings land together because #1/#4 reshape the same registerRun path: #1 (design, the #72 seam): explicit selectRun PINS the selection; auto-follow (newest registered live run, β latest-follow parity) only applies while nothing is pinned. A background solve starting can no longer yank the view off a run the user deliberately opened. Mutation-verified. #2: a FINISHED that is present but torn/invalid at discovery no longer finalizes as status:undefined-forever — the run falls through to the live path, whose checkFinished re-reads next tick (the retry the live lane already had). Promote stays suppressed (launch replay). No warming either (disk-checked: FINISHED exists). #3: RunRegistry.markFinished guards phase itself (first terminal wins) — a stray re-mark can't leave status:"failed" beside a stale fidelity. The guard now lives on the public surface, not only in completeRun. #4: discovery ingests the run dir ONCE. Auto-follow assigns selection BEFORE the registration replay, so the single pipelineSink pass both seeds state and feeds the display through routeIter/routePulse's selection gate — displaySink now backs only explicit selection replays. #5: the FINISHED→status→result.toml→fidelity orchestration lives ONCE, in run_dir_reader.readTerminalState (say-why callback preserved via the manager's channel); ingestRunDir and RunsManager.readTerminal both delegate, so a contract change (e.g. #64's formulation.toml) is edited in one place. Also rebased onto main (#68 Scheduler, #77 vsix-gate, #79 permission grant). 118 extension tests pass; typecheck + build clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
f303949 to
5353251
Compare
…er → executor → run-dir → RunsManager → inspector (#61) Two corpus fixtures (transmon X, cavity displacement — distinct telemetry profiles: 2×8 vs 1×6, 4 vs 3 iters) + test/corpus/fake-julia, a node stand-in the executor spawns exactly like julia (last-argv script, cwd=runDir). It reads each fixture's AMICODE_SMOKE directive and emits the template's telemetry grammar (PULSE_META / ITER / PULSE → run.log via the executor's tail) with a small inter-iter delay so the LIVE tail path is exercised, then writes a schema-conformant result.toml. Zero Julia/Piccolo cost: full chain in ~0.5s. The end-to-end test pins what the unit suites can't — that the pieces AGREE: - Scheduler (#56) lifecycle is strictly serial (B starts only after A's finished event) and satisfies RunsManager's structural seam; - the executor's run-dir writes (run.toml/index/run.log/result.toml/FINISHED) are exactly what the manager's tailer/registry read back (fidelity + iter high-water land in the registry); - telemetry reaches the inspector runId-keyed per run with no cross-tagging (asserted by per-run record dims, completion fidelity, iter records). Runs in the regular vitest suite → already in CI's fast job; #62 promotes it to a named required gate. Mutation-verified: a wrong result.toml fidelity reds the registry + completion assertions. Branch note: contains merge of rchari/56-scheduler (the corpus drives the real Scheduler); diff collapses once #68/#70/#72 land. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
All five findings addressed in Summary of the changes:
#72 and #78 are rebased on top (the pin composes with 1.3's fan-out: registration fans runId-tagged into the run's pane; |
|
Re-verified against
One still open — #3: a run whose dir isn't on disk yet at its index-line tick is still |
…er → executor → run-dir → RunsManager → inspector (#61) Two corpus fixtures (transmon X, cavity displacement — distinct telemetry profiles: 2×8 vs 1×6, 4 vs 3 iters) + test/corpus/fake-julia, a node stand-in the executor spawns exactly like julia (last-argv script, cwd=runDir). It reads each fixture's AMICODE_SMOKE directive and emits the template's telemetry grammar (PULSE_META / ITER / PULSE → run.log via the executor's tail) with a small inter-iter delay so the LIVE tail path is exercised, then writes a schema-conformant result.toml. Zero Julia/Piccolo cost: full chain in ~0.5s. The end-to-end test pins what the unit suites can't — that the pieces AGREE: - Scheduler (#56) lifecycle is strictly serial (B starts only after A's finished event) and satisfies RunsManager's structural seam; - the executor's run-dir writes (run.toml/index/run.log/result.toml/FINISHED) are exactly what the manager's tailer/registry read back (fidelity + iter high-water land in the registry); - telemetry reaches the inspector runId-keyed per run with no cross-tagging (asserted by per-run record dims, completion fidelity, iter records). Runs in the regular vitest suite → already in CI's fast job; #62 promotes it to a named required gate. Mutation-verified: a wrong result.toml fidelity reds the registry + completion assertions. Branch note: contains merge of rchari/56-scheduler (the corpus drives the real Scheduler); diff collapses once #68/#70/#72 land. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…2e (#78) * feat(1.3): Run Inspector single→multi-run (runId-keyed protocol, per-run panes) Freeze-2 reshape (#58): the host↔webview message protocol is now runId-keyed and both the host and the webview fan into per-run panes. Single→multi only — the pane markup stays the current pulseplot (design lane, UX4 #49). Host (run_inspector.ts): a PaneBuffer per runId + activeRunId; runId-keyed surface postPulse/postIterationRecord/postCompletion/setWarmingUp/setRunLabel + new activate(runId). Per-run 5 Hz pulse throttle. resolveWebviewView replays EVERY pane from its buffer (S36) with positional ordering, then posts activate last. setWarmingUp guarded from clobbering a pane that already has data/terminal state. pulse stays plot-only (deliberately does not clear warming). Webview (media/ui/views/inspector.ts): createPanel() instances the former single-run view per runId (no shared globals); a router keys panels by runId, activate toggles the one visible pane, background/late messages only touch their own pane. Pane-hiding uses two-class selectors so it wins over layout.css `.stack` on specificity, not stylesheet order. RunsManager (runs_manager.ts): fans every run's live events into the inspector runId-tagged (routeIter/routePulse ungated); registration replay is state-only so the selected run never double-posts; selectRun adds activate; the single status bar stays selection-gated; completion + promote still fire per-run. Tests: runs_manager + inspector_view_contract updated to the runId-keyed API and fan-out semantics; added per-run isolation, per-run-throttle independence, S36 reopen, activate-last, warming-guard. New happy-dom webview test covers the router itself (per-run isolation, activate toggle, empty-state, plot-only pulse) — closes the coverage gap flagged in adversarial review. All invariants mutation-verified. 121 tests pass; typecheck + build clean. S6 (formulation preview) deferred — no formulation-emit in the frozen contract. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor(1.3): flow RunCompletion whole through completeRun — the #84/#81 seam Jack's #72 merge-seam heads-up: #81 adds `formulation?` to RunCompletion, and completeRun was the third completion path cherry-picking fields positionally (runId/status/fidelity) — once #81 landed, live-completed runs would carry formulation: undefined while replayed runs got it (the exact bug Kate caught on onFinished, reintroduced here). completeRun now takes the WHOLE RunCompletion; both feeders (ingestRunDir's sink verbatim, checkFinished via {runId, runDir, ...readTerminalState()}) funnel the object from the one shared read. An additive field is now a one-place edit (RunCompletion + readTerminalState) and reaches every consumer by construction — consumers cherry-pick at the leaf. Documented as the #84 funnel on both the type and completeRun; the full N-reader consolidation (catalog hydrator etc.) stays #84. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(1.4a): smoke corpus — seconds-scale end-to-end fixtures, Scheduler → executor → run-dir → RunsManager → inspector (#61) Two corpus fixtures (transmon X, cavity displacement — distinct telemetry profiles: 2×8 vs 1×6, 4 vs 3 iters) + test/corpus/fake-julia, a node stand-in the executor spawns exactly like julia (last-argv script, cwd=runDir). It reads each fixture's AMICODE_SMOKE directive and emits the template's telemetry grammar (PULSE_META / ITER / PULSE → run.log via the executor's tail) with a small inter-iter delay so the LIVE tail path is exercised, then writes a schema-conformant result.toml. Zero Julia/Piccolo cost: full chain in ~0.5s. The end-to-end test pins what the unit suites can't — that the pieces AGREE: - Scheduler (#56) lifecycle is strictly serial (B starts only after A's finished event) and satisfies RunsManager's structural seam; - the executor's run-dir writes (run.toml/index/run.log/result.toml/FINISHED) are exactly what the manager's tailer/registry read back (fidelity + iter high-water land in the registry); - telemetry reaches the inspector runId-keyed per run with no cross-tagging (asserted by per-run record dims, completion fidelity, iter records). Runs in the regular vitest suite → already in CI's fast job; #62 promotes it to a named required gate. Mutation-verified: a wrong result.toml fidelity reds the registry + completion assertions. Branch note: contains merge of rchari/56-scheduler (the corpus drives the real Scheduler); diff collapses once #68/#70/#72 land. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(1.4a): review #78 — failure-lane fixture, throwing pumpUntil, wiring-vs-format scope note 1. failing_solve.jl (exit=1): the previously-dead `exit=` directive support now has a fixture — a solve that emits two iterations then dies. Asserts the full failure path end-to-end: executor writes FINISHED{failed}, no result.toml, registry terminal with fidelity undefined but latestIter=2 (pre-crash telemetry tracked), completion fans runId-keyed, promote never fires. 2. pumpUntil now THROWS on timeout with a named condition — a wiring regression fails fast at the offending await instead of an opaque hang. 3. Scope note in the suite header: this guards WIRING (fake ↔ parser), not FORMAT (template ↔ parser) — that boundary is #83's. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…/stop truth, model pin, audit sweep, formatter (#89) * feat(1.1): Scheduler — serial run queue built to the ratified Executor contract (#56) `enqueue(spec, {concurrent?}) → ScheduledRun{queueId, handle: Promise<RunHandle>, cancel}` plus a multi-consumer lifecycle stream (queued/started/finished/cancelled/error) for RunsManager/StatusBar (1.2). Lives in @amicode/amico-run (node-only, no vscode) beside the LocalExecutor it drives. Built TO the Track C contract (ratified 2026-07-02) so Δ8's RemoteExecutor drops in with zero reshape: - S12: enqueue resolves to the executor's RunHandle UNTOUCHED (identity passthrough, pinned by test) — downstream never sees an executor type. - (b) abort() is a request, not a kill: the pump advances ONLY when `finished` resolves; a post-abort() run still holds the queue (pinned by test). - (c) per-executor warming budget: the Scheduler owns NO timers — structurally pinned (test greps the source for setTimeout/setInterval). - (d) `finished` never rejects per contract; a rogue rejection is survived (error event) rather than wedging every queued run. Semantics: strictly serial; cancel() dequeues only pre-start (a live run is stopped via RunHandle.abort(), never the queue); a submit() ConfigError rejects that entry's handle, emits `error`, and the queue advances; `concurrent: true` is the NAMED Phase-4 seam — rejected loudly (ConfigError) instead of silently serializing. Listener errors are isolated from the pump. TDD: 12 tests (RED first) — serial ordering, S12 identity, opts passthrough, abort≠terminated, lifecycle sequence with positions, cancel pre/post start, ConfigError advance, the concurrent seam, multi-listener + dispose, throwing listener, and the no-timers structural pin. Repo suite green (schema 34, amico-run 59, extension 80); build + typecheck clean. Closes #56. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(1.1): scheduler review — microtask-deferred re-pump + enforce the defensive claims Adversarial review (mutation-tested; 0 must-fix, 2 should-fix) — all folded in: - finally's re-pump is now queueMicrotask-deferred: a contract-violating executor whose submit() throws SYNCHRONOUSLY previously made the finally a direct recursion — a backlog of such failures accumulated behind a pending run blew the stack on drain (RangeError) and STRANDED the rest of the queue. Mutation-verified: reverting to the direct call fails the new test (RangeError + timeout); the deferral drains 8000 sync-throwers flat. - The rogue-`finished`-rejection branch is now enforced, not just advertised: new test pins error-event + queue-advance (mutation-verified: deleting the branch fails it). Rejection reason normalized (instanceof Error) to match the submit path. - Explicit unhandledRejection pin for the internal handle.catch suppression (an untouched ScheduledRun.handle never trips the process on cancel). - cancel() docstring now names all three false cases (started / already cancelled / mid-submit, where handle may still reject); no-timers grep widened to setImmediate|Date.now. 15 tests green; repo suite green; typecheck clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat: catalog entry card — components, save-to-catalog flow, session catalog (UX2) (#73) * feat(47): catalog entry card — components, save-to-catalog flow, session catalog (UX2) The catalog card (Krishna p5, UX2) shipped end to end as a seam prototype. Identity is Hamiltonian-anchored and user-named; the card is the feedback artifact for the open field-selection questions. Components (media/ui): - chip: the entry's handle — gate · system · tags · #index. The system slot carries the USER-ASSIGNED name (researchers think in named devices, "Emerald-Q3"); derived family is the fallback. Tags render as dashed "proposed" segments — none of tags/index/name are in catalog-entry.schema.json, and the marking is deliberate. - catalogcard: header (chip + run-id + Tune/Warm-Start/Promote actions, VS Code secondary-button styling, right-justified) → pulseplot (reused, hydrated) → metadata / pulse-data / high-level-metrics panels → sibling-chips row. Metrics are uniform hero cards in a wrapping flex row: fidelity · gate time (params.T) · spectral bandwidth (95%-power, computed client-side from the knots, DC removed; definitional choices marked proposed) · robustness (empty proposed slot — needs perturbed rollouts recorded at solve time). Solver telemetry (iterations/wall) lives in metadata: provenance, not pulse quality. Drive labels are u_i, matching the trajectory component and Piccolo's plot defaults. Flow + shell (src): - Save to catalog: a converged run's promote prompt (live) or the demo replay prompt → optional system-name and tags input → a pointer entry in the session catalog (workspaceState — NOT the Phase-3 CatalogStore; Q91/Q92 open) → the card opens, hydrated from the real run artifacts (run.toml identity; result.toml fidelity/params with gate/system lifted; run.log pulse lines → the plot). - Catalog tree view: chip-shaped rows (gate · system · fidelity, tags in description/tooltip), newest-first, deduped by run_id; click reopens the card. amicode.catalog.refresh actually registered. - The solve-template change that records params.gate/params.system on NEW runs ships separately; the bundled demo fixture already carries them (schema-validated). Tests: pulse-line grammar fixtures, hydration (field mapping, gate lift, newest-record pulse, degradation), session tree (dedup, order, open-card command, empty state). Closes #47's Kate-lane scope as amended (actions row instead of the "what do next" section; resume hand-off design returns to UX1 — see the deferred-items comment on #46). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(47): remove-from-catalog — non-destructive unsave via the tree context menu Right-click a Catalog row → "Remove from Catalog": deletes the POINTER record only (workspaceState) — the run dir and pulse.jld2 stay on disk, per the raw-data-trust principle. Archive/supersede lifecycle belongs to the Phase-3 CatalogStore (Q94/Q95), deliberately not faked here. Kept off the command palette (when: false) — the action only means something on a row. Tests: removal preserves remaining order, is idempotent, and rows carry the context value gating the menu. Part of #47. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(47): reveal-or-create dedupe for catalog-card panels (review) Clicking the same catalog row repeatedly spawned duplicate panels. Per Jack's review: run_id → live-panel map, second open re-focuses via reveal, disposal cleans the map so a closed tab re-creates fresh. Shell test covers all three; vscode stub grows createWebviewPanel + registerCommand to support it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * 1.2 — RunsManager: multi-run engine keyed on the append-only runs/index (#70) * feat(1.2): RunsManager — multi-run engine keyed on the append-only runs/index (#57) Replaces β's single-run RunsRootWatcher. Discovery now tails `runs/index` (amico-run's appendIndex TSV) instead of following the `latest` symlink — `latest` keeps being written (frozen contract) but a second concurrent solve no longer yanks tracking off the first mid-flight: every run WITHOUT a FINISHED gets its own pipeline (replay → run-dir watch → run.log tail) and is tracked to completion. - run_registry.ts (pure, vscode-free): parseIndexLine (tolerant of torn/blank lines — the tail heals) + RunRegistry (idempotent by runId; iter high-water; FINISHED-keyed terminal state). - log_tailer.ts: LogTailer extracted verbatim from file_watcher.ts — reused for every run.log AND the index (both append-only). - runs_manager.ts: per-run pipelines with per-run PulseStream/SinkDedup; runId-gated routing — the SELECTED run drives the single-run Inspector + StatusBar (selection auto-follows the newest started run, β latest-follow parity; `selectRun` is 1.3's seam), while completions + the promote-once prompt fire for EVERY run, selected or not. Completion keys on FINISHED (never result.toml presence). Poll backstop + idempotent consumers as before. attachScheduler consumes the #56 lifecycle (structural SchedulerLike so this is independent of #68's merge): `started` registers + selects immediately. - extension.ts: RunsManager replaces the watcher; replayDemo now renders via EXPLICIT selection (pokeDiscovery + selectRun) since a finished-at-discovery run registers quietly (idle-at-launch parity) — promote stays suppressed. - file_watcher.ts deleted (superseded); its statemachine tests ported to runs_manager.test.ts (idle-on-finished, warming→FINISHED-keyed completion, #66 pulse tail routing, replay-seeded meta) + new multi-run coverage: concurrent runs both tracked with background completion/promote, re-select replay without promote re-pop, missing-dir index lines tolerated, the Scheduler seam, and the demo-replay explicit-selection path. 15 new tests; repo green (extension 103, amico-run 47, schema 34); typecheck + build clean. Closes #57. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(1.2): runs-manager review — disk-checked warming, scheduler metadata backfill, watch guards Adversarial review (0 must-fix, 2 should-fix) — applied: - selectRun re-checks DISK for FINISHED before posting warming (β parity): registry phase can be ≤700ms stale, and warming-after-completion inverted the terminal badge — real once 1.3's user-driven selectRun lands. - RunRegistry.backfill: a scheduler-registered run (runId+runDir only) gains createdAt/scriptPath when its index line lands — the 1.3 trees would otherwise see undefined metadata on every scheduler-launched run. - fs.watch 'error' listeners on all three watcher sites (root, per-run dir, tailer) — an unhandled FSWatcher error is an uncaught host exception; the poll backstop keeps things live. - RunRegistry.all() returns copies (1.3 callers can't mutate registry state); honest header comment on the transient replay/tail re-delivery window. - test/log_tailer.test.ts (review gap — the tailer is now load-bearing for discovery): torn-line carry-over, truncation re-read, startOffset contract, poke self-attach. 19 tests green, typecheck clean. Still owed next session (review nits, test-only): cross-run PULSE routing gate test, backfill unit test, stale-warming pin test; inspector pendingPulse reset on selection switch is deferred to 1.3. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(1.2): close the owed #70 review-nit gaps (all mutation-verified) The three test gaps the RunsManager review flagged: - RunRegistry.backfill: fills ONLY missing metadata (first-registration wins), no-throw on unknown runId — mutation-verified (dropping the missing-only guard fails it). - RunRegistry.all(): returns copies — a mutated snapshot can't corrupt registry state. - cross-run PULSE routing: a background run's pulse RECORD is gated on selection (not just iter) — never reaches the inspector while another run is selected. - stale-warming: selecting a run whose FINISHED landed inside the ≤700ms poll window shows completion, NOT warming (no terminal-badge inversion) — mutation-verified (reverting the disk re-check fails it). extension 111 tests (+8), repo green (amico-run 47, schema 34); typecheck clean. * fix(1.2): review #70 — pinned selection, torn-FINISHED retry, markFinished guard, single-pass discovery, one terminal orchestration Addresses jack-champagne's static/design pass, one commit per nothing — all five findings land together because #1/#4 reshape the same registerRun path: #1 (design, the #72 seam): explicit selectRun PINS the selection; auto-follow (newest registered live run, β latest-follow parity) only applies while nothing is pinned. A background solve starting can no longer yank the view off a run the user deliberately opened. Mutation-verified. #2: a FINISHED that is present but torn/invalid at discovery no longer finalizes as status:undefined-forever — the run falls through to the live path, whose checkFinished re-reads next tick (the retry the live lane already had). Promote stays suppressed (launch replay). No warming either (disk-checked: FINISHED exists). #3: RunRegistry.markFinished guards phase itself (first terminal wins) — a stray re-mark can't leave status:"failed" beside a stale fidelity. The guard now lives on the public surface, not only in completeRun. #4: discovery ingests the run dir ONCE. Auto-follow assigns selection BEFORE the registration replay, so the single pipelineSink pass both seeds state and feeds the display through routeIter/routePulse's selection gate — displaySink now backs only explicit selection replays. #5: the FINISHED→status→result.toml→fidelity orchestration lives ONCE, in run_dir_reader.readTerminalState (say-why callback preserved via the manager's channel); ingestRunDir and RunsManager.readTerminal both delegate, so a contract change (e.g. #64's formulation.toml) is edited in one place. Also rebased onto main (#68 Scheduler, #77 vsix-gate, #79 permission grant). 118 extension tests pass; typecheck + build clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * 1.3: Run Inspector single→multi-run (runId-keyed protocol, per-run panes) (#72) * feat(1.3): Run Inspector single→multi-run (runId-keyed protocol, per-run panes) Freeze-2 reshape (#58): the host↔webview message protocol is now runId-keyed and both the host and the webview fan into per-run panes. Single→multi only — the pane markup stays the current pulseplot (design lane, UX4 #49). Host (run_inspector.ts): a PaneBuffer per runId + activeRunId; runId-keyed surface postPulse/postIterationRecord/postCompletion/setWarmingUp/setRunLabel + new activate(runId). Per-run 5 Hz pulse throttle. resolveWebviewView replays EVERY pane from its buffer (S36) with positional ordering, then posts activate last. setWarmingUp guarded from clobbering a pane that already has data/terminal state. pulse stays plot-only (deliberately does not clear warming). Webview (media/ui/views/inspector.ts): createPanel() instances the former single-run view per runId (no shared globals); a router keys panels by runId, activate toggles the one visible pane, background/late messages only touch their own pane. Pane-hiding uses two-class selectors so it wins over layout.css `.stack` on specificity, not stylesheet order. RunsManager (runs_manager.ts): fans every run's live events into the inspector runId-tagged (routeIter/routePulse ungated); registration replay is state-only so the selected run never double-posts; selectRun adds activate; the single status bar stays selection-gated; completion + promote still fire per-run. Tests: runs_manager + inspector_view_contract updated to the runId-keyed API and fan-out semantics; added per-run isolation, per-run-throttle independence, S36 reopen, activate-last, warming-guard. New happy-dom webview test covers the router itself (per-run isolation, activate toggle, empty-state, plot-only pulse) — closes the coverage gap flagged in adversarial review. All invariants mutation-verified. 121 tests pass; typecheck + build clean. S6 (formulation preview) deferred — no formulation-emit in the frozen contract. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor(1.3): flow RunCompletion whole through completeRun — the #84/#81 seam Jack's #72 merge-seam heads-up: #81 adds `formulation?` to RunCompletion, and completeRun was the third completion path cherry-picking fields positionally (runId/status/fidelity) — once #81 landed, live-completed runs would carry formulation: undefined while replayed runs got it (the exact bug Kate caught on onFinished, reintroduced here). completeRun now takes the WHOLE RunCompletion; both feeders (ingestRunDir's sink verbatim, checkFinished via {runId, runDir, ...readTerminalState()}) funnel the object from the one shared read. An additive field is now a one-place edit (RunCompletion + readTerminalState) and reaches every consumer by construction — consumers cherry-pick at the leaf. Documented as the #84 funnel on both the type and completeRun; the full N-reader consolidation (catalog hydrator etc.) stays #84. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * 1.4a: smoke corpus — Scheduler → executor → RunsManager → Inspector e2e (#78) * feat(1.3): Run Inspector single→multi-run (runId-keyed protocol, per-run panes) Freeze-2 reshape (#58): the host↔webview message protocol is now runId-keyed and both the host and the webview fan into per-run panes. Single→multi only — the pane markup stays the current pulseplot (design lane, UX4 #49). Host (run_inspector.ts): a PaneBuffer per runId + activeRunId; runId-keyed surface postPulse/postIterationRecord/postCompletion/setWarmingUp/setRunLabel + new activate(runId). Per-run 5 Hz pulse throttle. resolveWebviewView replays EVERY pane from its buffer (S36) with positional ordering, then posts activate last. setWarmingUp guarded from clobbering a pane that already has data/terminal state. pulse stays plot-only (deliberately does not clear warming). Webview (media/ui/views/inspector.ts): createPanel() instances the former single-run view per runId (no shared globals); a router keys panels by runId, activate toggles the one visible pane, background/late messages only touch their own pane. Pane-hiding uses two-class selectors so it wins over layout.css `.stack` on specificity, not stylesheet order. RunsManager (runs_manager.ts): fans every run's live events into the inspector runId-tagged (routeIter/routePulse ungated); registration replay is state-only so the selected run never double-posts; selectRun adds activate; the single status bar stays selection-gated; completion + promote still fire per-run. Tests: runs_manager + inspector_view_contract updated to the runId-keyed API and fan-out semantics; added per-run isolation, per-run-throttle independence, S36 reopen, activate-last, warming-guard. New happy-dom webview test covers the router itself (per-run isolation, activate toggle, empty-state, plot-only pulse) — closes the coverage gap flagged in adversarial review. All invariants mutation-verified. 121 tests pass; typecheck + build clean. S6 (formulation preview) deferred — no formulation-emit in the frozen contract. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor(1.3): flow RunCompletion whole through completeRun — the #84/#81 seam Jack's #72 merge-seam heads-up: #81 adds `formulation?` to RunCompletion, and completeRun was the third completion path cherry-picking fields positionally (runId/status/fidelity) — once #81 landed, live-completed runs would carry formulation: undefined while replayed runs got it (the exact bug Kate caught on onFinished, reintroduced here). completeRun now takes the WHOLE RunCompletion; both feeders (ingestRunDir's sink verbatim, checkFinished via {runId, runDir, ...readTerminalState()}) funnel the object from the one shared read. An additive field is now a one-place edit (RunCompletion + readTerminalState) and reaches every consumer by construction — consumers cherry-pick at the leaf. Documented as the #84 funnel on both the type and completeRun; the full N-reader consolidation (catalog hydrator etc.) stays #84. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(1.4a): smoke corpus — seconds-scale end-to-end fixtures, Scheduler → executor → run-dir → RunsManager → inspector (#61) Two corpus fixtures (transmon X, cavity displacement — distinct telemetry profiles: 2×8 vs 1×6, 4 vs 3 iters) + test/corpus/fake-julia, a node stand-in the executor spawns exactly like julia (last-argv script, cwd=runDir). It reads each fixture's AMICODE_SMOKE directive and emits the template's telemetry grammar (PULSE_META / ITER / PULSE → run.log via the executor's tail) with a small inter-iter delay so the LIVE tail path is exercised, then writes a schema-conformant result.toml. Zero Julia/Piccolo cost: full chain in ~0.5s. The end-to-end test pins what the unit suites can't — that the pieces AGREE: - Scheduler (#56) lifecycle is strictly serial (B starts only after A's finished event) and satisfies RunsManager's structural seam; - the executor's run-dir writes (run.toml/index/run.log/result.toml/FINISHED) are exactly what the manager's tailer/registry read back (fidelity + iter high-water land in the registry); - telemetry reaches the inspector runId-keyed per run with no cross-tagging (asserted by per-run record dims, completion fidelity, iter records). Runs in the regular vitest suite → already in CI's fast job; #62 promotes it to a named required gate. Mutation-verified: a wrong result.toml fidelity reds the registry + completion assertions. Branch note: contains merge of rchari/56-scheduler (the corpus drives the real Scheduler); diff collapses once #68/#70/#72 land. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(1.4a): review #78 — failure-lane fixture, throwing pumpUntil, wiring-vs-format scope note 1. failing_solve.jl (exit=1): the previously-dead `exit=` directive support now has a fixture — a solve that emits two iterations then dies. Asserts the full failure path end-to-end: executor writes FINISHED{failed}, no result.toml, registry terminal with fidelity undefined but latestIter=2 (pre-crash telemetry tracked), completion fans runId-keyed, promote never fires. 2. pumpUntil now THROWS on timeout with a named condition — a wiring regression fails fast at the offending await instead of an opaque hang. 3. Scope note in the suite header: this guards WIRING (fake ↔ parser), not FORMAT (template ↔ parser) — that boundary is #83's. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> * feat: theme-calculated Harmoniqs yellow — OKLCH-solved brand accent (brand-wide) brand_accent.ts computes the deployed accent from the active theme at webview boot: the canonical #FFF676 ships EXACTLY wherever contrast vs the theme's editor background clears 3:1 (all dark themes); light themes get the closest-to-brand gold by binary-searching lightness with hue + chroma held (gamut-clamped). Two tokens with different jobs: lines (--color-accent, contrast-solved: borders/rings/marks) and fills (--color-accent-fill, always the brand lemon — black text on it ≈ 19:1; a 3:1-darkened gold passes WCAG math but reads muddy under text). --color-on-accent is contrast-picked; yellow is never text. Recomputed live on theme switch. Inspector + catalog-card webviews apply at boot; brand.css statics remain the no-JS fallback. Pill atom gains a dot-less badge variant (dot = process state; badges describe things). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * workbench: run picker + pane-ticker pause + runId-tagged controls (+ Kate's theme accent picked) - amicode.selectRun: QuickPick over the registry (newest first; live/completed/ stopped/failed icons, iter + fidelity + script). Picking pins; "Follow latest" releases the pin via RunsManager.resumeAutoFollow() (jumps to the newest live run). Pre-UX4 utility — unblocks real multi-run testing. - Hidden panes pause their 1 Hz elapsed-strip ticker (Panel.setActive from the router's activate); resumes with a fresh render on re-activation. - Control-row messages carry their pane's runId (post-UX4 correctness; commands still resolve the selected run today). - Cherry-picked Kate's 154650c: OKLCH theme-calculated brand accent — fixes the hardcoded #FFF676 that dies on light themes (audit P0-1, extension side). 408 tests pass; typecheck + build clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * workbench: wire catalog what-next — tune/warm-start stage a concrete chat prompt (clipboard + open chat); promote says Phase-3 honestly Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * workbench: chat theme bridge — iframe boots with ?colorScheme= from the editor theme; live re-theme via onDidChangeActiveColorTheme → two-lane relay (origin-pinned) → app's setColorScheme Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: one-spine mirror breadcrumb — readTerminalState semantics are mirrored in the fork's run-terminal.ts; change both in one change-set Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * workbench: animated H-robot mark in the Run Inspector (replaces the <0||0> text ket; breathe + eye-blink, reduced-motion aware); openRunDir reveals run.toml (bare-dir reveal errors on macOS) with openExternal fallback; inspector auto-open defaults ON Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * workbench: inspector mark = the house silhouette glyph (AmicoSpinner geometry + pulse-opacity language, reduced-motion aware); allow workbench.action.showCommands over the chat bridge (⌘⇧P → VS Code palette) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * workbench: chat auto-opens when the server is ready (amicode.chat.autoOpen, default on) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * workbench: inspector never reveals during the boot index replay — only a run that STARTS while the user works auto-opens it (chat remains the only boot-time surface) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * workbench: boot-quiet test coverage — fresh-run warming asserts the post-boot path; new pin that boot replay never warms/reveals Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * workbench: clipboard bridge — the framed app requests paste over the message bridge; extension answers with vscode.env.clipboard (webviews can't delegate clipboard-read into iframes) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * workbench: open-external bridge — framed app opens https links via vscode.env.openExternal (target=_blank is dead in the webview iframe) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * status bar: stalled-run gate — FINISHED-less run with run.log silent >10min shows 'stalled' (warning), never a perpetual 'running · iter N' from boot replay; mirrors fork isStalled Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * score: never leak the 'issimo' entitlement codename into chat (read as truncated Piccolissimo); dedupe doubled routing paragraph in stage-1 notes Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * stop always terminates: escalation ladder (cooperative STOP → stalled runs get kill+finalize immediately → healthy runs get a 120s grace then an explicit Force-stop offer) - stopPlan: FINISHED → no-op; fresh run.log → cooperative; log cold past the stall threshold (or logless zombie dir) → force - findRunPids: two-key match — cmdline references the run's solve script AND process cwd IS the run dir (sibling runs share the script, never the cwd; no pattern-kills, ever) - forceStop: TERM → 1.5s → KILL survivors → forceFinalize writes the terminal FINISHED (status aborted, atomic rename; run.log breadcrumb) so every contract reader on both spines converges and the UI clears - never a silent kill on a live solver: one long Ipopt iteration can look wedged — the grace path asks before forcing Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(stop): probe lsof at /usr/sbin (macOS) and /usr/bin (Linux) — hardcoded macOS path silently disabled the kill path on Linux (findRunPids proved nothing → force-finalized runs with the solver still alive) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(stop): realpath both sides of the cwd ownership proof — lsof reports physical paths, so a symlinked runs root (/tmp → /private/tmp on macOS) made every pid unprovable and the kill path a no-op Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(stop): forceStop yields to a FINISHED that appeared during the TERM→KILL window — the orchestrator's truthful verdict (failed/143) must not be overwritten with aborted (disk vs registry vs fork endpoints would disagree forever) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(stop): re-prove pid ownership before the SIGKILL sweep — a pid freed by TERM can be reused by an unrelated process inside the 1.5s window Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(stop): JSON-decode script_path from run.toml — values are written JSON-escaped, so escaped chars never matched ps argv and dropped the script-path key from the ownership match Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(stop): pin JSON-decoding of escaped script_path values Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(stop): every stop toast and the force-stop dialog name the run — a nameless dialog 120s later reads as the wrong run being wedged Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(stop): double-stop guard + escalation-timer disposal — a second Stop no longer stacks a second 120s dialog, and the timer dies with the extension instead of firing after deactivate Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(stop): tolerate a deleted run dir — STOP write and finalize are best-effort so a removed dir can't crash the command before the UI entry is cleared Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * perf(runs): 2s TTL cache on liveStatus — boot replay of a long run.log paid one statSync per iter line Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(runs): the poll backstop downgrades the selected run to 'stalled' — routeIter only fires when a line ARRIVES (i.e. not stalled), so a run that wedged mid-watch kept 'running · iter N' forever; downgrade-only so warming/iter flow stays untouched + pin test Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(runs): selectRun consults liveStatus instead of hardcoding 'running' (picking a stalled run stamped a lie nothing would correct); finished runs' display replay no longer flickers stalled/running per line — completion sets the bar once Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore: remove empty runs_manager_boot.test.ts accidentally created by a shell append probe two commits ago (vitest fails on suite-less files) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(picker): stalled runs show '$(warning) stalled · iter N' — the picker advertised a wedge as '$(pulse) live', contradicting the status bar Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(bridge): https scheme check is case-insensitive (RFC 3986); clipboard replies gated on panel visibility — a hidden panel rendering LLM-driven content must not sample the OS clipboard in the background Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(scores-e2e): version-agnostic score-marker assertion — hardcoded 'v1' while SCORE.md is v3, red on every creds-bearing machine Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor(runs): one STALL_AFTER_MS (runs_manager imports run_controls's) and the one-spine mirror comment now names the stall threshold + display vocabulary as mirrored semantics Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor(runs): one STALL_AFTER_MS (runs_manager imports run_controls's export); one-spine mirror comment names the stall threshold + display vocabulary as mirrored semantics Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * style: prettier 3.6.2 over the branch's touched files + a minimal .prettierrc (printWidth 120, semi) — the repo had no formatter configured; config formalizes the existing conventions Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * style: prettier over the branch's touched amico-run files (missed in the previous style commit) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * style: prettier repo-wide (new .prettierrc/.prettierignore) — one-time full-repo normalization so the formatter is enforceable from here Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(config): fallback-only model pin — without one, opencode's default resolution gambles on provider ordering and (with Google creds) picked a hanging preview model for every headless/agent turn; anthropic > GA gemini flash, and a user's global model always wins (1.17.3 preserve contract) + tests Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * ci(vsix-gate): fix by splitting package step — explicit build + fetch:opencode + vsce * test(slow): live-turn extractor is model-agnostic — Gemini opens with the amicode_ask TOOL CALL and no prose, so the ask input (question+options) counts as the turn text; production model pin wired into the e2e servers Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(bridge): save-file lane — the run-card gallery's PNG export routes through a save dialog (downloads are dead in the framed app); PNG-only, basename-sanitized, size-bounded, relay-allowlisted Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(config): google pin moves to gemini-2.5-flash — the newest flash is capacity-throttled at peak ('model overloaded' → failed turns render as 'model undefined' stubs in chat); the GA flash answered in 1.4s during the same window Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(config): creds-free default model — opencode/deepseek-v4-flash-free (zen free tier, no user quota; answered a tool-bearing turn in ~3s while Gemini kept capacity-throttling); anthropic still wins when creds exist, user global model always wins Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * ci: authenticate the private-fork opencode fetch (GH_TOKEN) fast/vsix-gate/boot-smoke fetch the vendored opencode binary from the PRIVATE harmoniqs/opencode release via `gh release download`. The default Actions GITHUB_TOKEN is scoped to this repo only, so gh is unauthenticated for harmoniqs/opencode and the fetch exits 1 — reding every job that vendors opencode (schema-roundtrip is untouched; it needs no binary). Pass a cross-repo token as GH_TOKEN to the three fetch:opencode step defs (boot-smoke's single step covers both matrix legs). Mirrors #80, adapted to this branch's split vsix-gate step. Requires repo secret OPENCODE_FETCH_TOKEN: a fine-grained PAT scoped to harmoniqs/opencode with Contents:Read (validated end-to-end — downloads both assets and the bytes match opencode.lock.json's SHA256 gate). Stays red until the secret exists; green once it's set, no further push needed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(extension): bump opencode.lock to v1.17.3-amicode.2 Pin the vendored opencode binary to the new fork release cut from rchari/amicode-fixes (opencode PR #1): Gemini tool-schema fix, /amicode run-cards + profile endpoints, one-spine run truth, multi-drive pulse fix, save-file bridge. darwin-arm64 + linux-x64 sha256 updated. vsix build verified locally (linux-x64 fetch + sha match + vsce package -> amicode.vsix). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Kate <katebonner277@gmail.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Jack Champagne <jackchampagne.r@gmail.com> Co-authored-by: Jack Champagne <jack@harmoniqs.co>
Closes #57 (Phase 1.2, plan §3). β built the single-run slice; this is the multi-run evolution.
What changes
Discovery:
runs/indexreplaces thelatestsymlink as the multi-run source of truth. The manager tails the append-only index (amico-run'sappendIndexTSV) with the sameLogTailerused forrun.log;latestkeeps being written (frozen contract) but is no longer followed. The β failure this fixes: a second concurrent solve swunglatestand yanked tracking off the first run mid-flight — now every run without aFINISHEDgets its own pipeline (replay → run-dir watch → log tail) and is tracked to completion.Fan-out & selection
RunRegistry(state); the selected run drives the single-run Inspector + StatusBar. Selection auto-follows the newest started run (β latest-follow parity);selectRun(runId)is 1.3's seam for per-run views.FINISHED(neverresult.tomlpresence) — pinned by test.PulseStream/SinkDedup(Render the live pulse client-side in the Run Inspector (pulseplot v1) #66 routing preserved: live tail is the meta carrier; replay-seeded meta arms the stream; selection replays are newest-record-only viaingestRunDir).Scheduler seam (1.1, #56/#68)
attachSchedulerconsumes the lifecycle stream —startedregisters + selects the run immediately (beats the index tail; also the only path for a non-default runsRoot). Typed structurally (SchedulerLike= #68'sonEventsurface), so this PR is independent of #68's merge order and wires up unchanged once it lands.Demo replay
A run that is FINISHED at discovery registers quietly (β's idle-at-launch parity) — so
replayDemonow renders via explicit selection (pokeDiscovery()+selectRun()), with promote suppressed. Pinned by test.Files
run_registry.ts(new, pure): index grammar + registry — vscode-free, unit-tested.log_tailer.ts(new):LogTailerextracted verbatim fromfile_watcher.ts.runs_manager.ts(new): the engine.file_watcher.tsdeleted (superseded); its state-machine tests ported toruns_manager.test.ts, all four preserved (idle-on-finished, warming→completion, Render the live pulse client-side in the Run Inspector (pulseplot v1) #66 tail routing, replay-seeded meta).extension.ts: swap wiring;replayDemoexplicit-selection.Tests
15 new (6 registry + 9 manager: the 4 ported + concurrent-runs/background-completion+promote, re-select-no-promote-re-pop, missing-dir tolerance, Scheduler seam, demo path). Repo green: extension 103 · amico-run 47 · schema 34; typecheck + build clean.
Feeds
1.3 (#58) keys per-run inspector panes on
selectRun/the registry; 1.4a (#61) exercises Scheduler → RunsManager → Inspector end-to-end.🤖 Generated with Claude Code