Skip to content

cloud: make the Piccolissimo + Altissimo tier actually mean Altissimo, in the cloud, automatically - #259

Open
Rchari1 wants to merge 10 commits into
mainfrom
rchari/cloud-spotless
Open

cloud: make the Piccolissimo + Altissimo tier actually mean Altissimo, in the cloud, automatically#259
Rchari1 wants to merge 10 commits into
mainfrom
rchari/cloud-spotless

Conversation

@Rchari1

@Rchari1 Rchari1 commented Aug 5, 2026

Copy link
Copy Markdown
Member

Selecting Piccolissimo + Altissimo is supposed to mean one thing: every solve runs Altissimo, in Harmoniqs Cloud, without anyone deciding anything. We wired that quickly for the hackathon and four gaps were left between the promise and the code. This closes them and adds the tests that would have caught each one.

1. Nothing actually selected Altissimo

The solve template shipped SOLVER = :ipopt. Choosing the paid cloud tier changed the entitlement, the routing, and the UI — but not the backend. A "High-Performance" run could solve on IPOPT and be indistinguishable in the artifacts: same run dir, same frames, same converged badge.

The template is now staged at session prep with SOLVER substituted from the solver mode, so the backend follows the selected tier instead of being an authoring decision the agent can get wrong. The placeholder lives inside a string literal (SOLVER = let s = "{{SOLVER}}" …), so an unstaged template still parses and degrades to a local IPOPT solve rather than dying on a syntax error.

2. Altissimo produced no telemetry at all on the shipped stack

This is why cloud runs came back with an empty Run Inspector. Two independent causes, both silent:

  • Piccolissimo 0.2.0's solve!(prob, ::AltissimoOptions; kwargs...) forwards a hardcoded whitelist to Altissimo.optimize!. callback is not on it, so the callback we hang telemetry on never fires.
  • AltissimoOptions.verbose defaults to false, so the iteration table never printed either.

Neither path existed, so a run reported iterations = 0 and looked like it had converged. Now: verbose = true (load-bearing, not chatter — it is plumbed to verbose_outer/verbose_inner), plus a stdout bridge that translates Altissimo's own table rows into AMICODE_ITER. Numbering is sequential over rows because the table's iter column is · for every inner step and only becomes an integer on the final outer iteration — trusting that column yields a single point at the end.

Verified locally: 154 streamed iterations, objective 48 → 7.8e-3, with the callback never firing once. Where a newer Piccolissimo does forward the callback it supersedes the bridge (it also carries frames), so there is exactly one numbering scheme per run.

Also still in: the DirectTrajOpt 0.9.7 _solve dispatch bridge, without which every Altissimo solve is a silent no-op that reports success.

3. The default launch failed instead of going to the cloud

A plain amico-run script.jl under the HP tier exited 64 with a message telling the agent to retry with --executor remote. That round-trip surfaced to users as a failed run.

It is now promoted to remote — selecting the cloud tier is the routing decision. An explicit --executor local is still refused, because silently inverting a flag someone typed is worse than an error. And a promotion with no cloud connection refuses with "Connect Harmoniqs Cloud in the Connections panel" rather than failing deep in RemoteExecutor with a ~/.amico/cloud.json path the user has never heard of.

4. The UI never said where a run ran

A cloud run and a local run produced an identical Inspector pane, so "did that actually use the cloud I'm paying for?" was unanswerable from the UI. The topbar now reads <runId> · Harmoniqs Cloud for remote runs.

Keyed on remote.json (written by RemoteExecutor and nothing else) rather than a new run.toml field — run.schema.json is additionalProperties: false, which is why the remote executor put task_id in that sidecar to begin with.

Guidance corrections

Four claims in the agent guidance no longer matched the code and are now accurate: the local-launch refusal (it promotes), the "AMICODE_ITER not yet available on the cloud bundle" note (it is), and the quoted SOLVER = :altissimo line (the guidance now says the line arrives already set, so the agent doesn't "fix" it).

Two real limits are now stated plainly instead of being papered over: the cooperative Stop needs the solver callback the cloud bundle's Piccolissimo does not forward, and re-rollout verification is skipped for cloud runs.

Tests

  • Promotion proven by a --julia that writes a sentinel file if it ever runs — it doesn't, and AMICODE_FINISHED comes from FakeCloud.
  • Explicit --executor local still exits 64; the no---spec path is covered (the gate never runs there).
  • Disconnected promotion refuses and names the Connections panel.
  • Template staging: hp → altissimo, piccolo → ipopt, agent and grants point at the same staged file, and the shipped template is asserted to keep its placeholder inside a string.
  • The cloud location label is proven end-to-end in remote_statemachine.test.ts: a real RemoteExecutor.submit writes the remote.json the label reads.

941 amico-run tests and the extension suite pass. The two slow/*_e2e tests need a live model provider and fail identically on main.

🤖 Generated with Claude Code

Rchari1 and others added 10 commits August 4, 2026 20:56
Selecting Piccolissimo + Altissimo is meant to mean "every solve runs
Altissimo, in Harmoniqs Cloud". Four gaps between that promise and the code:

1. The solve template defaulted to `SOLVER = :ipopt`, so nothing actually
   selected the Altissimo backend — a paid HP run could quietly solve on IPOPT
   and look identical in the artifacts. The template is now STAGED at session
   prep with SOLVER substituted from the solver mode, so the backend follows
   the selected tier instead of being an authoring decision the agent can get
   wrong. The placeholder lives inside a string, so an unstaged template still
   parses and degrades to a local IPOPT solve rather than a syntax error.

2. Altissimo produced NO telemetry on the shipped stack. Piccolissimo 0.2.0
   forwards a hardcoded kwarg whitelist to Altissimo.optimize! and drops
   `callback`, and `AltissimoOptions.verbose` defaults false — so neither the
   callback nor the iteration table existed, and a cloud solve reported
   iterations = 0 with an empty Run Inspector. Now: verbose = true, plus a
   stdout bridge that translates Altissimo's own table rows into AMICODE_ITER.
   Verified locally at 154 streamed iterations (objective 48 -> 7.8e-3) with
   the callback never firing. Where a newer Piccolissimo DOES forward the
   callback it supersedes the bridge, so there is one numbering scheme per run.

3. A defaulted `amico-run script.jl` under the HP tier exited 64 and relied on
   the agent reading the refusal and retrying with --executor remote — users
   saw that round-trip as a failed run. It is now PROMOTED to remote. An
   explicit --executor local is still refused, and a promotion with no cloud
   connection refuses with the Connections panel instead of failing deep in
   RemoteExecutor with a cloud.json path.

4. Nothing in the UI said where a run executed: a cloud run and a local run
   produced an identical Inspector pane. The topbar now reads
   "<runId> · Harmoniqs Cloud" for remote runs, keyed on remote.json (written
   only by RemoteExecutor) rather than a new run.toml field — run.schema.json
   is additionalProperties:false.

Also corrects four agent-guidance claims that no longer matched the code (the
local-launch refusal, the "AMICODE_ITER not available on cloud" note, and the
quoted SOLVER line), and states the two real remaining limits: cooperative Stop
needs the callback the cloud bundle does not forward, and re-rollout
verification is skipped for cloud runs.

941 amico-run tests and the extension suite pass; the two live-model e2e
tests are unrelated (they need a working provider).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The previous commit put the cloud fact in the Run Inspector's run label —
which is styled `mono small dim`, i.e. the least prominent element on the bar.
The information was there and nobody would see it. Two changes:

1. A real badge, not appended text. The pill atom gains a `cloud` state: a
   FILLED brand-lemon swatch with a ☁ glyph, which is how this product says
   "special". It obeys the rule in brand.css — yellow is a FILL, never an ink —
   so the label is --color-on-accent (black, 18.7:1) and the edge is the
   theme-solved hairline, because the lemon cannot bound itself against white
   (1.1:1). Rendered and eyeballed in both themes.

   The badge is hidden unless the run is remote. No "LOCAL" counterpart on
   purpose: a badge present on every run is chrome users learn to ignore, and
   this one needs to still mean something on the run that costs money.

2. The status bar says it too — the one surface visible with the Inspector
   closed, and "am I burning cloud credits right now?" belongs to the question
   it already answers. A queued cloud run now says "queued" rather than
   "warming": warming describes Julia precompiling locally, which is exactly
   what does not happen on this tier, so the local word misdescribes where the
   wait is. The location shows only while the run is in flight — once it is
   done, its outcome is the story.

Both surfaces derive location from remote.json via one seam. The six
status-bar call sites now route through setRunState(), which stamps it in one
place: six hand-maintained flags is how five get set and the sixth quietly
does not. Cached permanently rather than on a TTL (unlike liveStatus, which it
sits beside) because remote.json is written once at submit and never removed.

Tests: the badge is asserted through the real DOM the user sees — appears on
the run it was sent for, not on a sibling local run, never lights on a
malformed message, and does not disturb the status pill beside it. Plus the
brand-rule contract (no gold ink), glyph-survives-dot:false source ordering,
and per-pane buffering so a panel reopen keeps the badge. `runLocationLabel`
is deleted rather than left as a dead export.

One test-helper fix falls out: `pillText` matched the first `.pill`, which is
now the badge. It targets `.pill[role=status]` — the status pill's own semantic
hook, the same one a screen reader uses.

886 extension tests pass (86 files).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… tier

Field report: a solve ran locally with Piccolissimo + Altissimo selected.
Reproduced from the real machine state, and the promotion logic was not at
fault — the signal it read was:

  issimo entitlement  GRANTED        cloud.json          present
  connection          connected      solver-mode.json    piccolo (Jul 28)  <- only this was read

solver-mode.json only changes when something posts a `status:"switching"`
request, so one dropped write leaves it stale indefinitely. Every cloud
decision keyed off that single file, so the whole paid tier silently reverted:
no HP guidance, a template staged for IPOPT, no promotion, and a local solve
that looked completely normal.

The fix is to stop trusting one mutable file:

- amico-run's hpTierSelected() takes hp from EITHER solver-mode.json or the
  entitlement-resolved allowlist in authoring.json (Piccolissimo present ⇒
  issimo granted). It already read that file for the gate, so this is a new
  reading of data in hand, not a new dependency. Self-cleaning: switching to
  Piccolo revokes the entitlement, so both signals agree again.

- The extension's effectiveSolverMode() does the same for the three decisions
  that were wrong (routing guidance, template SOLVER, HP authoring guidance).

- reconcileSolverMode() heals the file itself at activation, because it is a
  SHARED contract: the app's solver toggle renders from it, so a stale file
  showed "Piccolo" selected while the user was on the paid tier. Healing once
  at the source beats teaching every reader the same OR. It never touches a
  switch in flight (the watcher owns that write).

OR, not AND, deliberately, and asymmetric: `hp` is sticky. Of the two ways to
be wrong, silently giving someone a local IPOPT solve on the tier they are
paying for is far worse than telling a revoked user to reconnect — the latter
is visible and actionable. Nothing is stranded, because switching to Piccolo
writes both halves.

Also closes a live-cloud hazard this created. The allowlist signal meant any
test launching without --executor read the DEVELOPER's real authoring.json,
promoted, and submitted a billed staging job: estimate.test.ts sat 13.5
minutes polling the real cloud. The guard belongs in test/setup.ts next to the
existing no-billed-model-calls and no-real-ledger guards — same reasoning,
since the risk is in the test someone writes next — and it fails closed the
same way, via paths that cannot exist. Side effect: the suite dropped from
655s to 36s, because tests are no longer waiting on a real endpoint.

944 amico-run tests and 896 extension tests pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…nvented API

Third cloud solve, third hand-authored script, zero uses of the vetted
template. The latest (task 975a7c07, exit 1) invented API wholesale:

  CubicSplinePulse(; T=, n_knots=, n_drives=, bounds=)  every real method takes
                                                        POSITIONAL args, so an
                                                        all-keyword call matches
                                                        no method
  CallbackLogger(qcp)                                   not defined in Piccolo
                                                        or Piccolissimo
  get_fidelity(qcp)                                     not defined either

It died with a MethodError at LOAD time, before any optimization — after the
user had already paid the full queue and instance-boot wait. Verified by
running the script locally and by checking each symbol with isdefined against
both packages.

Naming the specific invented symbols is what made the earlier `using Piccolo`
guidance stick (#225), so the same treatment here rather than another abstract
"follow the template" line.

Worth being explicit about what this does NOT fix: guidance is not enforcement.
The durable version is a preflight that resolves the script's symbols against
the entitled packages before submitting, which needs the Julia stack — not
available on a cloud-only machine, which is the whole point of the tier. In the
meantime aws-infra#230 makes the failure VISIBLE: with the runner's output
redirected to run.log, a MethodError reaches the user's run dir instead of
dying invisibly in the SSM stream, which is why this one presented as a bare
"failed, exit 1".

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Report: "if I say run a piccolissimo altissimo solve it might use piccolo
because the vetted template uses piccolo." Correct, and there were three
reasons, compounding:

  templates/solve_template.jl     Piccolo + IPOPT      the ONLY vetted registry
                                                       entry, so tier resolution
                                                       can return nothing else
  templates/solve_template_hp.jl  Piccolissimo + IPOPT never registered at all —
                                                       and despite the name it
                                                       called solve!(qcp;
                                                       max_iter, print_level=1),
                                                       with zero AltissimoOptions
  scores/…/templates/solve.jl     either backend       the only one that really
                                                       runs Altissimo (dispatch
                                                       bridge, verbose table
                                                       telemetry, run.log emit,
                                                       honest-result guard)

So even picking the "HP template" got IPOPT. Two files claiming to be the HP
path, neither doing it, is what made the behaviour look nondeterministic.

Consolidated to the score template, staged with SOLVER substituted from the
effective mode. The mode branch now lives in ONE line inside one file instead of
selecting between two files that drift — which is exactly how solve_template_hp.jl
came to be an IPOPT script with an HP name. solve_template_hp.jl is deleted.

Not a new pair of buttons, deliberately. The axes are not independent: Altissimo
is the cloud GPU solver for the issimo stack, so Piccolo + Altissimo is not a
supported combination and Piccolissimo + IPOPT is precisely the accidental state
above. Four buttons would offer two combinations we cannot honour. The existing
single toggle is the right control; it just was not reaching the decision.

Safe for the free tier: the template imports Piccolissimo only on the altissimo
branch via `@eval using Piccolissimo`, and the import scanner is line-anchored
(`^\s*(using|import)`), so it does not see a guarded import — a piccolo-mode
script needs no entitled package. Verified against the scanner's regex rather
than assumed.

Still open, and worth its own change: `amico-run resolve` returns the registry
path, which is the bundled file with `{{SOLVER}}` unsubstituted (it degrades to
ipopt by design). Registering the consolidated template for tier-1 needs the
resolve output to point at the STAGED copy, which means threading it through
authoring.json. The session path — what AGENTS.md hands the agent — is correct as
of this commit.

902 extension tests pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Found by running an Altissimo solve against Piccolissimo 0.3.1 (which, unlike
0.2.0, DOES forward `callback` into Altissimo.optimize!). Both telemetry channels
emitted and the iteration numbers collided:

  records 1..51   the stdout table bridge, counting INNER steps
  then      1..4  the callback, counting OUTER iterations
  sequence: 51 -> 1 -> 1 -> 2 -> 2 ...

The Inspector plots that as a sawtooth, which reads as a diverging solve.

The `alt_cb_fired` guard was reactive and therefore always too late: alt_cb fires
at the END of an outer iteration, so the bridge has already translated every inner
row of outer #1 before the guard can trip. The two scales cannot be reconciled
(dense inner steps vs sparse outer iterations), so exactly one channel may emit
and the choice has to be made BEFORE the solve.

CB_FORWARDED decides it from the installed Piccolissimo version — 0.3.x forwards,
0.2.x drops it via a hardcoded kwarg whitelist — and prefers the callback wherever
it exists, because that channel carries pulse frames as well as numbers. A version
gate rather than introspection: kwarg forwarding is not detectable from the method
object. alt_cb_fired stays as a belt for a backport that forwards on an older
version.

Verified on 0.3.1: iterations [1, 2, 3, 4], strictly increasing, one scheme, plus
4 AMICODE_PULSE records and the meta line — the frame channel that did not exist
on 0.2.0, where alt_cb never ran at all.

The closing NOTE now states which channel carried the run, because the two count
different things and a reader comparing runs needs to know which scale they are
looking at.

Unchanged and still load-bearing: the DirectTrajOpt 0.9.7 dispatch bridge.
Piccolissimo main still defines only the OLD extension point (0 `_solve`, 2
`Solvers.solve!`), so without the bridge an Altissimo solve on 0.3.1 is still a
silent no-op reporting success.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ith real output

Diagnosis of three cloud failures in two days. Every one was a script that did not
come from the template, and that single fact explains BOTH symptoms — the failures
and the empty Run Inspector:

  x-gate-transmon-56    authored from memory: CubicSplinePulse kwargs, CallbackLogger,
                        get_fidelity — none exist
  x-gate-transmon-hpc   REUSED a Jul-28 script verbatim; ZeroOrderPulse into
                        SplinePulseProblem, which Piccolo 1.19 rejects outright
  x-gate-transmon-hpc-3 authored from memory: CubicSplinePulseProblem (undefined),
                        AltissimoOptions(intermediate_callback=…) (no such field)

Each reproduced locally and each symbol checked with isdefined/fieldnames, not
assumed. Note the middle one: ~/.amico/problems/<slug>/solve.jl persists across
sessions and the agent reuses it, so template improvements never reach an existing
slug.

A correction to my own earlier claim: cloud streaming does NOT need aws-infra#230.
The template's emit() appends to run.log whenever TASK_ID is set, and the runner
sets TASK_ID — so a template-derived script streams today. All three of these wrote
run.log zero times, which is the entire reason nothing reached the Inspector. The
evidence is in the run itself: /frames reports iter: 60 for a solve whose /stats was
empty. It ran 60 iterations and wrote 60 frames; only the numbers were lost. #230
remains worth deploying — it captures stderr, which is why these failures presented
as a bare "failed, exit 1" — but it is not the streaming blocker.

So: a cloud telemetry preflight. A remote launch whose script never writes run.log
is refused in about a second, naming the cause and the fix, instead of costing ten
minutes and naming nothing. A refusal rather than a warning because it is a
certainty, not a risk, and stderr warnings have not changed the behaviour. Gated on
hasCloudConfig() so the missing-connection error keeps precedence — it is the more
fundamental problem. Local runs are untouched: LocalExecutor pipes the child's
stdout into run.log itself (local_executor.ts:293), which is why local has always
worked and why this must be remote-only.

Verification, and the reason it is worth trusting this time: FakeCloud now derives
BOTH /stats and /pulse from ONE run.log through the deployed lambda's own transform
(statsFromRunLog is a line-for-line port), so tests see the records the live service
actually returns — {raw: "iter=7 f=…"}, key=value, never pre-parsed JSON. Seeding
what the CLIENT expects is how three drift bugs shipped green. The end-to-end test
drives a run.log CAPTURED from a real Altissimo solve on Piccolissimo 0.3.1
(test/fixtures/altissimo_cloud_run.log) and asserts all four iterations arrive with
the real objectives, plus the pulse meta.

Chain status: template+TASK_ID → run.log (verified, real solve); S3 sync (proven in
production — frames arrive this way); /stats grep (verified against the live lambda);
client {raw} parse → local run.log (verified); tailer → Inspector (verified with
captured output). Every link but the S3 sync exercised locally.

948 amico-run + 903 extension tests pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The preflight worked — the agent copied the template byte-for-byte (401 lines,
diff-clean against the staged copy) and the telemetry chain came alive: task 582a
returned AMICODE_PULSE_META through /pulse into the local run.log, the first live
evidence of that path working.

The solve still failed, and I could not say why. julia's stderr goes to the SSM
command stream, which no API exposes, so a cloud failure arrives as `failed, exit 1`
and nothing else. Since it reached AMICODE_PULSE_META, it died AFTER problem
construction — and that is all the run could tell me.

Two additions, both inside the template and neither needing an infra change:

1. report_and_rethrow — any exception from the solve onward is emitted as
   AMICODE_ERROR lines (message + 12 stack frames) via emit(), which writes
   run.log, which the sidecar syncs and the poller greps. Rethrown, never
   swallowed: the run must still fail. A solve reporting success after an exception
   is the silent-no-op class this template already guards against.

2. AMICODE_ENV — a stack fingerprint emitted before the solve:
     piccolo=1.19.0 piccolissimo=0.3.1 dto=0.9.7 callback_forwarded=true
     julia=1.12.3 solver=altissimo
   A cloud run executes against the runner's BAKED bundle, not the caller's
   environment, so "which Piccolissimo was that?" is unanswerable after the fact —
   and it is the first question every cloud failure raises. It also records which
   telemetry channel is live, since the two count different things (outer
   iterations vs inner steps).

Both verified by running, not by reading: the reporter writes the stacktrace and
still exits 1, and the fingerprint line above is from a real solve. The first
placement of the fingerprint referenced CB_FORWARDED before it was defined and
threw UndefVarError at load — caught only because I ran it, which is the argument
for running it.

What this does NOT do is fix the underlying cloud failure. It makes the next one
self-diagnosing instead of silent, which is the prerequisite for fixing it: the
cloud image's stack is still unknown to me, and the fingerprint is how we learn it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…s the toggle

The last way an HP run could still land on Piccolo/IPOPT. AGENTS.md documents the
vetted path as "copy template_path", and template_path was always the BUNDLED
registry file — whose {{SOLVER}} placeholder is unsubstituted and, by design,
degrades to :ipopt. Meanwhile the AGENTS.md preamble pointed at the STAGED copy,
with SOLVER already resolved. Two instructions, two different files, and the
documented one was wrong.

So session prep now records the staged path in authoring.json (the seam amico-run
already reads for the allowlist and asset paths) and `resolve` returns it as
template_path. The two instructions now name the same file.

Falls back to the registry path when nothing was staged (a bare dev invocation) or
when staged_template points at a file that no longer exists — a stale authoring.json
must not send the agent to a missing path, which would be a worse failure than the
one being fixed.

Verified against the real machine state, not just in tests: resolve --platform
transmon --kind gate_synthesis --size 1 now returns the staged solve.jl whose SOLVER
line reads "altissimo". Before this it returned templates/solve_template.jl, which
is Piccolo + IPOPT.

951 amico-run + 903 extension tests pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
First live cloud run to stream iterations (task c584: 60 records, strictly
monotonic 1→60, inf_pr 78.6 → 1.24e-05) exposed a defect in my own regex. ALT_ROW's
`[-+0-9.eE]+` also matches the bare "-" Altissimo prints as a placeholder in
columns that do not apply to a row, so the first record reached the Inspector as
`AMICODE_ITER iter=1 f=-` — a non-numeric objective at the head of the curve.

Captures are now tryparse'd as Float64 and the row is skipped unless all three are
real numbers.

This does NOT fully explain that run's second record (`f=1`), which is a plausible
number in the wrong column — evidence that the cloud's baked Altissimo prints a
different table layout than the local one, and that scraping a table is inherently
version-coupled in a way the callback channel is not. Worth knowing before trusting
the first points of a cloud curve; the fix is the runner getting a Piccolissimo that
forwards `callback` (0.3.x), which retires the bridge entirely.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant