feat(hp): cloud-only enforcement + cloud iters/frames reach the Run Inspector - #219

Merged
jack-champagne merged 7 commits into
mainfrom
rchari/hp-cloud-only
Jul 29, 2026
Merged

feat(hp): cloud-only enforcement + cloud iters/frames reach the Run Inspector#219
jack-champagne merged 7 commits into
mainfrom
rchari/hp-cloud-only

Conversation

@Rchari1

@Rchari1Rchari1 commented Jul 28, 2026

Copy link
Copy Markdown
Member

Six commits, one thread: make the paid tier actually run in the cloud, and make what it produces actually reach the Run Inspector. 18 files, +673/−116.


1. Piccolissimo + Altissimo is cloud-only, and now enforced

Selecting the HP tier was supposed to mean "runs in Harmoniqs Cloud." In practice the agent kept dispatching it locally, where the laptop precompiled the whole HP stack (IPOPT included) until amico-run's process-group timeout SIGTERMed julia mid-precompile.

The API-key prompt and entitlement flip were already built and work (amicode#200/#167) — untouched here. Three other things allowed local dispatch:

  1. Nothing refused a local HP launch. The tier="hpc" gate is solid but only fires on a spec that saystier="hpc", and runGate only runs for --spec launches. A bare amico-run script.jl never reached it.
  2. The entitlement that unlocks the tier unlocked the failure — HP grants issimo, so the import scan admitted a local using Piccolissimo.
  3. The agent got contradictory orderssolverModeSection() said "launch in the cloud" while the routing section beside it said "routing is PER-SOLVE and EXPLICIT, default to local." Given a cloud-only tier and an instruction to default local, it defaulted local.

Fix, in the two-layer shape the tier already uses:

  • Enforcementlaunch.ts refuses --executor local while HP is selected, at the one choke point every run passes through, before the gate, so it covers spec and no-spec runs alike. Reads solver mode as status only, never a token, and fails safe to piccolo on a missing or corrupt file: this value can only ever refuse a run, so a bad read must never invent hp and block ordinary free-tier work.
  • Guidance — the routing section states the cloud-only contract instead of asking a routing question, and AGENTS.md step 5 makes the injected section authoritative over its local default. Piccolo sessions stay byte-identical (the section is still "" unless mode is hpand the cloud is connected).

The estimate keeps its reporting role and loses its deciding role: an estimate that fits in local RAM does not make an HP solve local.

Service is named Harmoniqs Cloud in every user-visible string. Wire id stays company-compute — renaming breaks the credential route, status cache, and CONNECTION_IDS for no visible gain.


2. Cloud iters and frames reach the Run Inspector

The client chain was fully built — poll the cloud, synthesize AMICODE_ITER into run.log, write frames, touch mtime for the stall logic. It read the wrong fields:

client readservice returns
statsbody.iters{task_id, **stats**[], submitter}
framespng_base64{task_id, iter, key, **url**, submitter}

Both reads sit in best-effort try/catch ("stats are advisory"), so it failed silentlyundefined ?? [] iterated zero times and run.log stayed 0 bytes. Confirmed on task 419a57e6: 60 IPOPT iterations and 11 frames banked to S3, empty inspector.

Why nobody caught it:fake_cloud.ts served the shapes the client read, not the shapes the service returns. Every test passed against a fake that agreed with the bug. So the fake is corrected first, and two tests pin the live payloads by exact key set — including that the presigned URL is fetchable with no auth header, because the signature is the credential.

Frame filenames also went 3-digit → 5-digit: iter_00007.png is what both the S3 layout and the local julia solve write. The old name matched neither, so cloud and local frames landed under two schemes in one run dir.

A third drift, found while proving the above: the poller only json.loads an AMICODE_ITER payload starting with {, so template-emitted key=value lines come back as {raw: "iter=7 f=…"}. The client keyed on it.iter and skipped every one as NaN — invisible because the smoke test seeds JSON. Now reconstructs from either shape and drops malformed records rather than emitting NaN.


3. The cloud can finally populate /stats

/solves/<id>/stats parses AMICODE_ITER out of run.log in the artifact prefix, and the runner's sidecar populates that prefix with aws s3 sync . — whatever is in the solve cwd. But julia's stdout on the runner goes to the SSM command stream, so run.log was never written there: nothing to sync, stats: [], empty inspector. The template's own comment ("AMICODE_PULSE lines on stdout (→ run.log)") was true locally and false in the cloud.

Telemetry now routes through emit(), which also appends to run.logonly when TASK_ID is set (the runner exports it). Locally amico-run's executor already writes run.log from stdout, so appending unconditionally would double every line and the inspector would count each iteration twice.

No terraform change and no AMI rebake: the solve script is uploaded per submission (<task_id>-solve.jl), so this ships immediately.


4. Altissimo streams like IPOPT

Both channels were bolted to IPOPT — frames off IpoptOptions.intermediate_callback, the iter line off a callback reading IpoptOptimizerState. Altissimo has neither; its only hook is callback on optimize!, arriving as (x, info). So an Altissimo solve lost the frames too, leaving the inspector completely dark rather than merely numberless.

SOLVER = :ipopt | :altissimo re-hangs both channels. xis the primal, so pulse_emit's solver-agnostic (primal, iter) contract takes it unchanged — same frames, same AMICODE_PULSE, same cooperative STOP (returning false stops an Altissimo solve exactly as it stops an Ipopt one). inf_pr/inf_du come from the callback tuple when present (Altissimo#414, merged) and are otherwise derived from eq_viol/ineq_viol/kkt_error, so it works on an older Altissimo instead of emitting NaN.

Two traps verified against Piccolissimo main and handled:

  • the qcp-level solve! forwards kwargs... to the prob-level method, which takes callback explicitly — so the wiring does reach optimize!;
  • but max_iter lands in that kwargs... and is silently dropped, so the budget now rides AltissimoOptions(max_outer_iter = max_iter). Left alone it would have quietly run Altissimo's default 20 outer iterations while the FILL-IN block said 60.

The guidance names SOLVER = :altissimo as the one-line switch and forbids hand-rolling the solve call, stating each trap so the instruction carries its own justification.


Testing

  • amico-run: 938 passed, 12 skipped.
  • extension: 778 passed, 3 skipped (the full CI set, --exclude '**/slow/**').
  • tsc --noEmit clean in both packages.
  • Live, against the real staging service: frames fetched from a presigned URL and landed locally with 5-digit names (task 0fccbbf9); template runs green end to end (fidelity 0.99997, 60 iterations); local run writes norun.log (no duplication), and with TASK_ID set run.log appears with iter lines in exactly the format the poller parses.
  • One incidental fix that matters for CI: the CLI tests were inheriting the developer's~/.amico/amicode/solver-mode.json. On an hp machine nine local-solve tests failed — on correct behaviour. They now spawn with a hermetic ops dir.

CI caught the one consumer I missed on the frame rename (the Δ9 remote state-machine test); the assertion above it — run.log contains iter=7 — passed, which is independent confirmation in a second harness that the stats fix delivers iters end to end.


What this does NOT fix

  • Altissimo on the cloud. The runner AMI bakes a Piccolissimo/Altissimo predating callback support, so it emits nothing there until Altissimo#414 is released and the image rebuilt. The template is ready for that moment.
  • The Altissimo code path has never been executed. It is written against the verified contract and parses, but the local checkouts here are 287/324 commits behind and cannot run it. Worth exercising on a current env before anyone demos it.
  • Cold start. ~9–10 min from submit to first frame, for ~55s of compute. Real, and unaddressed here.
  • Existing agent-authored scripts in ~/.amico/problems/ predate emit(), so they still show frames and no numbers until re-authored from the updated template.

solverModeSection is exported to make the guidance testable (matching its already-exported sibling routingSection). The same one-line export is on #225 — identical change, so either merge order is fine.

Reviewer note

This narrows behaviour: a local Piccolissimo run that "worked" before — slowly, usually fatally — is now refused outright. That is the intent of a cloud-only paid tier, but it is a real change for anyone relying on the local path, and the way out is one click on the solver control.

…forced
Selecting Piccolissimo + Altissimo was supposed to mean "this solve runs in
Harmoniqs Cloud". In practice the agent kept dispatching it LOCALLY: the laptop
precompiled the whole HP stack (IPOPT included) until amico-run's process-group
timeout SIGTERMed Julia mid-precompile.
Three things allowed that, none of them the pieces people assumed were missing
(the API-key prompt and the entitlement flip were already built and work):
1. Nothing refused a local HP launch. The gate's tier=hpc rules are solid, but
they only fire on a spec that SAYS tier="hpc" — and runGate only runs for
--spec launches at all. A bare `amico-run script.jl` never reached them.
2. Selecting HP grants the `issimo` entitlement, so the import scan happily
admits a local `using Piccolissimo`. The entitlement that unlocks the tier
was also unlocking the failure mode.
3. The agent was told two contradictory things. solverModeSection() said "launch
HP solves in the cloud" while the routing section beside it said "routing is
PER-SOLVE and EXPLICIT, you confirm where EVERY solve runs, default local" —
and the base AGENTS.md step 5 said the same. Given a cloud-only tier and an
instruction to default local, it defaulted local.
The fix, in the same two-layer shape the tier already uses:
- ENFORCEMENT: amico-run refuses `--executor local` while HP is selected, at the
one choke point every run passes through (launch.ts, before the gate), so it
covers spec and no-spec runs alike. Reads the extension's solver-mode.json —
status only, never a token — and fails SAFE to piccolo on an absent or corrupt
file, so a fresh install and every free-tier local run behave exactly as now.
- GUIDANCE: the routing section now states the cloud-only contract instead of
asking a routing question, and AGENTS.md step 5 makes the injected section
authoritative over its local default. Piccolo sessions are untouched: the
section is still "" unless mode is hp AND the cloud is connected.
The estimate keeps its reporting role and loses its deciding role — an estimate
that fits in local RAM does not make an HP solve local.
Also names the service "Harmoniqs Cloud" everywhere a user can see it (gate
refusals, routing prose, AGENTS.md, the HP row's tooltips) instead of the
internal "company compute" / bare "the cloud". The wire id stays
`company-compute` — renaming it would break the credential route, the status
cache, and the fork's CONNECTION_IDS for no user-visible gain.
Tests: 9 new (the reader's fail-safe directions; the refusal, including on the
no-spec path and that it leaves no run dir; that a REMOTE HP launch still
completes, against FakeCloud) + the routing/AGENTS contract updated to pin the
new copy, with an explicit regression test that the per-solve question does not
come back. amico-run 934 passed, extension suite green apart from two live-model
E2Es (interview_e2e, scores_e2e) that fail identically with this change stashed —
pre-existing, tracked separately.
Incidental but load-bearing: the CLI tests were inheriting the DEVELOPER's
~/.amico/amicode/solver-mode.json, so on an hp machine nine local-solve tests
failed on correct behaviour. They now spawn with a hermetic ops dir.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Rchari1and others added 4 commits July 28, 2026 20:39
A cloud solve produced an empty Run Inspector. The client-side chain was fully
built — RemoteExecutor polls the cloud, synthesizes AMICODE_ITER lines into
run.log, writes frames, and touches run.log's mtime for the stall logic — but it
read the WRONG FIELDS, so it dropped everything:
stats client read `body.iters` · service returns `{task_id, stats[], submitter}`
frames client read `png_base64` · service returns `{task_id, iter, key, url, submitter}`
Both reads sit inside best-effort try/catch blocks ("stats are advisory"), so
the failure was completely silent: `undefined ?? []` iterated zero times, run.log
stayed 0 bytes, and the Inspector had nothing to tail. Confirmed against task
419a57e6 on staging, which ran 60 IPOPT iterations and banked 11 frames to S3
while the local run.log stayed empty.
The reason nobody caught it: fake_cloud.ts served the shapes the CLIENT read,
not the shapes the SERVICE returns. Every test passed against a fake that agreed
with the bug. That is the real defect here, so the fake is corrected first and
two tests now pin the live payloads by exact key set — a fake that mirrors the
client proves nothing.
- stats: read `stats`, falling back to `iters` so an older runner still works.
- frames: fetch the presigned url (no auth header — the signature IS the
credential) and keep the base64 lane for older runners. FakeCloud now serves
artifact bytes from a route placed BEFORE its auth guard, matching S3.
- frame filenames go 3-digit → 5-digit: iter_00007.png is what both the S3 layout
and the local Julia solve write, so cloud and local frames no longer land under
two different schemes in one run dir.
Also: the HP solver-mode guidance now covers the Altissimo backend. IPOPT stays
the default because it is what streams telemetry; Altissimo is used on request,
but the agent must state the trade first — Piccolissimo's solve!(::AltissimoOptions)
does not forward a caller callback to Altissimo.optimize!, and there is no
intermediate_callback on that path, so an Altissimo run emits no frames and no
AMICODE_ITER and the Inspector stays empty until it finishes. Never switch
silently, never claim live iterations on it.
amico-run: 936 passed. extension: 62 passed in the touched suites, tsc clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CI caught the one consumer I missed when frame filenames went 3-digit → 5-digit.
The Δ9 test waited on iter_007.png and timed out; the assertion above it —
run.log contains "iter=7" — PASSED, which is independent confirmation in a second
harness that the stats-field fix delivers iters end to end.
Also corrects solver-mode guidance shipped earlier today. It asserted that
Piccolissimo's solve!(::AltissimoOptions) does not forward a caller callback —
taken from a report without checking. That is true of the local checkout (287
commits behind) but false of main, which accepts `callback` and forwards it to
Altissimo.optimize!. The guidance now says live iterations depend on the INSTALLED
version and tells the agent not to promise iterations it has not observed.
extension: 776 passed, 3 skipped (the full CI set).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two gaps stood between a cloud solve and a populated Run Inspector. Both are in
the solve template, which ships PER SUBMISSION (the client uploads
<task_id>-solve.jl), so neither needs an AMI rebake.
1. NOTHING WROTE run.log IN THE CLOUD.
/solves/<id>/stats parses AMICODE_ITER lines out of run.log in the artifact
prefix, and the runner's sidecar populates that prefix with `aws s3 sync .` —
whatever is in the solve cwd. But julia's stdout on the runner goes to the SSM
command stream, so run.log was never written there: nothing to sync, stats: [],
empty inspector. The template's own comment ("AMICODE_PULSE lines on stdout (→
run.log)") was true locally and false in the cloud.
Every AMICODE_* line now goes through emit(), which also appends to run.log —
but ONLY when TASK_ID is set (the runner exports it). Locally amico-run's
executor already writes run.log from stdout, so appending unconditionally would
double every line and the inspector would count each iteration twice.
Verified: local run writes no run.log (11 frames, result.toml, fidelity
0.99997); with TASK_ID set, run.log appears with AMICODE_ITER lines in the
exact format the poller parses.
2. THE TELEMETRY WAS BOLTED TO IPOPT.
Frames came off IpoptOptions.intermediate_callback and the iter line off a
callback reading IpoptOptimizerState. Altissimo has neither: its only hook is
`callback` on optimize! (forwarded by Piccolissimo's solve!(::AltissimoOptions))
and it arrives as (x, info). So an Altissimo solve lost the frames TOO, leaving
the inspector completely dark rather than merely numberless.
SOLVER = :ipopt | :altissimo now selects the backend and re-hangs both channels.
`x` is the primal, so pulse_emit's solver-agnostic (primal, iter) contract takes
it unchanged — same frames, same AMICODE_PULSE, same cooperative STOP.
inf_pr/inf_du come from the callback tuple when present (Altissimo#414) and are
otherwise derived from eq_viol/ineq_viol/kkt_error, so it works on an older
Altissimo too instead of emitting NaN.
And a third drift found while proving (1): the poller only json.loads an
AMICODE_ITER payload starting with "{", so template-emitted key=value lines come
back as {raw: "iter=7 f=…"}. The client keyed on it.iter and skipped every one as
NaN — invisible because the smoke test seeds JSON. It now reconstructs the line
from either shape, and drops malformed records rather than emitting NaN.
Still NOT solved by this: Altissimo on the cloud. The runner AMI bakes a
Piccolissimo/Altissimo predating callback support, so it will emit nothing there
until Altissimo#414 is released and the image rebuilt. The template is ready for
that moment and works today on IPOPT (cloud) and both backends locally.
amico-run 938 passed · extension 776 passed · template runs green end to end.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
solve!(::AltissimoOptions) forwards a HARDCODED kwarg list to
Altissimo.optimize! and swallows the rest, so `max_iter = 60` passed to solve!
never arrived: the solve quietly ran Altissimo's default 20 outer iterations
while the FILL-IN block said 60. Silent, not an error — the worst kind.
Budget now rides AltissimoOptions(max_outer_iter = max_iter). Verified against
Piccolissimo main: the qcp-level solve! forwards kwargs... to the prob-level
method, which takes `callback` explicitly, so the callback wiring does reach
optimize! — it is only the iteration budget that was being dropped.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The template gained a SOLVER flag but nothing told the agent it exists, so an
agent asked for Altissimo would hand-roll a solve call — and silently lose both
telemetry channels plus the iteration budget:
- frames come off IpoptOptions.intermediate_callback, which AltissimoOptions
has no equivalent of, so a hand-written call leaves the Run Inspector fully
dark rather than merely numberless;
- a `max_iter` passed to solve!(::AltissimoOptions) lands in kwargs... and is
dropped, so the solve quietly runs Altissimo's default 20 outer iterations;
- inf_pr/inf_du need deriving from eq_viol/ineq_viol/kkt_error on Altissimo
builds that predate #414.
The template already handles all three. The guidance now says the switch is ONE
line — `SOLVER = :altissimo` in the FILL-IN block — and states each trap, so the
instruction carries its own justification rather than reading as arbitrary.
solverModeSection is exported to make it testable, matching its already-exported
sibling routingSection. (The same one-line export is on #225; identical change,
so either merge order is fine.)
extension: 778 passed, tsc clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@Rchari1Rchari1 changed the title feat(hp): Piccolissimo + Altissimo is cloud-only, and now actually enforcedfeat(hp): cloud-only enforcement + cloud iters/frames reach the Run InspectorJul 29, 2026
# Conflicts:
#	packages/extension/src/opencode_config.ts
#	packages/extension/test/agents_md.test.ts
@jack-champagne
jack-champagneforce-pushed the rchari/hp-cloud-only branch 2 times, most recently from 54f4275 to 8aa362fCompareJuly 29, 2026 04:24
@jack-champagne
jack-champagne merged commit 199b23d into mainJul 29, 2026
10 checks passed
jeonghun-jj-lee pushed a commit that referenced this pull request Aug 21, 2026
Add WorkspaceTreeProvider — renders all workspace folders as collapsible
roots, expands recursively via vscode.workspace.fs.readDirectory(),
respects files.exclude, shows theme icons via resourceUri, opens files
on click, context menus (Copy Path, Reveal), and live-updates via
FileSystemWatcher. Replaces amicode.armonia placeholder; Catalog already
removed in #457.
package.json: rename amicode.armonia → amicode.workspace ("Workspace"),
keep runInspector, update activationEvents onView:workspace, add
view/item/context for workspace files.
Follows opencode#215 decision surface: Location.Ref.directories carries
all roots, no external-directory prompts, instructions stacked per dir.
Pairs with opencode PR #219 (engine: schema + boundary + env + instruction
multi-root + DB).
jeonghun-jj-lee added a commit that referenced this pull request Aug 22, 2026
…, #215)
* fix(ui): remove pulse catalog + CATALOG sidebar tab and widgets (#457)
Purge the legacy UX3 session catalog UI that predates the vault-backed
CatalogStore (Q94/Q95, packages/amico-run). The CATALOG activity-bar tab
(SessionCatalogTree → amicode.catalog, amicode.catalog.save/refresh/remove,
catalogCard webview + catalogcard component) is superseded by the mount stack
+ pack interface (WS1 #391) and is also slated for removal in opencode#215 AC7.
- package.json: drop amicode.catalog view + catalog commands/menus
- trees.ts: delete SessionCatalogTree/CATALOG_KEY, keep armonia placeholder
(will be replaced by Workspace tree per 215)
- extension.ts: remove registerCatalogCard + catalog save/refresh/remove
wiring, simplify amicode.savePulse to file-only Save dialog,
drop demo promote-to-catalog prompt, remove runs_manager promote-to-
catalog prompt (now info-only)
- esbuild.config.mjs: drop catalog_card_webview bundle
- delete catalog_card_shell/webview, media catalogcard, test suite
Pulse save stays via Save to file… → savePulseTo; vault CatalogStore
(packages/amico-run) untouched. Closes#457
* feat(workspace): Workspace sidebar for multi-root (opencode#215 AC6)
Add WorkspaceTreeProvider — renders all workspace folders as collapsible
roots, expands recursively via vscode.workspace.fs.readDirectory(),
respects files.exclude, shows theme icons via resourceUri, opens files
on click, context menus (Copy Path, Reveal), and live-updates via
FileSystemWatcher. Replaces amicode.armonia placeholder; Catalog already
removed in #457.
package.json: rename amicode.armonia → amicode.workspace ("Workspace"),
keep runInspector, update activationEvents onView:workspace, add
view/item/context for workspace files.
Follows opencode#215 decision surface: Location.Ref.directories carries
all roots, no external-directory prompts, instructions stacked per dir.
Pairs with opencode PR #219 (engine: schema + boundary + env + instruction
multi-root + DB).
* fix(workspace): full context menus for Workspace view (opencode#215 AC6)
Add New File/Folder, Rename, Delete, Copy Path/Relative, Reveal,
Open in Terminal to amicode.workspace viewItem context. Ensures
AC6 full context-menu coverage; Run Inspector stays as separate
webview (amicode.runInspector) not a directory in the tree.
* fix(workspace): only hide .git directory, not .gitignore/.github/etc
* feat(workspace): Chat with Amico button, full context menus, Remove from Workspace
- Add 'Chat with Amico' as first tree item with custom yellow SVG icon
that mutes (gray) when a chat tab is open
- Register custom workspace commands (newFile, newFolder, rename, delete,
copyPath, copyRelativePath, revealInOS, openInTerminal, openToSide,
removeFromWorkspace, addFolder) since built-in explorer.* commands
don't fire in custom tree views
- Remove stale amicode.runInspector webview declaration (provider was
deleted in PR #351, only the package.json entry remained)
- Add viewsWelcome for empty workspace state
- Listen to onDidChangeWorkspaceFolders to keep tree in sync
- Add ChatPanel.onLiveChange callback for cross-component state tracking
- Fix credential_scanner e2e test: guard opencode-provider assertion
(machine may have creds under different provider names)
- Add comprehensive test suite for workspace tree and context-menu commands
(24 tests covering tree rendering, all commands, muted state)
---------
Co-authored-by: Raghav Chari <raghavchari2021@gmail.com>
Sign up for freeto 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.

2 participants

@Rchari1@jack-champagne
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

feat(hp): cloud-only enforcement + cloud iters/frames reach the Run Inspector - #219

Merged
jack-champagne merged 7 commits into
mainfrom
rchari/hp-cloud-only
Jul 29, 2026
Merged

feat(hp): cloud-only enforcement + cloud iters/frames reach the Run Inspector#219
jack-champagne merged 7 commits into
mainfrom
rchari/hp-cloud-only

Conversation

@Rchari1

@Rchari1Rchari1 commented Jul 28, 2026

Copy link
Copy Markdown
Member

Six commits, one thread: make the paid tier actually run in the cloud, and make what it produces actually reach the Run Inspector. 18 files, +673/−116.


1. Piccolissimo + Altissimo is cloud-only, and now enforced

Selecting the HP tier was supposed to mean "runs in Harmoniqs Cloud." In practice the agent kept dispatching it locally, where the laptop precompiled the whole HP stack (IPOPT included) until amico-run's process-group timeout SIGTERMed julia mid-precompile.

The API-key prompt and entitlement flip were already built and work (amicode#200/#167) — untouched here. Three other things allowed local dispatch:

  1. Nothing refused a local HP launch. The tier="hpc" gate is solid but only fires on a spec that saystier="hpc", and runGate only runs for --spec launches. A bare amico-run script.jl never reached it.
  2. The entitlement that unlocks the tier unlocked the failure — HP grants issimo, so the import scan admitted a local using Piccolissimo.
  3. The agent got contradictory orderssolverModeSection() said "launch in the cloud" while the routing section beside it said "routing is PER-SOLVE and EXPLICIT, default to local." Given a cloud-only tier and an instruction to default local, it defaulted local.

Fix, in the two-layer shape the tier already uses:

  • Enforcementlaunch.ts refuses --executor local while HP is selected, at the one choke point every run passes through, before the gate, so it covers spec and no-spec runs alike. Reads solver mode as status only, never a token, and fails safe to piccolo on a missing or corrupt file: this value can only ever refuse a run, so a bad read must never invent hp and block ordinary free-tier work.
  • Guidance — the routing section states the cloud-only contract instead of asking a routing question, and AGENTS.md step 5 makes the injected section authoritative over its local default. Piccolo sessions stay byte-identical (the section is still "" unless mode is hpand the cloud is connected).

The estimate keeps its reporting role and loses its deciding role: an estimate that fits in local RAM does not make an HP solve local.

Service is named Harmoniqs Cloud in every user-visible string. Wire id stays company-compute — renaming breaks the credential route, status cache, and CONNECTION_IDS for no visible gain.


2. Cloud iters and frames reach the Run Inspector

The client chain was fully built — poll the cloud, synthesize AMICODE_ITER into run.log, write frames, touch mtime for the stall logic. It read the wrong fields:

client readservice returns
statsbody.iters{task_id, **stats**[], submitter}
framespng_base64{task_id, iter, key, **url**, submitter}

Both reads sit in best-effort try/catch ("stats are advisory"), so it failed silentlyundefined ?? [] iterated zero times and run.log stayed 0 bytes. Confirmed on task 419a57e6: 60 IPOPT iterations and 11 frames banked to S3, empty inspector.

Why nobody caught it:fake_cloud.ts served the shapes the client read, not the shapes the service returns. Every test passed against a fake that agreed with the bug. So the fake is corrected first, and two tests pin the live payloads by exact key set — including that the presigned URL is fetchable with no auth header, because the signature is the credential.

Frame filenames also went 3-digit → 5-digit: iter_00007.png is what both the S3 layout and the local julia solve write. The old name matched neither, so cloud and local frames landed under two schemes in one run dir.

A third drift, found while proving the above: the poller only json.loads an AMICODE_ITER payload starting with {, so template-emitted key=value lines come back as {raw: "iter=7 f=…"}. The client keyed on it.iter and skipped every one as NaN — invisible because the smoke test seeds JSON. Now reconstructs from either shape and drops malformed records rather than emitting NaN.


3. The cloud can finally populate /stats

/solves/<id>/stats parses AMICODE_ITER out of run.log in the artifact prefix, and the runner's sidecar populates that prefix with aws s3 sync . — whatever is in the solve cwd. But julia's stdout on the runner goes to the SSM command stream, so run.log was never written there: nothing to sync, stats: [], empty inspector. The template's own comment ("AMICODE_PULSE lines on stdout (→ run.log)") was true locally and false in the cloud.

Telemetry now routes through emit(), which also appends to run.logonly when TASK_ID is set (the runner exports it). Locally amico-run's executor already writes run.log from stdout, so appending unconditionally would double every line and the inspector would count each iteration twice.

No terraform change and no AMI rebake: the solve script is uploaded per submission (<task_id>-solve.jl), so this ships immediately.


4. Altissimo streams like IPOPT

Both channels were bolted to IPOPT — frames off IpoptOptions.intermediate_callback, the iter line off a callback reading IpoptOptimizerState. Altissimo has neither; its only hook is callback on optimize!, arriving as (x, info). So an Altissimo solve lost the frames too, leaving the inspector completely dark rather than merely numberless.

SOLVER = :ipopt | :altissimo re-hangs both channels. xis the primal, so pulse_emit's solver-agnostic (primal, iter) contract takes it unchanged — same frames, same AMICODE_PULSE, same cooperative STOP (returning false stops an Altissimo solve exactly as it stops an Ipopt one). inf_pr/inf_du come from the callback tuple when present (Altissimo#414, merged) and are otherwise derived from eq_viol/ineq_viol/kkt_error, so it works on an older Altissimo instead of emitting NaN.

Two traps verified against Piccolissimo main and handled:

  • the qcp-level solve! forwards kwargs... to the prob-level method, which takes callback explicitly — so the wiring does reach optimize!;
  • but max_iter lands in that kwargs... and is silently dropped, so the budget now rides AltissimoOptions(max_outer_iter = max_iter). Left alone it would have quietly run Altissimo's default 20 outer iterations while the FILL-IN block said 60.

The guidance names SOLVER = :altissimo as the one-line switch and forbids hand-rolling the solve call, stating each trap so the instruction carries its own justification.


Testing

  • amico-run: 938 passed, 12 skipped.
  • extension: 778 passed, 3 skipped (the full CI set, --exclude '**/slow/**').
  • tsc --noEmit clean in both packages.
  • Live, against the real staging service: frames fetched from a presigned URL and landed locally with 5-digit names (task 0fccbbf9); template runs green end to end (fidelity 0.99997, 60 iterations); local run writes norun.log (no duplication), and with TASK_ID set run.log appears with iter lines in exactly the format the poller parses.
  • One incidental fix that matters for CI: the CLI tests were inheriting the developer's~/.amico/amicode/solver-mode.json. On an hp machine nine local-solve tests failed — on correct behaviour. They now spawn with a hermetic ops dir.

CI caught the one consumer I missed on the frame rename (the Δ9 remote state-machine test); the assertion above it — run.log contains iter=7 — passed, which is independent confirmation in a second harness that the stats fix delivers iters end to end.


What this does NOT fix

  • Altissimo on the cloud. The runner AMI bakes a Piccolissimo/Altissimo predating callback support, so it emits nothing there until Altissimo#414 is released and the image rebuilt. The template is ready for that moment.
  • The Altissimo code path has never been executed. It is written against the verified contract and parses, but the local checkouts here are 287/324 commits behind and cannot run it. Worth exercising on a current env before anyone demos it.
  • Cold start. ~9–10 min from submit to first frame, for ~55s of compute. Real, and unaddressed here.
  • Existing agent-authored scripts in ~/.amico/problems/ predate emit(), so they still show frames and no numbers until re-authored from the updated template.

solverModeSection is exported to make the guidance testable (matching its already-exported sibling routingSection). The same one-line export is on #225 — identical change, so either merge order is fine.

Reviewer note

This narrows behaviour: a local Piccolissimo run that "worked" before — slowly, usually fatally — is now refused outright. That is the intent of a cloud-only paid tier, but it is a real change for anyone relying on the local path, and the way out is one click on the solver control.

…forced
Selecting Piccolissimo + Altissimo was supposed to mean "this solve runs in
Harmoniqs Cloud". In practice the agent kept dispatching it LOCALLY: the laptop
precompiled the whole HP stack (IPOPT included) until amico-run's process-group
timeout SIGTERMed Julia mid-precompile.
Three things allowed that, none of them the pieces people assumed were missing
(the API-key prompt and the entitlement flip were already built and work):
1. Nothing refused a local HP launch. The gate's tier=hpc rules are solid, but
they only fire on a spec that SAYS tier="hpc" — and runGate only runs for
--spec launches at all. A bare `amico-run script.jl` never reached them.
2. Selecting HP grants the `issimo` entitlement, so the import scan happily
admits a local `using Piccolissimo`. The entitlement that unlocks the tier
was also unlocking the failure mode.
3. The agent was told two contradictory things. solverModeSection() said "launch
HP solves in the cloud" while the routing section beside it said "routing is
PER-SOLVE and EXPLICIT, you confirm where EVERY solve runs, default local" —
and the base AGENTS.md step 5 said the same. Given a cloud-only tier and an
instruction to default local, it defaulted local.
The fix, in the same two-layer shape the tier already uses:
- ENFORCEMENT: amico-run refuses `--executor local` while HP is selected, at the
one choke point every run passes through (launch.ts, before the gate), so it
covers spec and no-spec runs alike. Reads the extension's solver-mode.json —
status only, never a token — and fails SAFE to piccolo on an absent or corrupt
file, so a fresh install and every free-tier local run behave exactly as now.
- GUIDANCE: the routing section now states the cloud-only contract instead of
asking a routing question, and AGENTS.md step 5 makes the injected section
authoritative over its local default. Piccolo sessions are untouched: the
section is still "" unless mode is hp AND the cloud is connected.
The estimate keeps its reporting role and loses its deciding role — an estimate
that fits in local RAM does not make an HP solve local.
Also names the service "Harmoniqs Cloud" everywhere a user can see it (gate
refusals, routing prose, AGENTS.md, the HP row's tooltips) instead of the
internal "company compute" / bare "the cloud". The wire id stays
`company-compute` — renaming it would break the credential route, the status
cache, and the fork's CONNECTION_IDS for no user-visible gain.
Tests: 9 new (the reader's fail-safe directions; the refusal, including on the
no-spec path and that it leaves no run dir; that a REMOTE HP launch still
completes, against FakeCloud) + the routing/AGENTS contract updated to pin the
new copy, with an explicit regression test that the per-solve question does not
come back. amico-run 934 passed, extension suite green apart from two live-model
E2Es (interview_e2e, scores_e2e) that fail identically with this change stashed —
pre-existing, tracked separately.
Incidental but load-bearing: the CLI tests were inheriting the DEVELOPER's
~/.amico/amicode/solver-mode.json, so on an hp machine nine local-solve tests
failed on correct behaviour. They now spawn with a hermetic ops dir.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Rchari1and others added 4 commits July 28, 2026 20:39
A cloud solve produced an empty Run Inspector. The client-side chain was fully
built — RemoteExecutor polls the cloud, synthesizes AMICODE_ITER lines into
run.log, writes frames, and touches run.log's mtime for the stall logic — but it
read the WRONG FIELDS, so it dropped everything:
stats client read `body.iters` · service returns `{task_id, stats[], submitter}`
frames client read `png_base64` · service returns `{task_id, iter, key, url, submitter}`
Both reads sit inside best-effort try/catch blocks ("stats are advisory"), so
the failure was completely silent: `undefined ?? []` iterated zero times, run.log
stayed 0 bytes, and the Inspector had nothing to tail. Confirmed against task
419a57e6 on staging, which ran 60 IPOPT iterations and banked 11 frames to S3
while the local run.log stayed empty.
The reason nobody caught it: fake_cloud.ts served the shapes the CLIENT read,
not the shapes the SERVICE returns. Every test passed against a fake that agreed
with the bug. That is the real defect here, so the fake is corrected first and
two tests now pin the live payloads by exact key set — a fake that mirrors the
client proves nothing.
- stats: read `stats`, falling back to `iters` so an older runner still works.
- frames: fetch the presigned url (no auth header — the signature IS the
credential) and keep the base64 lane for older runners. FakeCloud now serves
artifact bytes from a route placed BEFORE its auth guard, matching S3.
- frame filenames go 3-digit → 5-digit: iter_00007.png is what both the S3 layout
and the local Julia solve write, so cloud and local frames no longer land under
two different schemes in one run dir.
Also: the HP solver-mode guidance now covers the Altissimo backend. IPOPT stays
the default because it is what streams telemetry; Altissimo is used on request,
but the agent must state the trade first — Piccolissimo's solve!(::AltissimoOptions)
does not forward a caller callback to Altissimo.optimize!, and there is no
intermediate_callback on that path, so an Altissimo run emits no frames and no
AMICODE_ITER and the Inspector stays empty until it finishes. Never switch
silently, never claim live iterations on it.
amico-run: 936 passed. extension: 62 passed in the touched suites, tsc clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CI caught the one consumer I missed when frame filenames went 3-digit → 5-digit.
The Δ9 test waited on iter_007.png and timed out; the assertion above it —
run.log contains "iter=7" — PASSED, which is independent confirmation in a second
harness that the stats-field fix delivers iters end to end.
Also corrects solver-mode guidance shipped earlier today. It asserted that
Piccolissimo's solve!(::AltissimoOptions) does not forward a caller callback —
taken from a report without checking. That is true of the local checkout (287
commits behind) but false of main, which accepts `callback` and forwards it to
Altissimo.optimize!. The guidance now says live iterations depend on the INSTALLED
version and tells the agent not to promise iterations it has not observed.
extension: 776 passed, 3 skipped (the full CI set).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two gaps stood between a cloud solve and a populated Run Inspector. Both are in
the solve template, which ships PER SUBMISSION (the client uploads
<task_id>-solve.jl), so neither needs an AMI rebake.
1. NOTHING WROTE run.log IN THE CLOUD.
/solves/<id>/stats parses AMICODE_ITER lines out of run.log in the artifact
prefix, and the runner's sidecar populates that prefix with `aws s3 sync .` —
whatever is in the solve cwd. But julia's stdout on the runner goes to the SSM
command stream, so run.log was never written there: nothing to sync, stats: [],
empty inspector. The template's own comment ("AMICODE_PULSE lines on stdout (→
run.log)") was true locally and false in the cloud.
Every AMICODE_* line now goes through emit(), which also appends to run.log —
but ONLY when TASK_ID is set (the runner exports it). Locally amico-run's
executor already writes run.log from stdout, so appending unconditionally would
double every line and the inspector would count each iteration twice.
Verified: local run writes no run.log (11 frames, result.toml, fidelity
0.99997); with TASK_ID set, run.log appears with AMICODE_ITER lines in the
exact format the poller parses.
2. THE TELEMETRY WAS BOLTED TO IPOPT.
Frames came off IpoptOptions.intermediate_callback and the iter line off a
callback reading IpoptOptimizerState. Altissimo has neither: its only hook is
`callback` on optimize! (forwarded by Piccolissimo's solve!(::AltissimoOptions))
and it arrives as (x, info). So an Altissimo solve lost the frames TOO, leaving
the inspector completely dark rather than merely numberless.
SOLVER = :ipopt | :altissimo now selects the backend and re-hangs both channels.
`x` is the primal, so pulse_emit's solver-agnostic (primal, iter) contract takes
it unchanged — same frames, same AMICODE_PULSE, same cooperative STOP.
inf_pr/inf_du come from the callback tuple when present (Altissimo#414) and are
otherwise derived from eq_viol/ineq_viol/kkt_error, so it works on an older
Altissimo too instead of emitting NaN.
And a third drift found while proving (1): the poller only json.loads an
AMICODE_ITER payload starting with "{", so template-emitted key=value lines come
back as {raw: "iter=7 f=…"}. The client keyed on it.iter and skipped every one as
NaN — invisible because the smoke test seeds JSON. It now reconstructs the line
from either shape, and drops malformed records rather than emitting NaN.
Still NOT solved by this: Altissimo on the cloud. The runner AMI bakes a
Piccolissimo/Altissimo predating callback support, so it will emit nothing there
until Altissimo#414 is released and the image rebuilt. The template is ready for
that moment and works today on IPOPT (cloud) and both backends locally.
amico-run 938 passed · extension 776 passed · template runs green end to end.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
solve!(::AltissimoOptions) forwards a HARDCODED kwarg list to
Altissimo.optimize! and swallows the rest, so `max_iter = 60` passed to solve!
never arrived: the solve quietly ran Altissimo's default 20 outer iterations
while the FILL-IN block said 60. Silent, not an error — the worst kind.
Budget now rides AltissimoOptions(max_outer_iter = max_iter). Verified against
Piccolissimo main: the qcp-level solve! forwards kwargs... to the prob-level
method, which takes `callback` explicitly, so the callback wiring does reach
optimize! — it is only the iteration budget that was being dropped.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The template gained a SOLVER flag but nothing told the agent it exists, so an
agent asked for Altissimo would hand-roll a solve call — and silently lose both
telemetry channels plus the iteration budget:
- frames come off IpoptOptions.intermediate_callback, which AltissimoOptions
has no equivalent of, so a hand-written call leaves the Run Inspector fully
dark rather than merely numberless;
- a `max_iter` passed to solve!(::AltissimoOptions) lands in kwargs... and is
dropped, so the solve quietly runs Altissimo's default 20 outer iterations;
- inf_pr/inf_du need deriving from eq_viol/ineq_viol/kkt_error on Altissimo
builds that predate #414.
The template already handles all three. The guidance now says the switch is ONE
line — `SOLVER = :altissimo` in the FILL-IN block — and states each trap, so the
instruction carries its own justification rather than reading as arbitrary.
solverModeSection is exported to make it testable, matching its already-exported
sibling routingSection. (The same one-line export is on #225; identical change,
so either merge order is fine.)
extension: 778 passed, tsc clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@Rchari1Rchari1 changed the title feat(hp): Piccolissimo + Altissimo is cloud-only, and now actually enforcedfeat(hp): cloud-only enforcement + cloud iters/frames reach the Run InspectorJul 29, 2026
# Conflicts:
#	packages/extension/src/opencode_config.ts
#	packages/extension/test/agents_md.test.ts
@jack-champagne
jack-champagneforce-pushed the rchari/hp-cloud-only branch 2 times, most recently from 54f4275 to 8aa362fCompareJuly 29, 2026 04:24
@jack-champagne
jack-champagne merged commit 199b23d into mainJul 29, 2026
10 checks passed
jeonghun-jj-lee pushed a commit that referenced this pull request Aug 21, 2026
Add WorkspaceTreeProvider — renders all workspace folders as collapsible
roots, expands recursively via vscode.workspace.fs.readDirectory(),
respects files.exclude, shows theme icons via resourceUri, opens files
on click, context menus (Copy Path, Reveal), and live-updates via
FileSystemWatcher. Replaces amicode.armonia placeholder; Catalog already
removed in #457.
package.json: rename amicode.armonia → amicode.workspace ("Workspace"),
keep runInspector, update activationEvents onView:workspace, add
view/item/context for workspace files.
Follows opencode#215 decision surface: Location.Ref.directories carries
all roots, no external-directory prompts, instructions stacked per dir.
Pairs with opencode PR #219 (engine: schema + boundary + env + instruction
multi-root + DB).
jeonghun-jj-lee added a commit that referenced this pull request Aug 22, 2026
…, #215)
* fix(ui): remove pulse catalog + CATALOG sidebar tab and widgets (#457)
Purge the legacy UX3 session catalog UI that predates the vault-backed
CatalogStore (Q94/Q95, packages/amico-run). The CATALOG activity-bar tab
(SessionCatalogTree → amicode.catalog, amicode.catalog.save/refresh/remove,
catalogCard webview + catalogcard component) is superseded by the mount stack
+ pack interface (WS1 #391) and is also slated for removal in opencode#215 AC7.
- package.json: drop amicode.catalog view + catalog commands/menus
- trees.ts: delete SessionCatalogTree/CATALOG_KEY, keep armonia placeholder
(will be replaced by Workspace tree per 215)
- extension.ts: remove registerCatalogCard + catalog save/refresh/remove
wiring, simplify amicode.savePulse to file-only Save dialog,
drop demo promote-to-catalog prompt, remove runs_manager promote-to-
catalog prompt (now info-only)
- esbuild.config.mjs: drop catalog_card_webview bundle
- delete catalog_card_shell/webview, media catalogcard, test suite
Pulse save stays via Save to file… → savePulseTo; vault CatalogStore
(packages/amico-run) untouched. Closes#457
* feat(workspace): Workspace sidebar for multi-root (opencode#215 AC6)
Add WorkspaceTreeProvider — renders all workspace folders as collapsible
roots, expands recursively via vscode.workspace.fs.readDirectory(),
respects files.exclude, shows theme icons via resourceUri, opens files
on click, context menus (Copy Path, Reveal), and live-updates via
FileSystemWatcher. Replaces amicode.armonia placeholder; Catalog already
removed in #457.
package.json: rename amicode.armonia → amicode.workspace ("Workspace"),
keep runInspector, update activationEvents onView:workspace, add
view/item/context for workspace files.
Follows opencode#215 decision surface: Location.Ref.directories carries
all roots, no external-directory prompts, instructions stacked per dir.
Pairs with opencode PR #219 (engine: schema + boundary + env + instruction
multi-root + DB).
* fix(workspace): full context menus for Workspace view (opencode#215 AC6)
Add New File/Folder, Rename, Delete, Copy Path/Relative, Reveal,
Open in Terminal to amicode.workspace viewItem context. Ensures
AC6 full context-menu coverage; Run Inspector stays as separate
webview (amicode.runInspector) not a directory in the tree.
* fix(workspace): only hide .git directory, not .gitignore/.github/etc
* feat(workspace): Chat with Amico button, full context menus, Remove from Workspace
- Add 'Chat with Amico' as first tree item with custom yellow SVG icon
that mutes (gray) when a chat tab is open
- Register custom workspace commands (newFile, newFolder, rename, delete,
copyPath, copyRelativePath, revealInOS, openInTerminal, openToSide,
removeFromWorkspace, addFolder) since built-in explorer.* commands
don't fire in custom tree views
- Remove stale amicode.runInspector webview declaration (provider was
deleted in PR #351, only the package.json entry remained)
- Add viewsWelcome for empty workspace state
- Listen to onDidChangeWorkspaceFolders to keep tree in sync
- Add ChatPanel.onLiveChange callback for cross-component state tracking
- Fix credential_scanner e2e test: guard opencode-provider assertion
(machine may have creds under different provider names)
- Add comprehensive test suite for workspace tree and context-menu commands
(24 tests covering tree rendering, all commands, muted state)
---------
Co-authored-by: Raghav Chari <raghavchari2021@gmail.com>
Sign up for freeto 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.

2 participants

@Rchari1@jack-champagne
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(hp): cloud-only enforcement + cloud iters/frames reach the Run Inspector - #219

Merged
jack-champagne merged 7 commits into
mainfrom
rchari/hp-cloud-only
Jul 29, 2026
Merged

feat(hp): cloud-only enforcement + cloud iters/frames reach the Run Inspector#219
jack-champagne merged 7 commits into
mainfrom
rchari/hp-cloud-only

Conversation

@Rchari1

@Rchari1Rchari1 commented Jul 28, 2026

Copy link
Copy Markdown
Member

Six commits, one thread: make the paid tier actually run in the cloud, and make what it produces actually reach the Run Inspector. 18 files, +673/−116.


1. Piccolissimo + Altissimo is cloud-only, and now enforced

Selecting the HP tier was supposed to mean "runs in Harmoniqs Cloud." In practice the agent kept dispatching it locally, where the laptop precompiled the whole HP stack (IPOPT included) until amico-run's process-group timeout SIGTERMed julia mid-precompile.

The API-key prompt and entitlement flip were already built and work (amicode#200/#167) — untouched here. Three other things allowed local dispatch:

  1. Nothing refused a local HP launch. The tier="hpc" gate is solid but only fires on a spec that saystier="hpc", and runGate only runs for --spec launches. A bare amico-run script.jl never reached it.
  2. The entitlement that unlocks the tier unlocked the failure — HP grants issimo, so the import scan admitted a local using Piccolissimo.
  3. The agent got contradictory orderssolverModeSection() said "launch in the cloud" while the routing section beside it said "routing is PER-SOLVE and EXPLICIT, default to local." Given a cloud-only tier and an instruction to default local, it defaulted local.

Fix, in the two-layer shape the tier already uses:

  • Enforcementlaunch.ts refuses --executor local while HP is selected, at the one choke point every run passes through, before the gate, so it covers spec and no-spec runs alike. Reads solver mode as status only, never a token, and fails safe to piccolo on a missing or corrupt file: this value can only ever refuse a run, so a bad read must never invent hp and block ordinary free-tier work.
  • Guidance — the routing section states the cloud-only contract instead of asking a routing question, and AGENTS.md step 5 makes the injected section authoritative over its local default. Piccolo sessions stay byte-identical (the section is still "" unless mode is hpand the cloud is connected).

The estimate keeps its reporting role and loses its deciding role: an estimate that fits in local RAM does not make an HP solve local.

Service is named Harmoniqs Cloud in every user-visible string. Wire id stays company-compute — renaming breaks the credential route, status cache, and CONNECTION_IDS for no visible gain.


2. Cloud iters and frames reach the Run Inspector

The client chain was fully built — poll the cloud, synthesize AMICODE_ITER into run.log, write frames, touch mtime for the stall logic. It read the wrong fields:

client readservice returns
statsbody.iters{task_id, **stats**[], submitter}
framespng_base64{task_id, iter, key, **url**, submitter}

Both reads sit in best-effort try/catch ("stats are advisory"), so it failed silentlyundefined ?? [] iterated zero times and run.log stayed 0 bytes. Confirmed on task 419a57e6: 60 IPOPT iterations and 11 frames banked to S3, empty inspector.

Why nobody caught it:fake_cloud.ts served the shapes the client read, not the shapes the service returns. Every test passed against a fake that agreed with the bug. So the fake is corrected first, and two tests pin the live payloads by exact key set — including that the presigned URL is fetchable with no auth header, because the signature is the credential.

Frame filenames also went 3-digit → 5-digit: iter_00007.png is what both the S3 layout and the local julia solve write. The old name matched neither, so cloud and local frames landed under two schemes in one run dir.

A third drift, found while proving the above: the poller only json.loads an AMICODE_ITER payload starting with {, so template-emitted key=value lines come back as {raw: "iter=7 f=…"}. The client keyed on it.iter and skipped every one as NaN — invisible because the smoke test seeds JSON. Now reconstructs from either shape and drops malformed records rather than emitting NaN.


3. The cloud can finally populate /stats

/solves/<id>/stats parses AMICODE_ITER out of run.log in the artifact prefix, and the runner's sidecar populates that prefix with aws s3 sync . — whatever is in the solve cwd. But julia's stdout on the runner goes to the SSM command stream, so run.log was never written there: nothing to sync, stats: [], empty inspector. The template's own comment ("AMICODE_PULSE lines on stdout (→ run.log)") was true locally and false in the cloud.

Telemetry now routes through emit(), which also appends to run.logonly when TASK_ID is set (the runner exports it). Locally amico-run's executor already writes run.log from stdout, so appending unconditionally would double every line and the inspector would count each iteration twice.

No terraform change and no AMI rebake: the solve script is uploaded per submission (<task_id>-solve.jl), so this ships immediately.


4. Altissimo streams like IPOPT

Both channels were bolted to IPOPT — frames off IpoptOptions.intermediate_callback, the iter line off a callback reading IpoptOptimizerState. Altissimo has neither; its only hook is callback on optimize!, arriving as (x, info). So an Altissimo solve lost the frames too, leaving the inspector completely dark rather than merely numberless.

SOLVER = :ipopt | :altissimo re-hangs both channels. xis the primal, so pulse_emit's solver-agnostic (primal, iter) contract takes it unchanged — same frames, same AMICODE_PULSE, same cooperative STOP (returning false stops an Altissimo solve exactly as it stops an Ipopt one). inf_pr/inf_du come from the callback tuple when present (Altissimo#414, merged) and are otherwise derived from eq_viol/ineq_viol/kkt_error, so it works on an older Altissimo instead of emitting NaN.

Two traps verified against Piccolissimo main and handled:

  • the qcp-level solve! forwards kwargs... to the prob-level method, which takes callback explicitly — so the wiring does reach optimize!;
  • but max_iter lands in that kwargs... and is silently dropped, so the budget now rides AltissimoOptions(max_outer_iter = max_iter). Left alone it would have quietly run Altissimo's default 20 outer iterations while the FILL-IN block said 60.

The guidance names SOLVER = :altissimo as the one-line switch and forbids hand-rolling the solve call, stating each trap so the instruction carries its own justification.


Testing

  • amico-run: 938 passed, 12 skipped.
  • extension: 778 passed, 3 skipped (the full CI set, --exclude '**/slow/**').
  • tsc --noEmit clean in both packages.
  • Live, against the real staging service: frames fetched from a presigned URL and landed locally with 5-digit names (task 0fccbbf9); template runs green end to end (fidelity 0.99997, 60 iterations); local run writes norun.log (no duplication), and with TASK_ID set run.log appears with iter lines in exactly the format the poller parses.
  • One incidental fix that matters for CI: the CLI tests were inheriting the developer's~/.amico/amicode/solver-mode.json. On an hp machine nine local-solve tests failed — on correct behaviour. They now spawn with a hermetic ops dir.

CI caught the one consumer I missed on the frame rename (the Δ9 remote state-machine test); the assertion above it — run.log contains iter=7 — passed, which is independent confirmation in a second harness that the stats fix delivers iters end to end.


What this does NOT fix

  • Altissimo on the cloud. The runner AMI bakes a Piccolissimo/Altissimo predating callback support, so it emits nothing there until Altissimo#414 is released and the image rebuilt. The template is ready for that moment.
  • The Altissimo code path has never been executed. It is written against the verified contract and parses, but the local checkouts here are 287/324 commits behind and cannot run it. Worth exercising on a current env before anyone demos it.
  • Cold start. ~9–10 min from submit to first frame, for ~55s of compute. Real, and unaddressed here.
  • Existing agent-authored scripts in ~/.amico/problems/ predate emit(), so they still show frames and no numbers until re-authored from the updated template.

solverModeSection is exported to make the guidance testable (matching its already-exported sibling routingSection). The same one-line export is on #225 — identical change, so either merge order is fine.

Reviewer note

This narrows behaviour: a local Piccolissimo run that "worked" before — slowly, usually fatally — is now refused outright. That is the intent of a cloud-only paid tier, but it is a real change for anyone relying on the local path, and the way out is one click on the solver control.

…forced
Selecting Piccolissimo + Altissimo was supposed to mean "this solve runs in
Harmoniqs Cloud". In practice the agent kept dispatching it LOCALLY: the laptop
precompiled the whole HP stack (IPOPT included) until amico-run's process-group
timeout SIGTERMed Julia mid-precompile.
Three things allowed that, none of them the pieces people assumed were missing
(the API-key prompt and the entitlement flip were already built and work):
1. Nothing refused a local HP launch. The gate's tier=hpc rules are solid, but
they only fire on a spec that SAYS tier="hpc" — and runGate only runs for
--spec launches at all. A bare `amico-run script.jl` never reached them.
2. Selecting HP grants the `issimo` entitlement, so the import scan happily
admits a local `using Piccolissimo`. The entitlement that unlocks the tier
was also unlocking the failure mode.
3. The agent was told two contradictory things. solverModeSection() said "launch
HP solves in the cloud" while the routing section beside it said "routing is
PER-SOLVE and EXPLICIT, you confirm where EVERY solve runs, default local" —
and the base AGENTS.md step 5 said the same. Given a cloud-only tier and an
instruction to default local, it defaulted local.
The fix, in the same two-layer shape the tier already uses:
- ENFORCEMENT: amico-run refuses `--executor local` while HP is selected, at the
one choke point every run passes through (launch.ts, before the gate), so it
covers spec and no-spec runs alike. Reads the extension's solver-mode.json —
status only, never a token — and fails SAFE to piccolo on an absent or corrupt
file, so a fresh install and every free-tier local run behave exactly as now.
- GUIDANCE: the routing section now states the cloud-only contract instead of
asking a routing question, and AGENTS.md step 5 makes the injected section
authoritative over its local default. Piccolo sessions are untouched: the
section is still "" unless mode is hp AND the cloud is connected.
The estimate keeps its reporting role and loses its deciding role — an estimate
that fits in local RAM does not make an HP solve local.
Also names the service "Harmoniqs Cloud" everywhere a user can see it (gate
refusals, routing prose, AGENTS.md, the HP row's tooltips) instead of the
internal "company compute" / bare "the cloud". The wire id stays
`company-compute` — renaming it would break the credential route, the status
cache, and the fork's CONNECTION_IDS for no user-visible gain.
Tests: 9 new (the reader's fail-safe directions; the refusal, including on the
no-spec path and that it leaves no run dir; that a REMOTE HP launch still
completes, against FakeCloud) + the routing/AGENTS contract updated to pin the
new copy, with an explicit regression test that the per-solve question does not
come back. amico-run 934 passed, extension suite green apart from two live-model
E2Es (interview_e2e, scores_e2e) that fail identically with this change stashed —
pre-existing, tracked separately.
Incidental but load-bearing: the CLI tests were inheriting the DEVELOPER's
~/.amico/amicode/solver-mode.json, so on an hp machine nine local-solve tests
failed on correct behaviour. They now spawn with a hermetic ops dir.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Rchari1and others added 4 commits July 28, 2026 20:39
A cloud solve produced an empty Run Inspector. The client-side chain was fully
built — RemoteExecutor polls the cloud, synthesizes AMICODE_ITER lines into
run.log, writes frames, and touches run.log's mtime for the stall logic — but it
read the WRONG FIELDS, so it dropped everything:
stats client read `body.iters` · service returns `{task_id, stats[], submitter}`
frames client read `png_base64` · service returns `{task_id, iter, key, url, submitter}`
Both reads sit inside best-effort try/catch blocks ("stats are advisory"), so
the failure was completely silent: `undefined ?? []` iterated zero times, run.log
stayed 0 bytes, and the Inspector had nothing to tail. Confirmed against task
419a57e6 on staging, which ran 60 IPOPT iterations and banked 11 frames to S3
while the local run.log stayed empty.
The reason nobody caught it: fake_cloud.ts served the shapes the CLIENT read,
not the shapes the SERVICE returns. Every test passed against a fake that agreed
with the bug. That is the real defect here, so the fake is corrected first and
two tests now pin the live payloads by exact key set — a fake that mirrors the
client proves nothing.
- stats: read `stats`, falling back to `iters` so an older runner still works.
- frames: fetch the presigned url (no auth header — the signature IS the
credential) and keep the base64 lane for older runners. FakeCloud now serves
artifact bytes from a route placed BEFORE its auth guard, matching S3.
- frame filenames go 3-digit → 5-digit: iter_00007.png is what both the S3 layout
and the local Julia solve write, so cloud and local frames no longer land under
two different schemes in one run dir.
Also: the HP solver-mode guidance now covers the Altissimo backend. IPOPT stays
the default because it is what streams telemetry; Altissimo is used on request,
but the agent must state the trade first — Piccolissimo's solve!(::AltissimoOptions)
does not forward a caller callback to Altissimo.optimize!, and there is no
intermediate_callback on that path, so an Altissimo run emits no frames and no
AMICODE_ITER and the Inspector stays empty until it finishes. Never switch
silently, never claim live iterations on it.
amico-run: 936 passed. extension: 62 passed in the touched suites, tsc clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CI caught the one consumer I missed when frame filenames went 3-digit → 5-digit.
The Δ9 test waited on iter_007.png and timed out; the assertion above it —
run.log contains "iter=7" — PASSED, which is independent confirmation in a second
harness that the stats-field fix delivers iters end to end.
Also corrects solver-mode guidance shipped earlier today. It asserted that
Piccolissimo's solve!(::AltissimoOptions) does not forward a caller callback —
taken from a report without checking. That is true of the local checkout (287
commits behind) but false of main, which accepts `callback` and forwards it to
Altissimo.optimize!. The guidance now says live iterations depend on the INSTALLED
version and tells the agent not to promise iterations it has not observed.
extension: 776 passed, 3 skipped (the full CI set).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two gaps stood between a cloud solve and a populated Run Inspector. Both are in
the solve template, which ships PER SUBMISSION (the client uploads
<task_id>-solve.jl), so neither needs an AMI rebake.
1. NOTHING WROTE run.log IN THE CLOUD.
/solves/<id>/stats parses AMICODE_ITER lines out of run.log in the artifact
prefix, and the runner's sidecar populates that prefix with `aws s3 sync .` —
whatever is in the solve cwd. But julia's stdout on the runner goes to the SSM
command stream, so run.log was never written there: nothing to sync, stats: [],
empty inspector. The template's own comment ("AMICODE_PULSE lines on stdout (→
run.log)") was true locally and false in the cloud.
Every AMICODE_* line now goes through emit(), which also appends to run.log —
but ONLY when TASK_ID is set (the runner exports it). Locally amico-run's
executor already writes run.log from stdout, so appending unconditionally would
double every line and the inspector would count each iteration twice.
Verified: local run writes no run.log (11 frames, result.toml, fidelity
0.99997); with TASK_ID set, run.log appears with AMICODE_ITER lines in the
exact format the poller parses.
2. THE TELEMETRY WAS BOLTED TO IPOPT.
Frames came off IpoptOptions.intermediate_callback and the iter line off a
callback reading IpoptOptimizerState. Altissimo has neither: its only hook is
`callback` on optimize! (forwarded by Piccolissimo's solve!(::AltissimoOptions))
and it arrives as (x, info). So an Altissimo solve lost the frames TOO, leaving
the inspector completely dark rather than merely numberless.
SOLVER = :ipopt | :altissimo now selects the backend and re-hangs both channels.
`x` is the primal, so pulse_emit's solver-agnostic (primal, iter) contract takes
it unchanged — same frames, same AMICODE_PULSE, same cooperative STOP.
inf_pr/inf_du come from the callback tuple when present (Altissimo#414) and are
otherwise derived from eq_viol/ineq_viol/kkt_error, so it works on an older
Altissimo too instead of emitting NaN.
And a third drift found while proving (1): the poller only json.loads an
AMICODE_ITER payload starting with "{", so template-emitted key=value lines come
back as {raw: "iter=7 f=…"}. The client keyed on it.iter and skipped every one as
NaN — invisible because the smoke test seeds JSON. It now reconstructs the line
from either shape, and drops malformed records rather than emitting NaN.
Still NOT solved by this: Altissimo on the cloud. The runner AMI bakes a
Piccolissimo/Altissimo predating callback support, so it will emit nothing there
until Altissimo#414 is released and the image rebuilt. The template is ready for
that moment and works today on IPOPT (cloud) and both backends locally.
amico-run 938 passed · extension 776 passed · template runs green end to end.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
solve!(::AltissimoOptions) forwards a HARDCODED kwarg list to
Altissimo.optimize! and swallows the rest, so `max_iter = 60` passed to solve!
never arrived: the solve quietly ran Altissimo's default 20 outer iterations
while the FILL-IN block said 60. Silent, not an error — the worst kind.
Budget now rides AltissimoOptions(max_outer_iter = max_iter). Verified against
Piccolissimo main: the qcp-level solve! forwards kwargs... to the prob-level
method, which takes `callback` explicitly, so the callback wiring does reach
optimize! — it is only the iteration budget that was being dropped.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The template gained a SOLVER flag but nothing told the agent it exists, so an
agent asked for Altissimo would hand-roll a solve call — and silently lose both
telemetry channels plus the iteration budget:
- frames come off IpoptOptions.intermediate_callback, which AltissimoOptions
has no equivalent of, so a hand-written call leaves the Run Inspector fully
dark rather than merely numberless;
- a `max_iter` passed to solve!(::AltissimoOptions) lands in kwargs... and is
dropped, so the solve quietly runs Altissimo's default 20 outer iterations;
- inf_pr/inf_du need deriving from eq_viol/ineq_viol/kkt_error on Altissimo
builds that predate #414.
The template already handles all three. The guidance now says the switch is ONE
line — `SOLVER = :altissimo` in the FILL-IN block — and states each trap, so the
instruction carries its own justification rather than reading as arbitrary.
solverModeSection is exported to make it testable, matching its already-exported
sibling routingSection. (The same one-line export is on #225; identical change,
so either merge order is fine.)
extension: 778 passed, tsc clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@Rchari1Rchari1 changed the title feat(hp): Piccolissimo + Altissimo is cloud-only, and now actually enforcedfeat(hp): cloud-only enforcement + cloud iters/frames reach the Run InspectorJul 29, 2026
# Conflicts:
#	packages/extension/src/opencode_config.ts
#	packages/extension/test/agents_md.test.ts
@jack-champagne
jack-champagneforce-pushed the rchari/hp-cloud-only branch 2 times, most recently from 54f4275 to 8aa362fCompareJuly 29, 2026 04:24
@jack-champagne
jack-champagne merged commit 199b23d into mainJul 29, 2026
10 checks passed
jeonghun-jj-lee pushed a commit that referenced this pull request Aug 21, 2026
Add WorkspaceTreeProvider — renders all workspace folders as collapsible
roots, expands recursively via vscode.workspace.fs.readDirectory(),
respects files.exclude, shows theme icons via resourceUri, opens files
on click, context menus (Copy Path, Reveal), and live-updates via
FileSystemWatcher. Replaces amicode.armonia placeholder; Catalog already
removed in #457.
package.json: rename amicode.armonia → amicode.workspace ("Workspace"),
keep runInspector, update activationEvents onView:workspace, add
view/item/context for workspace files.
Follows opencode#215 decision surface: Location.Ref.directories carries
all roots, no external-directory prompts, instructions stacked per dir.
Pairs with opencode PR #219 (engine: schema + boundary + env + instruction
multi-root + DB).
jeonghun-jj-lee added a commit that referenced this pull request Aug 22, 2026
…, #215)
* fix(ui): remove pulse catalog + CATALOG sidebar tab and widgets (#457)
Purge the legacy UX3 session catalog UI that predates the vault-backed
CatalogStore (Q94/Q95, packages/amico-run). The CATALOG activity-bar tab
(SessionCatalogTree → amicode.catalog, amicode.catalog.save/refresh/remove,
catalogCard webview + catalogcard component) is superseded by the mount stack
+ pack interface (WS1 #391) and is also slated for removal in opencode#215 AC7.
- package.json: drop amicode.catalog view + catalog commands/menus
- trees.ts: delete SessionCatalogTree/CATALOG_KEY, keep armonia placeholder
(will be replaced by Workspace tree per 215)
- extension.ts: remove registerCatalogCard + catalog save/refresh/remove
wiring, simplify amicode.savePulse to file-only Save dialog,
drop demo promote-to-catalog prompt, remove runs_manager promote-to-
catalog prompt (now info-only)
- esbuild.config.mjs: drop catalog_card_webview bundle
- delete catalog_card_shell/webview, media catalogcard, test suite
Pulse save stays via Save to file… → savePulseTo; vault CatalogStore
(packages/amico-run) untouched. Closes#457
* feat(workspace): Workspace sidebar for multi-root (opencode#215 AC6)
Add WorkspaceTreeProvider — renders all workspace folders as collapsible
roots, expands recursively via vscode.workspace.fs.readDirectory(),
respects files.exclude, shows theme icons via resourceUri, opens files
on click, context menus (Copy Path, Reveal), and live-updates via
FileSystemWatcher. Replaces amicode.armonia placeholder; Catalog already
removed in #457.
package.json: rename amicode.armonia → amicode.workspace ("Workspace"),
keep runInspector, update activationEvents onView:workspace, add
view/item/context for workspace files.
Follows opencode#215 decision surface: Location.Ref.directories carries
all roots, no external-directory prompts, instructions stacked per dir.
Pairs with opencode PR #219 (engine: schema + boundary + env + instruction
multi-root + DB).
* fix(workspace): full context menus for Workspace view (opencode#215 AC6)
Add New File/Folder, Rename, Delete, Copy Path/Relative, Reveal,
Open in Terminal to amicode.workspace viewItem context. Ensures
AC6 full context-menu coverage; Run Inspector stays as separate
webview (amicode.runInspector) not a directory in the tree.
* fix(workspace): only hide .git directory, not .gitignore/.github/etc
* feat(workspace): Chat with Amico button, full context menus, Remove from Workspace
- Add 'Chat with Amico' as first tree item with custom yellow SVG icon
that mutes (gray) when a chat tab is open
- Register custom workspace commands (newFile, newFolder, rename, delete,
copyPath, copyRelativePath, revealInOS, openInTerminal, openToSide,
removeFromWorkspace, addFolder) since built-in explorer.* commands
don't fire in custom tree views
- Remove stale amicode.runInspector webview declaration (provider was
deleted in PR #351, only the package.json entry remained)
- Add viewsWelcome for empty workspace state
- Listen to onDidChangeWorkspaceFolders to keep tree in sync
- Add ChatPanel.onLiveChange callback for cross-component state tracking
- Fix credential_scanner e2e test: guard opencode-provider assertion
(machine may have creds under different provider names)
- Add comprehensive test suite for workspace tree and context-menu commands
(24 tests covering tree rendering, all commands, muted state)
---------
Co-authored-by: Raghav Chari <raghavchari2021@gmail.com>
Sign up for freeto 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.

2 participants

@Rchari1@jack-champagne
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(hp): cloud-only enforcement + cloud iters/frames reach the Run Inspector - #219

Merged
jack-champagne merged 7 commits into
mainfrom
rchari/hp-cloud-only
Jul 29, 2026
Merged

feat(hp): cloud-only enforcement + cloud iters/frames reach the Run Inspector#219
jack-champagne merged 7 commits into
mainfrom
rchari/hp-cloud-only

Conversation

@Rchari1

@Rchari1Rchari1 commented Jul 28, 2026

Copy link
Copy Markdown
Member

Six commits, one thread: make the paid tier actually run in the cloud, and make what it produces actually reach the Run Inspector. 18 files, +673/−116.


1. Piccolissimo + Altissimo is cloud-only, and now enforced

Selecting the HP tier was supposed to mean "runs in Harmoniqs Cloud." In practice the agent kept dispatching it locally, where the laptop precompiled the whole HP stack (IPOPT included) until amico-run's process-group timeout SIGTERMed julia mid-precompile.

The API-key prompt and entitlement flip were already built and work (amicode#200/#167) — untouched here. Three other things allowed local dispatch:

  1. Nothing refused a local HP launch. The tier="hpc" gate is solid but only fires on a spec that saystier="hpc", and runGate only runs for --spec launches. A bare amico-run script.jl never reached it.
  2. The entitlement that unlocks the tier unlocked the failure — HP grants issimo, so the import scan admitted a local using Piccolissimo.
  3. The agent got contradictory orderssolverModeSection() said "launch in the cloud" while the routing section beside it said "routing is PER-SOLVE and EXPLICIT, default to local." Given a cloud-only tier and an instruction to default local, it defaulted local.

Fix, in the two-layer shape the tier already uses:

  • Enforcementlaunch.ts refuses --executor local while HP is selected, at the one choke point every run passes through, before the gate, so it covers spec and no-spec runs alike. Reads solver mode as status only, never a token, and fails safe to piccolo on a missing or corrupt file: this value can only ever refuse a run, so a bad read must never invent hp and block ordinary free-tier work.
  • Guidance — the routing section states the cloud-only contract instead of asking a routing question, and AGENTS.md step 5 makes the injected section authoritative over its local default. Piccolo sessions stay byte-identical (the section is still "" unless mode is hpand the cloud is connected).

The estimate keeps its reporting role and loses its deciding role: an estimate that fits in local RAM does not make an HP solve local.

Service is named Harmoniqs Cloud in every user-visible string. Wire id stays company-compute — renaming breaks the credential route, status cache, and CONNECTION_IDS for no visible gain.


2. Cloud iters and frames reach the Run Inspector

The client chain was fully built — poll the cloud, synthesize AMICODE_ITER into run.log, write frames, touch mtime for the stall logic. It read the wrong fields:

client readservice returns
statsbody.iters{task_id, **stats**[], submitter}
framespng_base64{task_id, iter, key, **url**, submitter}

Both reads sit in best-effort try/catch ("stats are advisory"), so it failed silentlyundefined ?? [] iterated zero times and run.log stayed 0 bytes. Confirmed on task 419a57e6: 60 IPOPT iterations and 11 frames banked to S3, empty inspector.

Why nobody caught it:fake_cloud.ts served the shapes the client read, not the shapes the service returns. Every test passed against a fake that agreed with the bug. So the fake is corrected first, and two tests pin the live payloads by exact key set — including that the presigned URL is fetchable with no auth header, because the signature is the credential.

Frame filenames also went 3-digit → 5-digit: iter_00007.png is what both the S3 layout and the local julia solve write. The old name matched neither, so cloud and local frames landed under two schemes in one run dir.

A third drift, found while proving the above: the poller only json.loads an AMICODE_ITER payload starting with {, so template-emitted key=value lines come back as {raw: "iter=7 f=…"}. The client keyed on it.iter and skipped every one as NaN — invisible because the smoke test seeds JSON. Now reconstructs from either shape and drops malformed records rather than emitting NaN.


3. The cloud can finally populate /stats

/solves/<id>/stats parses AMICODE_ITER out of run.log in the artifact prefix, and the runner's sidecar populates that prefix with aws s3 sync . — whatever is in the solve cwd. But julia's stdout on the runner goes to the SSM command stream, so run.log was never written there: nothing to sync, stats: [], empty inspector. The template's own comment ("AMICODE_PULSE lines on stdout (→ run.log)") was true locally and false in the cloud.

Telemetry now routes through emit(), which also appends to run.logonly when TASK_ID is set (the runner exports it). Locally amico-run's executor already writes run.log from stdout, so appending unconditionally would double every line and the inspector would count each iteration twice.

No terraform change and no AMI rebake: the solve script is uploaded per submission (<task_id>-solve.jl), so this ships immediately.


4. Altissimo streams like IPOPT

Both channels were bolted to IPOPT — frames off IpoptOptions.intermediate_callback, the iter line off a callback reading IpoptOptimizerState. Altissimo has neither; its only hook is callback on optimize!, arriving as (x, info). So an Altissimo solve lost the frames too, leaving the inspector completely dark rather than merely numberless.

SOLVER = :ipopt | :altissimo re-hangs both channels. xis the primal, so pulse_emit's solver-agnostic (primal, iter) contract takes it unchanged — same frames, same AMICODE_PULSE, same cooperative STOP (returning false stops an Altissimo solve exactly as it stops an Ipopt one). inf_pr/inf_du come from the callback tuple when present (Altissimo#414, merged) and are otherwise derived from eq_viol/ineq_viol/kkt_error, so it works on an older Altissimo instead of emitting NaN.

Two traps verified against Piccolissimo main and handled:

  • the qcp-level solve! forwards kwargs... to the prob-level method, which takes callback explicitly — so the wiring does reach optimize!;
  • but max_iter lands in that kwargs... and is silently dropped, so the budget now rides AltissimoOptions(max_outer_iter = max_iter). Left alone it would have quietly run Altissimo's default 20 outer iterations while the FILL-IN block said 60.

The guidance names SOLVER = :altissimo as the one-line switch and forbids hand-rolling the solve call, stating each trap so the instruction carries its own justification.


Testing

  • amico-run: 938 passed, 12 skipped.
  • extension: 778 passed, 3 skipped (the full CI set, --exclude '**/slow/**').
  • tsc --noEmit clean in both packages.
  • Live, against the real staging service: frames fetched from a presigned URL and landed locally with 5-digit names (task 0fccbbf9); template runs green end to end (fidelity 0.99997, 60 iterations); local run writes norun.log (no duplication), and with TASK_ID set run.log appears with iter lines in exactly the format the poller parses.
  • One incidental fix that matters for CI: the CLI tests were inheriting the developer's~/.amico/amicode/solver-mode.json. On an hp machine nine local-solve tests failed — on correct behaviour. They now spawn with a hermetic ops dir.

CI caught the one consumer I missed on the frame rename (the Δ9 remote state-machine test); the assertion above it — run.log contains iter=7 — passed, which is independent confirmation in a second harness that the stats fix delivers iters end to end.


What this does NOT fix

  • Altissimo on the cloud. The runner AMI bakes a Piccolissimo/Altissimo predating callback support, so it emits nothing there until Altissimo#414 is released and the image rebuilt. The template is ready for that moment.
  • The Altissimo code path has never been executed. It is written against the verified contract and parses, but the local checkouts here are 287/324 commits behind and cannot run it. Worth exercising on a current env before anyone demos it.
  • Cold start. ~9–10 min from submit to first frame, for ~55s of compute. Real, and unaddressed here.
  • Existing agent-authored scripts in ~/.amico/problems/ predate emit(), so they still show frames and no numbers until re-authored from the updated template.

solverModeSection is exported to make the guidance testable (matching its already-exported sibling routingSection). The same one-line export is on #225 — identical change, so either merge order is fine.

Reviewer note

This narrows behaviour: a local Piccolissimo run that "worked" before — slowly, usually fatally — is now refused outright. That is the intent of a cloud-only paid tier, but it is a real change for anyone relying on the local path, and the way out is one click on the solver control.

…forced
Selecting Piccolissimo + Altissimo was supposed to mean "this solve runs in
Harmoniqs Cloud". In practice the agent kept dispatching it LOCALLY: the laptop
precompiled the whole HP stack (IPOPT included) until amico-run's process-group
timeout SIGTERMed Julia mid-precompile.
Three things allowed that, none of them the pieces people assumed were missing
(the API-key prompt and the entitlement flip were already built and work):
1. Nothing refused a local HP launch. The gate's tier=hpc rules are solid, but
they only fire on a spec that SAYS tier="hpc" — and runGate only runs for
--spec launches at all. A bare `amico-run script.jl` never reached them.
2. Selecting HP grants the `issimo` entitlement, so the import scan happily
admits a local `using Piccolissimo`. The entitlement that unlocks the tier
was also unlocking the failure mode.
3. The agent was told two contradictory things. solverModeSection() said "launch
HP solves in the cloud" while the routing section beside it said "routing is
PER-SOLVE and EXPLICIT, you confirm where EVERY solve runs, default local" —
and the base AGENTS.md step 5 said the same. Given a cloud-only tier and an
instruction to default local, it defaulted local.
The fix, in the same two-layer shape the tier already uses:
- ENFORCEMENT: amico-run refuses `--executor local` while HP is selected, at the
one choke point every run passes through (launch.ts, before the gate), so it
covers spec and no-spec runs alike. Reads the extension's solver-mode.json —
status only, never a token — and fails SAFE to piccolo on an absent or corrupt
file, so a fresh install and every free-tier local run behave exactly as now.
- GUIDANCE: the routing section now states the cloud-only contract instead of
asking a routing question, and AGENTS.md step 5 makes the injected section
authoritative over its local default. Piccolo sessions are untouched: the
section is still "" unless mode is hp AND the cloud is connected.
The estimate keeps its reporting role and loses its deciding role — an estimate
that fits in local RAM does not make an HP solve local.
Also names the service "Harmoniqs Cloud" everywhere a user can see it (gate
refusals, routing prose, AGENTS.md, the HP row's tooltips) instead of the
internal "company compute" / bare "the cloud". The wire id stays
`company-compute` — renaming it would break the credential route, the status
cache, and the fork's CONNECTION_IDS for no user-visible gain.
Tests: 9 new (the reader's fail-safe directions; the refusal, including on the
no-spec path and that it leaves no run dir; that a REMOTE HP launch still
completes, against FakeCloud) + the routing/AGENTS contract updated to pin the
new copy, with an explicit regression test that the per-solve question does not
come back. amico-run 934 passed, extension suite green apart from two live-model
E2Es (interview_e2e, scores_e2e) that fail identically with this change stashed —
pre-existing, tracked separately.
Incidental but load-bearing: the CLI tests were inheriting the DEVELOPER's
~/.amico/amicode/solver-mode.json, so on an hp machine nine local-solve tests
failed on correct behaviour. They now spawn with a hermetic ops dir.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Rchari1and others added 4 commits July 28, 2026 20:39
A cloud solve produced an empty Run Inspector. The client-side chain was fully
built — RemoteExecutor polls the cloud, synthesizes AMICODE_ITER lines into
run.log, writes frames, and touches run.log's mtime for the stall logic — but it
read the WRONG FIELDS, so it dropped everything:
stats client read `body.iters` · service returns `{task_id, stats[], submitter}`
frames client read `png_base64` · service returns `{task_id, iter, key, url, submitter}`
Both reads sit inside best-effort try/catch blocks ("stats are advisory"), so
the failure was completely silent: `undefined ?? []` iterated zero times, run.log
stayed 0 bytes, and the Inspector had nothing to tail. Confirmed against task
419a57e6 on staging, which ran 60 IPOPT iterations and banked 11 frames to S3
while the local run.log stayed empty.
The reason nobody caught it: fake_cloud.ts served the shapes the CLIENT read,
not the shapes the SERVICE returns. Every test passed against a fake that agreed
with the bug. That is the real defect here, so the fake is corrected first and
two tests now pin the live payloads by exact key set — a fake that mirrors the
client proves nothing.
- stats: read `stats`, falling back to `iters` so an older runner still works.
- frames: fetch the presigned url (no auth header — the signature IS the
credential) and keep the base64 lane for older runners. FakeCloud now serves
artifact bytes from a route placed BEFORE its auth guard, matching S3.
- frame filenames go 3-digit → 5-digit: iter_00007.png is what both the S3 layout
and the local Julia solve write, so cloud and local frames no longer land under
two different schemes in one run dir.
Also: the HP solver-mode guidance now covers the Altissimo backend. IPOPT stays
the default because it is what streams telemetry; Altissimo is used on request,
but the agent must state the trade first — Piccolissimo's solve!(::AltissimoOptions)
does not forward a caller callback to Altissimo.optimize!, and there is no
intermediate_callback on that path, so an Altissimo run emits no frames and no
AMICODE_ITER and the Inspector stays empty until it finishes. Never switch
silently, never claim live iterations on it.
amico-run: 936 passed. extension: 62 passed in the touched suites, tsc clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CI caught the one consumer I missed when frame filenames went 3-digit → 5-digit.
The Δ9 test waited on iter_007.png and timed out; the assertion above it —
run.log contains "iter=7" — PASSED, which is independent confirmation in a second
harness that the stats-field fix delivers iters end to end.
Also corrects solver-mode guidance shipped earlier today. It asserted that
Piccolissimo's solve!(::AltissimoOptions) does not forward a caller callback —
taken from a report without checking. That is true of the local checkout (287
commits behind) but false of main, which accepts `callback` and forwards it to
Altissimo.optimize!. The guidance now says live iterations depend on the INSTALLED
version and tells the agent not to promise iterations it has not observed.
extension: 776 passed, 3 skipped (the full CI set).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two gaps stood between a cloud solve and a populated Run Inspector. Both are in
the solve template, which ships PER SUBMISSION (the client uploads
<task_id>-solve.jl), so neither needs an AMI rebake.
1. NOTHING WROTE run.log IN THE CLOUD.
/solves/<id>/stats parses AMICODE_ITER lines out of run.log in the artifact
prefix, and the runner's sidecar populates that prefix with `aws s3 sync .` —
whatever is in the solve cwd. But julia's stdout on the runner goes to the SSM
command stream, so run.log was never written there: nothing to sync, stats: [],
empty inspector. The template's own comment ("AMICODE_PULSE lines on stdout (→
run.log)") was true locally and false in the cloud.
Every AMICODE_* line now goes through emit(), which also appends to run.log —
but ONLY when TASK_ID is set (the runner exports it). Locally amico-run's
executor already writes run.log from stdout, so appending unconditionally would
double every line and the inspector would count each iteration twice.
Verified: local run writes no run.log (11 frames, result.toml, fidelity
0.99997); with TASK_ID set, run.log appears with AMICODE_ITER lines in the
exact format the poller parses.
2. THE TELEMETRY WAS BOLTED TO IPOPT.
Frames came off IpoptOptions.intermediate_callback and the iter line off a
callback reading IpoptOptimizerState. Altissimo has neither: its only hook is
`callback` on optimize! (forwarded by Piccolissimo's solve!(::AltissimoOptions))
and it arrives as (x, info). So an Altissimo solve lost the frames TOO, leaving
the inspector completely dark rather than merely numberless.
SOLVER = :ipopt | :altissimo now selects the backend and re-hangs both channels.
`x` is the primal, so pulse_emit's solver-agnostic (primal, iter) contract takes
it unchanged — same frames, same AMICODE_PULSE, same cooperative STOP.
inf_pr/inf_du come from the callback tuple when present (Altissimo#414) and are
otherwise derived from eq_viol/ineq_viol/kkt_error, so it works on an older
Altissimo too instead of emitting NaN.
And a third drift found while proving (1): the poller only json.loads an
AMICODE_ITER payload starting with "{", so template-emitted key=value lines come
back as {raw: "iter=7 f=…"}. The client keyed on it.iter and skipped every one as
NaN — invisible because the smoke test seeds JSON. It now reconstructs the line
from either shape, and drops malformed records rather than emitting NaN.
Still NOT solved by this: Altissimo on the cloud. The runner AMI bakes a
Piccolissimo/Altissimo predating callback support, so it will emit nothing there
until Altissimo#414 is released and the image rebuilt. The template is ready for
that moment and works today on IPOPT (cloud) and both backends locally.
amico-run 938 passed · extension 776 passed · template runs green end to end.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
solve!(::AltissimoOptions) forwards a HARDCODED kwarg list to
Altissimo.optimize! and swallows the rest, so `max_iter = 60` passed to solve!
never arrived: the solve quietly ran Altissimo's default 20 outer iterations
while the FILL-IN block said 60. Silent, not an error — the worst kind.
Budget now rides AltissimoOptions(max_outer_iter = max_iter). Verified against
Piccolissimo main: the qcp-level solve! forwards kwargs... to the prob-level
method, which takes `callback` explicitly, so the callback wiring does reach
optimize! — it is only the iteration budget that was being dropped.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The template gained a SOLVER flag but nothing told the agent it exists, so an
agent asked for Altissimo would hand-roll a solve call — and silently lose both
telemetry channels plus the iteration budget:
- frames come off IpoptOptions.intermediate_callback, which AltissimoOptions
has no equivalent of, so a hand-written call leaves the Run Inspector fully
dark rather than merely numberless;
- a `max_iter` passed to solve!(::AltissimoOptions) lands in kwargs... and is
dropped, so the solve quietly runs Altissimo's default 20 outer iterations;
- inf_pr/inf_du need deriving from eq_viol/ineq_viol/kkt_error on Altissimo
builds that predate #414.
The template already handles all three. The guidance now says the switch is ONE
line — `SOLVER = :altissimo` in the FILL-IN block — and states each trap, so the
instruction carries its own justification rather than reading as arbitrary.
solverModeSection is exported to make it testable, matching its already-exported
sibling routingSection. (The same one-line export is on #225; identical change,
so either merge order is fine.)
extension: 778 passed, tsc clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@Rchari1Rchari1 changed the title feat(hp): Piccolissimo + Altissimo is cloud-only, and now actually enforcedfeat(hp): cloud-only enforcement + cloud iters/frames reach the Run InspectorJul 29, 2026
# Conflicts:
#	packages/extension/src/opencode_config.ts
#	packages/extension/test/agents_md.test.ts
@jack-champagne
jack-champagneforce-pushed the rchari/hp-cloud-only branch 2 times, most recently from 54f4275 to 8aa362fCompareJuly 29, 2026 04:24
@jack-champagne
jack-champagne merged commit 199b23d into mainJul 29, 2026
10 checks passed
jeonghun-jj-lee pushed a commit that referenced this pull request Aug 21, 2026
Add WorkspaceTreeProvider — renders all workspace folders as collapsible
roots, expands recursively via vscode.workspace.fs.readDirectory(),
respects files.exclude, shows theme icons via resourceUri, opens files
on click, context menus (Copy Path, Reveal), and live-updates via
FileSystemWatcher. Replaces amicode.armonia placeholder; Catalog already
removed in #457.
package.json: rename amicode.armonia → amicode.workspace ("Workspace"),
keep runInspector, update activationEvents onView:workspace, add
view/item/context for workspace files.
Follows opencode#215 decision surface: Location.Ref.directories carries
all roots, no external-directory prompts, instructions stacked per dir.
Pairs with opencode PR #219 (engine: schema + boundary + env + instruction
multi-root + DB).
jeonghun-jj-lee added a commit that referenced this pull request Aug 22, 2026
…, #215)
* fix(ui): remove pulse catalog + CATALOG sidebar tab and widgets (#457)
Purge the legacy UX3 session catalog UI that predates the vault-backed
CatalogStore (Q94/Q95, packages/amico-run). The CATALOG activity-bar tab
(SessionCatalogTree → amicode.catalog, amicode.catalog.save/refresh/remove,
catalogCard webview + catalogcard component) is superseded by the mount stack
+ pack interface (WS1 #391) and is also slated for removal in opencode#215 AC7.
- package.json: drop amicode.catalog view + catalog commands/menus
- trees.ts: delete SessionCatalogTree/CATALOG_KEY, keep armonia placeholder
(will be replaced by Workspace tree per 215)
- extension.ts: remove registerCatalogCard + catalog save/refresh/remove
wiring, simplify amicode.savePulse to file-only Save dialog,
drop demo promote-to-catalog prompt, remove runs_manager promote-to-
catalog prompt (now info-only)
- esbuild.config.mjs: drop catalog_card_webview bundle
- delete catalog_card_shell/webview, media catalogcard, test suite
Pulse save stays via Save to file… → savePulseTo; vault CatalogStore
(packages/amico-run) untouched. Closes#457
* feat(workspace): Workspace sidebar for multi-root (opencode#215 AC6)
Add WorkspaceTreeProvider — renders all workspace folders as collapsible
roots, expands recursively via vscode.workspace.fs.readDirectory(),
respects files.exclude, shows theme icons via resourceUri, opens files
on click, context menus (Copy Path, Reveal), and live-updates via
FileSystemWatcher. Replaces amicode.armonia placeholder; Catalog already
removed in #457.
package.json: rename amicode.armonia → amicode.workspace ("Workspace"),
keep runInspector, update activationEvents onView:workspace, add
view/item/context for workspace files.
Follows opencode#215 decision surface: Location.Ref.directories carries
all roots, no external-directory prompts, instructions stacked per dir.
Pairs with opencode PR #219 (engine: schema + boundary + env + instruction
multi-root + DB).
* fix(workspace): full context menus for Workspace view (opencode#215 AC6)
Add New File/Folder, Rename, Delete, Copy Path/Relative, Reveal,
Open in Terminal to amicode.workspace viewItem context. Ensures
AC6 full context-menu coverage; Run Inspector stays as separate
webview (amicode.runInspector) not a directory in the tree.
* fix(workspace): only hide .git directory, not .gitignore/.github/etc
* feat(workspace): Chat with Amico button, full context menus, Remove from Workspace
- Add 'Chat with Amico' as first tree item with custom yellow SVG icon
that mutes (gray) when a chat tab is open
- Register custom workspace commands (newFile, newFolder, rename, delete,
copyPath, copyRelativePath, revealInOS, openInTerminal, openToSide,
removeFromWorkspace, addFolder) since built-in explorer.* commands
don't fire in custom tree views
- Remove stale amicode.runInspector webview declaration (provider was
deleted in PR #351, only the package.json entry remained)
- Add viewsWelcome for empty workspace state
- Listen to onDidChangeWorkspaceFolders to keep tree in sync
- Add ChatPanel.onLiveChange callback for cross-component state tracking
- Fix credential_scanner e2e test: guard opencode-provider assertion
(machine may have creds under different provider names)
- Add comprehensive test suite for workspace tree and context-menu commands
(24 tests covering tree rendering, all commands, muted state)
---------
Co-authored-by: Raghav Chari <raghavchari2021@gmail.com>
Sign up for freeto 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.

2 participants

@Rchari1@jack-champagne
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

feat(hp): cloud-only enforcement + cloud iters/frames reach the Run Inspector - #219

Merged
jack-champagne merged 7 commits into
mainfrom
rchari/hp-cloud-only
Jul 29, 2026
Merged

feat(hp): cloud-only enforcement + cloud iters/frames reach the Run Inspector#219
jack-champagne merged 7 commits into
mainfrom
rchari/hp-cloud-only

Conversation

@Rchari1

@Rchari1Rchari1 commented Jul 28, 2026

Copy link
Copy Markdown
Member

Six commits, one thread: make the paid tier actually run in the cloud, and make what it produces actually reach the Run Inspector. 18 files, +673/−116.


1. Piccolissimo + Altissimo is cloud-only, and now enforced

Selecting the HP tier was supposed to mean "runs in Harmoniqs Cloud." In practice the agent kept dispatching it locally, where the laptop precompiled the whole HP stack (IPOPT included) until amico-run's process-group timeout SIGTERMed julia mid-precompile.

The API-key prompt and entitlement flip were already built and work (amicode#200/#167) — untouched here. Three other things allowed local dispatch:

  1. Nothing refused a local HP launch. The tier="hpc" gate is solid but only fires on a spec that saystier="hpc", and runGate only runs for --spec launches. A bare amico-run script.jl never reached it.
  2. The entitlement that unlocks the tier unlocked the failure — HP grants issimo, so the import scan admitted a local using Piccolissimo.
  3. The agent got contradictory orderssolverModeSection() said "launch in the cloud" while the routing section beside it said "routing is PER-SOLVE and EXPLICIT, default to local." Given a cloud-only tier and an instruction to default local, it defaulted local.

Fix, in the two-layer shape the tier already uses:

  • Enforcementlaunch.ts refuses --executor local while HP is selected, at the one choke point every run passes through, before the gate, so it covers spec and no-spec runs alike. Reads solver mode as status only, never a token, and fails safe to piccolo on a missing or corrupt file: this value can only ever refuse a run, so a bad read must never invent hp and block ordinary free-tier work.
  • Guidance — the routing section states the cloud-only contract instead of asking a routing question, and AGENTS.md step 5 makes the injected section authoritative over its local default. Piccolo sessions stay byte-identical (the section is still "" unless mode is hpand the cloud is connected).

The estimate keeps its reporting role and loses its deciding role: an estimate that fits in local RAM does not make an HP solve local.

Service is named Harmoniqs Cloud in every user-visible string. Wire id stays company-compute — renaming breaks the credential route, status cache, and CONNECTION_IDS for no visible gain.


2. Cloud iters and frames reach the Run Inspector

The client chain was fully built — poll the cloud, synthesize AMICODE_ITER into run.log, write frames, touch mtime for the stall logic. It read the wrong fields:

client readservice returns
statsbody.iters{task_id, **stats**[], submitter}
framespng_base64{task_id, iter, key, **url**, submitter}

Both reads sit in best-effort try/catch ("stats are advisory"), so it failed silentlyundefined ?? [] iterated zero times and run.log stayed 0 bytes. Confirmed on task 419a57e6: 60 IPOPT iterations and 11 frames banked to S3, empty inspector.

Why nobody caught it:fake_cloud.ts served the shapes the client read, not the shapes the service returns. Every test passed against a fake that agreed with the bug. So the fake is corrected first, and two tests pin the live payloads by exact key set — including that the presigned URL is fetchable with no auth header, because the signature is the credential.

Frame filenames also went 3-digit → 5-digit: iter_00007.png is what both the S3 layout and the local julia solve write. The old name matched neither, so cloud and local frames landed under two schemes in one run dir.

A third drift, found while proving the above: the poller only json.loads an AMICODE_ITER payload starting with {, so template-emitted key=value lines come back as {raw: "iter=7 f=…"}. The client keyed on it.iter and skipped every one as NaN — invisible because the smoke test seeds JSON. Now reconstructs from either shape and drops malformed records rather than emitting NaN.


3. The cloud can finally populate /stats

/solves/<id>/stats parses AMICODE_ITER out of run.log in the artifact prefix, and the runner's sidecar populates that prefix with aws s3 sync . — whatever is in the solve cwd. But julia's stdout on the runner goes to the SSM command stream, so run.log was never written there: nothing to sync, stats: [], empty inspector. The template's own comment ("AMICODE_PULSE lines on stdout (→ run.log)") was true locally and false in the cloud.

Telemetry now routes through emit(), which also appends to run.logonly when TASK_ID is set (the runner exports it). Locally amico-run's executor already writes run.log from stdout, so appending unconditionally would double every line and the inspector would count each iteration twice.

No terraform change and no AMI rebake: the solve script is uploaded per submission (<task_id>-solve.jl), so this ships immediately.


4. Altissimo streams like IPOPT

Both channels were bolted to IPOPT — frames off IpoptOptions.intermediate_callback, the iter line off a callback reading IpoptOptimizerState. Altissimo has neither; its only hook is callback on optimize!, arriving as (x, info). So an Altissimo solve lost the frames too, leaving the inspector completely dark rather than merely numberless.

SOLVER = :ipopt | :altissimo re-hangs both channels. xis the primal, so pulse_emit's solver-agnostic (primal, iter) contract takes it unchanged — same frames, same AMICODE_PULSE, same cooperative STOP (returning false stops an Altissimo solve exactly as it stops an Ipopt one). inf_pr/inf_du come from the callback tuple when present (Altissimo#414, merged) and are otherwise derived from eq_viol/ineq_viol/kkt_error, so it works on an older Altissimo instead of emitting NaN.

Two traps verified against Piccolissimo main and handled:

  • the qcp-level solve! forwards kwargs... to the prob-level method, which takes callback explicitly — so the wiring does reach optimize!;
  • but max_iter lands in that kwargs... and is silently dropped, so the budget now rides AltissimoOptions(max_outer_iter = max_iter). Left alone it would have quietly run Altissimo's default 20 outer iterations while the FILL-IN block said 60.

The guidance names SOLVER = :altissimo as the one-line switch and forbids hand-rolling the solve call, stating each trap so the instruction carries its own justification.


Testing

  • amico-run: 938 passed, 12 skipped.
  • extension: 778 passed, 3 skipped (the full CI set, --exclude '**/slow/**').
  • tsc --noEmit clean in both packages.
  • Live, against the real staging service: frames fetched from a presigned URL and landed locally with 5-digit names (task 0fccbbf9); template runs green end to end (fidelity 0.99997, 60 iterations); local run writes norun.log (no duplication), and with TASK_ID set run.log appears with iter lines in exactly the format the poller parses.
  • One incidental fix that matters for CI: the CLI tests were inheriting the developer's~/.amico/amicode/solver-mode.json. On an hp machine nine local-solve tests failed — on correct behaviour. They now spawn with a hermetic ops dir.

CI caught the one consumer I missed on the frame rename (the Δ9 remote state-machine test); the assertion above it — run.log contains iter=7 — passed, which is independent confirmation in a second harness that the stats fix delivers iters end to end.


What this does NOT fix

  • Altissimo on the cloud. The runner AMI bakes a Piccolissimo/Altissimo predating callback support, so it emits nothing there until Altissimo#414 is released and the image rebuilt. The template is ready for that moment.
  • The Altissimo code path has never been executed. It is written against the verified contract and parses, but the local checkouts here are 287/324 commits behind and cannot run it. Worth exercising on a current env before anyone demos it.
  • Cold start. ~9–10 min from submit to first frame, for ~55s of compute. Real, and unaddressed here.
  • Existing agent-authored scripts in ~/.amico/problems/ predate emit(), so they still show frames and no numbers until re-authored from the updated template.

solverModeSection is exported to make the guidance testable (matching its already-exported sibling routingSection). The same one-line export is on #225 — identical change, so either merge order is fine.

Reviewer note

This narrows behaviour: a local Piccolissimo run that "worked" before — slowly, usually fatally — is now refused outright. That is the intent of a cloud-only paid tier, but it is a real change for anyone relying on the local path, and the way out is one click on the solver control.

…forced
Selecting Piccolissimo + Altissimo was supposed to mean "this solve runs in
Harmoniqs Cloud". In practice the agent kept dispatching it LOCALLY: the laptop
precompiled the whole HP stack (IPOPT included) until amico-run's process-group
timeout SIGTERMed Julia mid-precompile.
Three things allowed that, none of them the pieces people assumed were missing
(the API-key prompt and the entitlement flip were already built and work):
1. Nothing refused a local HP launch. The gate's tier=hpc rules are solid, but
they only fire on a spec that SAYS tier="hpc" — and runGate only runs for
--spec launches at all. A bare `amico-run script.jl` never reached them.
2. Selecting HP grants the `issimo` entitlement, so the import scan happily
admits a local `using Piccolissimo`. The entitlement that unlocks the tier
was also unlocking the failure mode.
3. The agent was told two contradictory things. solverModeSection() said "launch
HP solves in the cloud" while the routing section beside it said "routing is
PER-SOLVE and EXPLICIT, you confirm where EVERY solve runs, default local" —
and the base AGENTS.md step 5 said the same. Given a cloud-only tier and an
instruction to default local, it defaulted local.
The fix, in the same two-layer shape the tier already uses:
- ENFORCEMENT: amico-run refuses `--executor local` while HP is selected, at the
one choke point every run passes through (launch.ts, before the gate), so it
covers spec and no-spec runs alike. Reads the extension's solver-mode.json —
status only, never a token — and fails SAFE to piccolo on an absent or corrupt
file, so a fresh install and every free-tier local run behave exactly as now.
- GUIDANCE: the routing section now states the cloud-only contract instead of
asking a routing question, and AGENTS.md step 5 makes the injected section
authoritative over its local default. Piccolo sessions are untouched: the
section is still "" unless mode is hp AND the cloud is connected.
The estimate keeps its reporting role and loses its deciding role — an estimate
that fits in local RAM does not make an HP solve local.
Also names the service "Harmoniqs Cloud" everywhere a user can see it (gate
refusals, routing prose, AGENTS.md, the HP row's tooltips) instead of the
internal "company compute" / bare "the cloud". The wire id stays
`company-compute` — renaming it would break the credential route, the status
cache, and the fork's CONNECTION_IDS for no user-visible gain.
Tests: 9 new (the reader's fail-safe directions; the refusal, including on the
no-spec path and that it leaves no run dir; that a REMOTE HP launch still
completes, against FakeCloud) + the routing/AGENTS contract updated to pin the
new copy, with an explicit regression test that the per-solve question does not
come back. amico-run 934 passed, extension suite green apart from two live-model
E2Es (interview_e2e, scores_e2e) that fail identically with this change stashed —
pre-existing, tracked separately.
Incidental but load-bearing: the CLI tests were inheriting the DEVELOPER's
~/.amico/amicode/solver-mode.json, so on an hp machine nine local-solve tests
failed on correct behaviour. They now spawn with a hermetic ops dir.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Rchari1and others added 4 commits July 28, 2026 20:39
A cloud solve produced an empty Run Inspector. The client-side chain was fully
built — RemoteExecutor polls the cloud, synthesizes AMICODE_ITER lines into
run.log, writes frames, and touches run.log's mtime for the stall logic — but it
read the WRONG FIELDS, so it dropped everything:
stats client read `body.iters` · service returns `{task_id, stats[], submitter}`
frames client read `png_base64` · service returns `{task_id, iter, key, url, submitter}`
Both reads sit inside best-effort try/catch blocks ("stats are advisory"), so
the failure was completely silent: `undefined ?? []` iterated zero times, run.log
stayed 0 bytes, and the Inspector had nothing to tail. Confirmed against task
419a57e6 on staging, which ran 60 IPOPT iterations and banked 11 frames to S3
while the local run.log stayed empty.
The reason nobody caught it: fake_cloud.ts served the shapes the CLIENT read,
not the shapes the SERVICE returns. Every test passed against a fake that agreed
with the bug. That is the real defect here, so the fake is corrected first and
two tests now pin the live payloads by exact key set — a fake that mirrors the
client proves nothing.
- stats: read `stats`, falling back to `iters` so an older runner still works.
- frames: fetch the presigned url (no auth header — the signature IS the
credential) and keep the base64 lane for older runners. FakeCloud now serves
artifact bytes from a route placed BEFORE its auth guard, matching S3.
- frame filenames go 3-digit → 5-digit: iter_00007.png is what both the S3 layout
and the local Julia solve write, so cloud and local frames no longer land under
two different schemes in one run dir.
Also: the HP solver-mode guidance now covers the Altissimo backend. IPOPT stays
the default because it is what streams telemetry; Altissimo is used on request,
but the agent must state the trade first — Piccolissimo's solve!(::AltissimoOptions)
does not forward a caller callback to Altissimo.optimize!, and there is no
intermediate_callback on that path, so an Altissimo run emits no frames and no
AMICODE_ITER and the Inspector stays empty until it finishes. Never switch
silently, never claim live iterations on it.
amico-run: 936 passed. extension: 62 passed in the touched suites, tsc clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CI caught the one consumer I missed when frame filenames went 3-digit → 5-digit.
The Δ9 test waited on iter_007.png and timed out; the assertion above it —
run.log contains "iter=7" — PASSED, which is independent confirmation in a second
harness that the stats-field fix delivers iters end to end.
Also corrects solver-mode guidance shipped earlier today. It asserted that
Piccolissimo's solve!(::AltissimoOptions) does not forward a caller callback —
taken from a report without checking. That is true of the local checkout (287
commits behind) but false of main, which accepts `callback` and forwards it to
Altissimo.optimize!. The guidance now says live iterations depend on the INSTALLED
version and tells the agent not to promise iterations it has not observed.
extension: 776 passed, 3 skipped (the full CI set).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two gaps stood between a cloud solve and a populated Run Inspector. Both are in
the solve template, which ships PER SUBMISSION (the client uploads
<task_id>-solve.jl), so neither needs an AMI rebake.
1. NOTHING WROTE run.log IN THE CLOUD.
/solves/<id>/stats parses AMICODE_ITER lines out of run.log in the artifact
prefix, and the runner's sidecar populates that prefix with `aws s3 sync .` —
whatever is in the solve cwd. But julia's stdout on the runner goes to the SSM
command stream, so run.log was never written there: nothing to sync, stats: [],
empty inspector. The template's own comment ("AMICODE_PULSE lines on stdout (→
run.log)") was true locally and false in the cloud.
Every AMICODE_* line now goes through emit(), which also appends to run.log —
but ONLY when TASK_ID is set (the runner exports it). Locally amico-run's
executor already writes run.log from stdout, so appending unconditionally would
double every line and the inspector would count each iteration twice.
Verified: local run writes no run.log (11 frames, result.toml, fidelity
0.99997); with TASK_ID set, run.log appears with AMICODE_ITER lines in the
exact format the poller parses.
2. THE TELEMETRY WAS BOLTED TO IPOPT.
Frames came off IpoptOptions.intermediate_callback and the iter line off a
callback reading IpoptOptimizerState. Altissimo has neither: its only hook is
`callback` on optimize! (forwarded by Piccolissimo's solve!(::AltissimoOptions))
and it arrives as (x, info). So an Altissimo solve lost the frames TOO, leaving
the inspector completely dark rather than merely numberless.
SOLVER = :ipopt | :altissimo now selects the backend and re-hangs both channels.
`x` is the primal, so pulse_emit's solver-agnostic (primal, iter) contract takes
it unchanged — same frames, same AMICODE_PULSE, same cooperative STOP.
inf_pr/inf_du come from the callback tuple when present (Altissimo#414) and are
otherwise derived from eq_viol/ineq_viol/kkt_error, so it works on an older
Altissimo too instead of emitting NaN.
And a third drift found while proving (1): the poller only json.loads an
AMICODE_ITER payload starting with "{", so template-emitted key=value lines come
back as {raw: "iter=7 f=…"}. The client keyed on it.iter and skipped every one as
NaN — invisible because the smoke test seeds JSON. It now reconstructs the line
from either shape, and drops malformed records rather than emitting NaN.
Still NOT solved by this: Altissimo on the cloud. The runner AMI bakes a
Piccolissimo/Altissimo predating callback support, so it will emit nothing there
until Altissimo#414 is released and the image rebuilt. The template is ready for
that moment and works today on IPOPT (cloud) and both backends locally.
amico-run 938 passed · extension 776 passed · template runs green end to end.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
solve!(::AltissimoOptions) forwards a HARDCODED kwarg list to
Altissimo.optimize! and swallows the rest, so `max_iter = 60` passed to solve!
never arrived: the solve quietly ran Altissimo's default 20 outer iterations
while the FILL-IN block said 60. Silent, not an error — the worst kind.
Budget now rides AltissimoOptions(max_outer_iter = max_iter). Verified against
Piccolissimo main: the qcp-level solve! forwards kwargs... to the prob-level
method, which takes `callback` explicitly, so the callback wiring does reach
optimize! — it is only the iteration budget that was being dropped.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The template gained a SOLVER flag but nothing told the agent it exists, so an
agent asked for Altissimo would hand-roll a solve call — and silently lose both
telemetry channels plus the iteration budget:
- frames come off IpoptOptions.intermediate_callback, which AltissimoOptions
has no equivalent of, so a hand-written call leaves the Run Inspector fully
dark rather than merely numberless;
- a `max_iter` passed to solve!(::AltissimoOptions) lands in kwargs... and is
dropped, so the solve quietly runs Altissimo's default 20 outer iterations;
- inf_pr/inf_du need deriving from eq_viol/ineq_viol/kkt_error on Altissimo
builds that predate #414.
The template already handles all three. The guidance now says the switch is ONE
line — `SOLVER = :altissimo` in the FILL-IN block — and states each trap, so the
instruction carries its own justification rather than reading as arbitrary.
solverModeSection is exported to make it testable, matching its already-exported
sibling routingSection. (The same one-line export is on #225; identical change,
so either merge order is fine.)
extension: 778 passed, tsc clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@Rchari1Rchari1 changed the title feat(hp): Piccolissimo + Altissimo is cloud-only, and now actually enforcedfeat(hp): cloud-only enforcement + cloud iters/frames reach the Run InspectorJul 29, 2026
# Conflicts:
#	packages/extension/src/opencode_config.ts
#	packages/extension/test/agents_md.test.ts
@jack-champagne
jack-champagneforce-pushed the rchari/hp-cloud-only branch 2 times, most recently from 54f4275 to 8aa362fCompareJuly 29, 2026 04:24
@jack-champagne
jack-champagne merged commit 199b23d into mainJul 29, 2026
10 checks passed
jeonghun-jj-lee pushed a commit that referenced this pull request Aug 21, 2026
Add WorkspaceTreeProvider — renders all workspace folders as collapsible
roots, expands recursively via vscode.workspace.fs.readDirectory(),
respects files.exclude, shows theme icons via resourceUri, opens files
on click, context menus (Copy Path, Reveal), and live-updates via
FileSystemWatcher. Replaces amicode.armonia placeholder; Catalog already
removed in #457.
package.json: rename amicode.armonia → amicode.workspace ("Workspace"),
keep runInspector, update activationEvents onView:workspace, add
view/item/context for workspace files.
Follows opencode#215 decision surface: Location.Ref.directories carries
all roots, no external-directory prompts, instructions stacked per dir.
Pairs with opencode PR #219 (engine: schema + boundary + env + instruction
multi-root + DB).
jeonghun-jj-lee added a commit that referenced this pull request Aug 22, 2026
…, #215)
* fix(ui): remove pulse catalog + CATALOG sidebar tab and widgets (#457)
Purge the legacy UX3 session catalog UI that predates the vault-backed
CatalogStore (Q94/Q95, packages/amico-run). The CATALOG activity-bar tab
(SessionCatalogTree → amicode.catalog, amicode.catalog.save/refresh/remove,
catalogCard webview + catalogcard component) is superseded by the mount stack
+ pack interface (WS1 #391) and is also slated for removal in opencode#215 AC7.
- package.json: drop amicode.catalog view + catalog commands/menus
- trees.ts: delete SessionCatalogTree/CATALOG_KEY, keep armonia placeholder
(will be replaced by Workspace tree per 215)
- extension.ts: remove registerCatalogCard + catalog save/refresh/remove
wiring, simplify amicode.savePulse to file-only Save dialog,
drop demo promote-to-catalog prompt, remove runs_manager promote-to-
catalog prompt (now info-only)
- esbuild.config.mjs: drop catalog_card_webview bundle
- delete catalog_card_shell/webview, media catalogcard, test suite
Pulse save stays via Save to file… → savePulseTo; vault CatalogStore
(packages/amico-run) untouched. Closes#457
* feat(workspace): Workspace sidebar for multi-root (opencode#215 AC6)
Add WorkspaceTreeProvider — renders all workspace folders as collapsible
roots, expands recursively via vscode.workspace.fs.readDirectory(),
respects files.exclude, shows theme icons via resourceUri, opens files
on click, context menus (Copy Path, Reveal), and live-updates via
FileSystemWatcher. Replaces amicode.armonia placeholder; Catalog already
removed in #457.
package.json: rename amicode.armonia → amicode.workspace ("Workspace"),
keep runInspector, update activationEvents onView:workspace, add
view/item/context for workspace files.
Follows opencode#215 decision surface: Location.Ref.directories carries
all roots, no external-directory prompts, instructions stacked per dir.
Pairs with opencode PR #219 (engine: schema + boundary + env + instruction
multi-root + DB).
* fix(workspace): full context menus for Workspace view (opencode#215 AC6)
Add New File/Folder, Rename, Delete, Copy Path/Relative, Reveal,
Open in Terminal to amicode.workspace viewItem context. Ensures
AC6 full context-menu coverage; Run Inspector stays as separate
webview (amicode.runInspector) not a directory in the tree.
* fix(workspace): only hide .git directory, not .gitignore/.github/etc
* feat(workspace): Chat with Amico button, full context menus, Remove from Workspace
- Add 'Chat with Amico' as first tree item with custom yellow SVG icon
that mutes (gray) when a chat tab is open
- Register custom workspace commands (newFile, newFolder, rename, delete,
copyPath, copyRelativePath, revealInOS, openInTerminal, openToSide,
removeFromWorkspace, addFolder) since built-in explorer.* commands
don't fire in custom tree views
- Remove stale amicode.runInspector webview declaration (provider was
deleted in PR #351, only the package.json entry remained)
- Add viewsWelcome for empty workspace state
- Listen to onDidChangeWorkspaceFolders to keep tree in sync
- Add ChatPanel.onLiveChange callback for cross-component state tracking
- Fix credential_scanner e2e test: guard opencode-provider assertion
(machine may have creds under different provider names)
- Add comprehensive test suite for workspace tree and context-menu commands
(24 tests covering tree rendering, all commands, muted state)
---------
Co-authored-by: Raghav Chari <raghavchari2021@gmail.com>
Sign up for freeto 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.

2 participants

@Rchari1@jack-champagne
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(hp): cloud-only enforcement + cloud iters/frames reach the Run Inspector - #219

Merged
jack-champagne merged 7 commits into
mainfrom
rchari/hp-cloud-only
Jul 29, 2026
Merged

feat(hp): cloud-only enforcement + cloud iters/frames reach the Run Inspector#219
jack-champagne merged 7 commits into
mainfrom
rchari/hp-cloud-only

Conversation

@Rchari1

@Rchari1Rchari1 commented Jul 28, 2026

Copy link
Copy Markdown
Member

Six commits, one thread: make the paid tier actually run in the cloud, and make what it produces actually reach the Run Inspector. 18 files, +673/−116.


1. Piccolissimo + Altissimo is cloud-only, and now enforced

Selecting the HP tier was supposed to mean "runs in Harmoniqs Cloud." In practice the agent kept dispatching it locally, where the laptop precompiled the whole HP stack (IPOPT included) until amico-run's process-group timeout SIGTERMed julia mid-precompile.

The API-key prompt and entitlement flip were already built and work (amicode#200/#167) — untouched here. Three other things allowed local dispatch:

  1. Nothing refused a local HP launch. The tier="hpc" gate is solid but only fires on a spec that saystier="hpc", and runGate only runs for --spec launches. A bare amico-run script.jl never reached it.
  2. The entitlement that unlocks the tier unlocked the failure — HP grants issimo, so the import scan admitted a local using Piccolissimo.
  3. The agent got contradictory orderssolverModeSection() said "launch in the cloud" while the routing section beside it said "routing is PER-SOLVE and EXPLICIT, default to local." Given a cloud-only tier and an instruction to default local, it defaulted local.

Fix, in the two-layer shape the tier already uses:

  • Enforcementlaunch.ts refuses --executor local while HP is selected, at the one choke point every run passes through, before the gate, so it covers spec and no-spec runs alike. Reads solver mode as status only, never a token, and fails safe to piccolo on a missing or corrupt file: this value can only ever refuse a run, so a bad read must never invent hp and block ordinary free-tier work.
  • Guidance — the routing section states the cloud-only contract instead of asking a routing question, and AGENTS.md step 5 makes the injected section authoritative over its local default. Piccolo sessions stay byte-identical (the section is still "" unless mode is hpand the cloud is connected).

The estimate keeps its reporting role and loses its deciding role: an estimate that fits in local RAM does not make an HP solve local.

Service is named Harmoniqs Cloud in every user-visible string. Wire id stays company-compute — renaming breaks the credential route, status cache, and CONNECTION_IDS for no visible gain.


2. Cloud iters and frames reach the Run Inspector

The client chain was fully built — poll the cloud, synthesize AMICODE_ITER into run.log, write frames, touch mtime for the stall logic. It read the wrong fields:

client readservice returns
statsbody.iters{task_id, **stats**[], submitter}
framespng_base64{task_id, iter, key, **url**, submitter}

Both reads sit in best-effort try/catch ("stats are advisory"), so it failed silentlyundefined ?? [] iterated zero times and run.log stayed 0 bytes. Confirmed on task 419a57e6: 60 IPOPT iterations and 11 frames banked to S3, empty inspector.

Why nobody caught it:fake_cloud.ts served the shapes the client read, not the shapes the service returns. Every test passed against a fake that agreed with the bug. So the fake is corrected first, and two tests pin the live payloads by exact key set — including that the presigned URL is fetchable with no auth header, because the signature is the credential.

Frame filenames also went 3-digit → 5-digit: iter_00007.png is what both the S3 layout and the local julia solve write. The old name matched neither, so cloud and local frames landed under two schemes in one run dir.

A third drift, found while proving the above: the poller only json.loads an AMICODE_ITER payload starting with {, so template-emitted key=value lines come back as {raw: "iter=7 f=…"}. The client keyed on it.iter and skipped every one as NaN — invisible because the smoke test seeds JSON. Now reconstructs from either shape and drops malformed records rather than emitting NaN.


3. The cloud can finally populate /stats

/solves/<id>/stats parses AMICODE_ITER out of run.log in the artifact prefix, and the runner's sidecar populates that prefix with aws s3 sync . — whatever is in the solve cwd. But julia's stdout on the runner goes to the SSM command stream, so run.log was never written there: nothing to sync, stats: [], empty inspector. The template's own comment ("AMICODE_PULSE lines on stdout (→ run.log)") was true locally and false in the cloud.

Telemetry now routes through emit(), which also appends to run.logonly when TASK_ID is set (the runner exports it). Locally amico-run's executor already writes run.log from stdout, so appending unconditionally would double every line and the inspector would count each iteration twice.

No terraform change and no AMI rebake: the solve script is uploaded per submission (<task_id>-solve.jl), so this ships immediately.


4. Altissimo streams like IPOPT

Both channels were bolted to IPOPT — frames off IpoptOptions.intermediate_callback, the iter line off a callback reading IpoptOptimizerState. Altissimo has neither; its only hook is callback on optimize!, arriving as (x, info). So an Altissimo solve lost the frames too, leaving the inspector completely dark rather than merely numberless.

SOLVER = :ipopt | :altissimo re-hangs both channels. xis the primal, so pulse_emit's solver-agnostic (primal, iter) contract takes it unchanged — same frames, same AMICODE_PULSE, same cooperative STOP (returning false stops an Altissimo solve exactly as it stops an Ipopt one). inf_pr/inf_du come from the callback tuple when present (Altissimo#414, merged) and are otherwise derived from eq_viol/ineq_viol/kkt_error, so it works on an older Altissimo instead of emitting NaN.

Two traps verified against Piccolissimo main and handled:

  • the qcp-level solve! forwards kwargs... to the prob-level method, which takes callback explicitly — so the wiring does reach optimize!;
  • but max_iter lands in that kwargs... and is silently dropped, so the budget now rides AltissimoOptions(max_outer_iter = max_iter). Left alone it would have quietly run Altissimo's default 20 outer iterations while the FILL-IN block said 60.

The guidance names SOLVER = :altissimo as the one-line switch and forbids hand-rolling the solve call, stating each trap so the instruction carries its own justification.


Testing

  • amico-run: 938 passed, 12 skipped.
  • extension: 778 passed, 3 skipped (the full CI set, --exclude '**/slow/**').
  • tsc --noEmit clean in both packages.
  • Live, against the real staging service: frames fetched from a presigned URL and landed locally with 5-digit names (task 0fccbbf9); template runs green end to end (fidelity 0.99997, 60 iterations); local run writes norun.log (no duplication), and with TASK_ID set run.log appears with iter lines in exactly the format the poller parses.
  • One incidental fix that matters for CI: the CLI tests were inheriting the developer's~/.amico/amicode/solver-mode.json. On an hp machine nine local-solve tests failed — on correct behaviour. They now spawn with a hermetic ops dir.

CI caught the one consumer I missed on the frame rename (the Δ9 remote state-machine test); the assertion above it — run.log contains iter=7 — passed, which is independent confirmation in a second harness that the stats fix delivers iters end to end.


What this does NOT fix

  • Altissimo on the cloud. The runner AMI bakes a Piccolissimo/Altissimo predating callback support, so it emits nothing there until Altissimo#414 is released and the image rebuilt. The template is ready for that moment.
  • The Altissimo code path has never been executed. It is written against the verified contract and parses, but the local checkouts here are 287/324 commits behind and cannot run it. Worth exercising on a current env before anyone demos it.
  • Cold start. ~9–10 min from submit to first frame, for ~55s of compute. Real, and unaddressed here.
  • Existing agent-authored scripts in ~/.amico/problems/ predate emit(), so they still show frames and no numbers until re-authored from the updated template.

solverModeSection is exported to make the guidance testable (matching its already-exported sibling routingSection). The same one-line export is on #225 — identical change, so either merge order is fine.

Reviewer note

This narrows behaviour: a local Piccolissimo run that "worked" before — slowly, usually fatally — is now refused outright. That is the intent of a cloud-only paid tier, but it is a real change for anyone relying on the local path, and the way out is one click on the solver control.

…forced
Selecting Piccolissimo + Altissimo was supposed to mean "this solve runs in
Harmoniqs Cloud". In practice the agent kept dispatching it LOCALLY: the laptop
precompiled the whole HP stack (IPOPT included) until amico-run's process-group
timeout SIGTERMed Julia mid-precompile.
Three things allowed that, none of them the pieces people assumed were missing
(the API-key prompt and the entitlement flip were already built and work):
1. Nothing refused a local HP launch. The gate's tier=hpc rules are solid, but
they only fire on a spec that SAYS tier="hpc" — and runGate only runs for
--spec launches at all. A bare `amico-run script.jl` never reached them.
2. Selecting HP grants the `issimo` entitlement, so the import scan happily
admits a local `using Piccolissimo`. The entitlement that unlocks the tier
was also unlocking the failure mode.
3. The agent was told two contradictory things. solverModeSection() said "launch
HP solves in the cloud" while the routing section beside it said "routing is
PER-SOLVE and EXPLICIT, you confirm where EVERY solve runs, default local" —
and the base AGENTS.md step 5 said the same. Given a cloud-only tier and an
instruction to default local, it defaulted local.
The fix, in the same two-layer shape the tier already uses:
- ENFORCEMENT: amico-run refuses `--executor local` while HP is selected, at the
one choke point every run passes through (launch.ts, before the gate), so it
covers spec and no-spec runs alike. Reads the extension's solver-mode.json —
status only, never a token — and fails SAFE to piccolo on an absent or corrupt
file, so a fresh install and every free-tier local run behave exactly as now.
- GUIDANCE: the routing section now states the cloud-only contract instead of
asking a routing question, and AGENTS.md step 5 makes the injected section
authoritative over its local default. Piccolo sessions are untouched: the
section is still "" unless mode is hp AND the cloud is connected.
The estimate keeps its reporting role and loses its deciding role — an estimate
that fits in local RAM does not make an HP solve local.
Also names the service "Harmoniqs Cloud" everywhere a user can see it (gate
refusals, routing prose, AGENTS.md, the HP row's tooltips) instead of the
internal "company compute" / bare "the cloud". The wire id stays
`company-compute` — renaming it would break the credential route, the status
cache, and the fork's CONNECTION_IDS for no user-visible gain.
Tests: 9 new (the reader's fail-safe directions; the refusal, including on the
no-spec path and that it leaves no run dir; that a REMOTE HP launch still
completes, against FakeCloud) + the routing/AGENTS contract updated to pin the
new copy, with an explicit regression test that the per-solve question does not
come back. amico-run 934 passed, extension suite green apart from two live-model
E2Es (interview_e2e, scores_e2e) that fail identically with this change stashed —
pre-existing, tracked separately.
Incidental but load-bearing: the CLI tests were inheriting the DEVELOPER's
~/.amico/amicode/solver-mode.json, so on an hp machine nine local-solve tests
failed on correct behaviour. They now spawn with a hermetic ops dir.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Rchari1and others added 4 commits July 28, 2026 20:39
A cloud solve produced an empty Run Inspector. The client-side chain was fully
built — RemoteExecutor polls the cloud, synthesizes AMICODE_ITER lines into
run.log, writes frames, and touches run.log's mtime for the stall logic — but it
read the WRONG FIELDS, so it dropped everything:
stats client read `body.iters` · service returns `{task_id, stats[], submitter}`
frames client read `png_base64` · service returns `{task_id, iter, key, url, submitter}`
Both reads sit inside best-effort try/catch blocks ("stats are advisory"), so
the failure was completely silent: `undefined ?? []` iterated zero times, run.log
stayed 0 bytes, and the Inspector had nothing to tail. Confirmed against task
419a57e6 on staging, which ran 60 IPOPT iterations and banked 11 frames to S3
while the local run.log stayed empty.
The reason nobody caught it: fake_cloud.ts served the shapes the CLIENT read,
not the shapes the SERVICE returns. Every test passed against a fake that agreed
with the bug. That is the real defect here, so the fake is corrected first and
two tests now pin the live payloads by exact key set — a fake that mirrors the
client proves nothing.
- stats: read `stats`, falling back to `iters` so an older runner still works.
- frames: fetch the presigned url (no auth header — the signature IS the
credential) and keep the base64 lane for older runners. FakeCloud now serves
artifact bytes from a route placed BEFORE its auth guard, matching S3.
- frame filenames go 3-digit → 5-digit: iter_00007.png is what both the S3 layout
and the local Julia solve write, so cloud and local frames no longer land under
two different schemes in one run dir.
Also: the HP solver-mode guidance now covers the Altissimo backend. IPOPT stays
the default because it is what streams telemetry; Altissimo is used on request,
but the agent must state the trade first — Piccolissimo's solve!(::AltissimoOptions)
does not forward a caller callback to Altissimo.optimize!, and there is no
intermediate_callback on that path, so an Altissimo run emits no frames and no
AMICODE_ITER and the Inspector stays empty until it finishes. Never switch
silently, never claim live iterations on it.
amico-run: 936 passed. extension: 62 passed in the touched suites, tsc clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CI caught the one consumer I missed when frame filenames went 3-digit → 5-digit.
The Δ9 test waited on iter_007.png and timed out; the assertion above it —
run.log contains "iter=7" — PASSED, which is independent confirmation in a second
harness that the stats-field fix delivers iters end to end.
Also corrects solver-mode guidance shipped earlier today. It asserted that
Piccolissimo's solve!(::AltissimoOptions) does not forward a caller callback —
taken from a report without checking. That is true of the local checkout (287
commits behind) but false of main, which accepts `callback` and forwards it to
Altissimo.optimize!. The guidance now says live iterations depend on the INSTALLED
version and tells the agent not to promise iterations it has not observed.
extension: 776 passed, 3 skipped (the full CI set).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two gaps stood between a cloud solve and a populated Run Inspector. Both are in
the solve template, which ships PER SUBMISSION (the client uploads
<task_id>-solve.jl), so neither needs an AMI rebake.
1. NOTHING WROTE run.log IN THE CLOUD.
/solves/<id>/stats parses AMICODE_ITER lines out of run.log in the artifact
prefix, and the runner's sidecar populates that prefix with `aws s3 sync .` —
whatever is in the solve cwd. But julia's stdout on the runner goes to the SSM
command stream, so run.log was never written there: nothing to sync, stats: [],
empty inspector. The template's own comment ("AMICODE_PULSE lines on stdout (→
run.log)") was true locally and false in the cloud.
Every AMICODE_* line now goes through emit(), which also appends to run.log —
but ONLY when TASK_ID is set (the runner exports it). Locally amico-run's
executor already writes run.log from stdout, so appending unconditionally would
double every line and the inspector would count each iteration twice.
Verified: local run writes no run.log (11 frames, result.toml, fidelity
0.99997); with TASK_ID set, run.log appears with AMICODE_ITER lines in the
exact format the poller parses.
2. THE TELEMETRY WAS BOLTED TO IPOPT.
Frames came off IpoptOptions.intermediate_callback and the iter line off a
callback reading IpoptOptimizerState. Altissimo has neither: its only hook is
`callback` on optimize! (forwarded by Piccolissimo's solve!(::AltissimoOptions))
and it arrives as (x, info). So an Altissimo solve lost the frames TOO, leaving
the inspector completely dark rather than merely numberless.
SOLVER = :ipopt | :altissimo now selects the backend and re-hangs both channels.
`x` is the primal, so pulse_emit's solver-agnostic (primal, iter) contract takes
it unchanged — same frames, same AMICODE_PULSE, same cooperative STOP.
inf_pr/inf_du come from the callback tuple when present (Altissimo#414) and are
otherwise derived from eq_viol/ineq_viol/kkt_error, so it works on an older
Altissimo too instead of emitting NaN.
And a third drift found while proving (1): the poller only json.loads an
AMICODE_ITER payload starting with "{", so template-emitted key=value lines come
back as {raw: "iter=7 f=…"}. The client keyed on it.iter and skipped every one as
NaN — invisible because the smoke test seeds JSON. It now reconstructs the line
from either shape, and drops malformed records rather than emitting NaN.
Still NOT solved by this: Altissimo on the cloud. The runner AMI bakes a
Piccolissimo/Altissimo predating callback support, so it will emit nothing there
until Altissimo#414 is released and the image rebuilt. The template is ready for
that moment and works today on IPOPT (cloud) and both backends locally.
amico-run 938 passed · extension 776 passed · template runs green end to end.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
solve!(::AltissimoOptions) forwards a HARDCODED kwarg list to
Altissimo.optimize! and swallows the rest, so `max_iter = 60` passed to solve!
never arrived: the solve quietly ran Altissimo's default 20 outer iterations
while the FILL-IN block said 60. Silent, not an error — the worst kind.
Budget now rides AltissimoOptions(max_outer_iter = max_iter). Verified against
Piccolissimo main: the qcp-level solve! forwards kwargs... to the prob-level
method, which takes `callback` explicitly, so the callback wiring does reach
optimize! — it is only the iteration budget that was being dropped.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The template gained a SOLVER flag but nothing told the agent it exists, so an
agent asked for Altissimo would hand-roll a solve call — and silently lose both
telemetry channels plus the iteration budget:
- frames come off IpoptOptions.intermediate_callback, which AltissimoOptions
has no equivalent of, so a hand-written call leaves the Run Inspector fully
dark rather than merely numberless;
- a `max_iter` passed to solve!(::AltissimoOptions) lands in kwargs... and is
dropped, so the solve quietly runs Altissimo's default 20 outer iterations;
- inf_pr/inf_du need deriving from eq_viol/ineq_viol/kkt_error on Altissimo
builds that predate #414.
The template already handles all three. The guidance now says the switch is ONE
line — `SOLVER = :altissimo` in the FILL-IN block — and states each trap, so the
instruction carries its own justification rather than reading as arbitrary.
solverModeSection is exported to make it testable, matching its already-exported
sibling routingSection. (The same one-line export is on #225; identical change,
so either merge order is fine.)
extension: 778 passed, tsc clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@Rchari1Rchari1 changed the title feat(hp): Piccolissimo + Altissimo is cloud-only, and now actually enforcedfeat(hp): cloud-only enforcement + cloud iters/frames reach the Run InspectorJul 29, 2026
# Conflicts:
#	packages/extension/src/opencode_config.ts
#	packages/extension/test/agents_md.test.ts
@jack-champagne
jack-champagneforce-pushed the rchari/hp-cloud-only branch 2 times, most recently from 54f4275 to 8aa362fCompareJuly 29, 2026 04:24
@jack-champagne
jack-champagne merged commit 199b23d into mainJul 29, 2026
10 checks passed
jeonghun-jj-lee pushed a commit that referenced this pull request Aug 21, 2026
Add WorkspaceTreeProvider — renders all workspace folders as collapsible
roots, expands recursively via vscode.workspace.fs.readDirectory(),
respects files.exclude, shows theme icons via resourceUri, opens files
on click, context menus (Copy Path, Reveal), and live-updates via
FileSystemWatcher. Replaces amicode.armonia placeholder; Catalog already
removed in #457.
package.json: rename amicode.armonia → amicode.workspace ("Workspace"),
keep runInspector, update activationEvents onView:workspace, add
view/item/context for workspace files.
Follows opencode#215 decision surface: Location.Ref.directories carries
all roots, no external-directory prompts, instructions stacked per dir.
Pairs with opencode PR #219 (engine: schema + boundary + env + instruction
multi-root + DB).
jeonghun-jj-lee added a commit that referenced this pull request Aug 22, 2026
…, #215)
* fix(ui): remove pulse catalog + CATALOG sidebar tab and widgets (#457)
Purge the legacy UX3 session catalog UI that predates the vault-backed
CatalogStore (Q94/Q95, packages/amico-run). The CATALOG activity-bar tab
(SessionCatalogTree → amicode.catalog, amicode.catalog.save/refresh/remove,
catalogCard webview + catalogcard component) is superseded by the mount stack
+ pack interface (WS1 #391) and is also slated for removal in opencode#215 AC7.
- package.json: drop amicode.catalog view + catalog commands/menus
- trees.ts: delete SessionCatalogTree/CATALOG_KEY, keep armonia placeholder
(will be replaced by Workspace tree per 215)
- extension.ts: remove registerCatalogCard + catalog save/refresh/remove
wiring, simplify amicode.savePulse to file-only Save dialog,
drop demo promote-to-catalog prompt, remove runs_manager promote-to-
catalog prompt (now info-only)
- esbuild.config.mjs: drop catalog_card_webview bundle
- delete catalog_card_shell/webview, media catalogcard, test suite
Pulse save stays via Save to file… → savePulseTo; vault CatalogStore
(packages/amico-run) untouched. Closes#457
* feat(workspace): Workspace sidebar for multi-root (opencode#215 AC6)
Add WorkspaceTreeProvider — renders all workspace folders as collapsible
roots, expands recursively via vscode.workspace.fs.readDirectory(),
respects files.exclude, shows theme icons via resourceUri, opens files
on click, context menus (Copy Path, Reveal), and live-updates via
FileSystemWatcher. Replaces amicode.armonia placeholder; Catalog already
removed in #457.
package.json: rename amicode.armonia → amicode.workspace ("Workspace"),
keep runInspector, update activationEvents onView:workspace, add
view/item/context for workspace files.
Follows opencode#215 decision surface: Location.Ref.directories carries
all roots, no external-directory prompts, instructions stacked per dir.
Pairs with opencode PR #219 (engine: schema + boundary + env + instruction
multi-root + DB).
* fix(workspace): full context menus for Workspace view (opencode#215 AC6)
Add New File/Folder, Rename, Delete, Copy Path/Relative, Reveal,
Open in Terminal to amicode.workspace viewItem context. Ensures
AC6 full context-menu coverage; Run Inspector stays as separate
webview (amicode.runInspector) not a directory in the tree.
* fix(workspace): only hide .git directory, not .gitignore/.github/etc
* feat(workspace): Chat with Amico button, full context menus, Remove from Workspace
- Add 'Chat with Amico' as first tree item with custom yellow SVG icon
that mutes (gray) when a chat tab is open
- Register custom workspace commands (newFile, newFolder, rename, delete,
copyPath, copyRelativePath, revealInOS, openInTerminal, openToSide,
removeFromWorkspace, addFolder) since built-in explorer.* commands
don't fire in custom tree views
- Remove stale amicode.runInspector webview declaration (provider was
deleted in PR #351, only the package.json entry remained)
- Add viewsWelcome for empty workspace state
- Listen to onDidChangeWorkspaceFolders to keep tree in sync
- Add ChatPanel.onLiveChange callback for cross-component state tracking
- Fix credential_scanner e2e test: guard opencode-provider assertion
(machine may have creds under different provider names)
- Add comprehensive test suite for workspace tree and context-menu commands
(24 tests covering tree rendering, all commands, muted state)
---------
Co-authored-by: Raghav Chari <raghavchari2021@gmail.com>
Sign up for freeto 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.

2 participants

@Rchari1@jack-champagne
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(hp): cloud-only enforcement + cloud iters/frames reach the Run Inspector - #219

Merged
jack-champagne merged 7 commits into
mainfrom
rchari/hp-cloud-only
Jul 29, 2026
Merged

feat(hp): cloud-only enforcement + cloud iters/frames reach the Run Inspector#219
jack-champagne merged 7 commits into
mainfrom
rchari/hp-cloud-only

Conversation

@Rchari1

@Rchari1Rchari1 commented Jul 28, 2026

Copy link
Copy Markdown
Member

Six commits, one thread: make the paid tier actually run in the cloud, and make what it produces actually reach the Run Inspector. 18 files, +673/−116.


1. Piccolissimo + Altissimo is cloud-only, and now enforced

Selecting the HP tier was supposed to mean "runs in Harmoniqs Cloud." In practice the agent kept dispatching it locally, where the laptop precompiled the whole HP stack (IPOPT included) until amico-run's process-group timeout SIGTERMed julia mid-precompile.

The API-key prompt and entitlement flip were already built and work (amicode#200/#167) — untouched here. Three other things allowed local dispatch:

  1. Nothing refused a local HP launch. The tier="hpc" gate is solid but only fires on a spec that saystier="hpc", and runGate only runs for --spec launches. A bare amico-run script.jl never reached it.
  2. The entitlement that unlocks the tier unlocked the failure — HP grants issimo, so the import scan admitted a local using Piccolissimo.
  3. The agent got contradictory orderssolverModeSection() said "launch in the cloud" while the routing section beside it said "routing is PER-SOLVE and EXPLICIT, default to local." Given a cloud-only tier and an instruction to default local, it defaulted local.

Fix, in the two-layer shape the tier already uses:

  • Enforcementlaunch.ts refuses --executor local while HP is selected, at the one choke point every run passes through, before the gate, so it covers spec and no-spec runs alike. Reads solver mode as status only, never a token, and fails safe to piccolo on a missing or corrupt file: this value can only ever refuse a run, so a bad read must never invent hp and block ordinary free-tier work.
  • Guidance — the routing section states the cloud-only contract instead of asking a routing question, and AGENTS.md step 5 makes the injected section authoritative over its local default. Piccolo sessions stay byte-identical (the section is still "" unless mode is hpand the cloud is connected).

The estimate keeps its reporting role and loses its deciding role: an estimate that fits in local RAM does not make an HP solve local.

Service is named Harmoniqs Cloud in every user-visible string. Wire id stays company-compute — renaming breaks the credential route, status cache, and CONNECTION_IDS for no visible gain.


2. Cloud iters and frames reach the Run Inspector

The client chain was fully built — poll the cloud, synthesize AMICODE_ITER into run.log, write frames, touch mtime for the stall logic. It read the wrong fields:

client readservice returns
statsbody.iters{task_id, **stats**[], submitter}
framespng_base64{task_id, iter, key, **url**, submitter}

Both reads sit in best-effort try/catch ("stats are advisory"), so it failed silentlyundefined ?? [] iterated zero times and run.log stayed 0 bytes. Confirmed on task 419a57e6: 60 IPOPT iterations and 11 frames banked to S3, empty inspector.

Why nobody caught it:fake_cloud.ts served the shapes the client read, not the shapes the service returns. Every test passed against a fake that agreed with the bug. So the fake is corrected first, and two tests pin the live payloads by exact key set — including that the presigned URL is fetchable with no auth header, because the signature is the credential.

Frame filenames also went 3-digit → 5-digit: iter_00007.png is what both the S3 layout and the local julia solve write. The old name matched neither, so cloud and local frames landed under two schemes in one run dir.

A third drift, found while proving the above: the poller only json.loads an AMICODE_ITER payload starting with {, so template-emitted key=value lines come back as {raw: "iter=7 f=…"}. The client keyed on it.iter and skipped every one as NaN — invisible because the smoke test seeds JSON. Now reconstructs from either shape and drops malformed records rather than emitting NaN.


3. The cloud can finally populate /stats

/solves/<id>/stats parses AMICODE_ITER out of run.log in the artifact prefix, and the runner's sidecar populates that prefix with aws s3 sync . — whatever is in the solve cwd. But julia's stdout on the runner goes to the SSM command stream, so run.log was never written there: nothing to sync, stats: [], empty inspector. The template's own comment ("AMICODE_PULSE lines on stdout (→ run.log)") was true locally and false in the cloud.

Telemetry now routes through emit(), which also appends to run.logonly when TASK_ID is set (the runner exports it). Locally amico-run's executor already writes run.log from stdout, so appending unconditionally would double every line and the inspector would count each iteration twice.

No terraform change and no AMI rebake: the solve script is uploaded per submission (<task_id>-solve.jl), so this ships immediately.


4. Altissimo streams like IPOPT

Both channels were bolted to IPOPT — frames off IpoptOptions.intermediate_callback, the iter line off a callback reading IpoptOptimizerState. Altissimo has neither; its only hook is callback on optimize!, arriving as (x, info). So an Altissimo solve lost the frames too, leaving the inspector completely dark rather than merely numberless.

SOLVER = :ipopt | :altissimo re-hangs both channels. xis the primal, so pulse_emit's solver-agnostic (primal, iter) contract takes it unchanged — same frames, same AMICODE_PULSE, same cooperative STOP (returning false stops an Altissimo solve exactly as it stops an Ipopt one). inf_pr/inf_du come from the callback tuple when present (Altissimo#414, merged) and are otherwise derived from eq_viol/ineq_viol/kkt_error, so it works on an older Altissimo instead of emitting NaN.

Two traps verified against Piccolissimo main and handled:

  • the qcp-level solve! forwards kwargs... to the prob-level method, which takes callback explicitly — so the wiring does reach optimize!;
  • but max_iter lands in that kwargs... and is silently dropped, so the budget now rides AltissimoOptions(max_outer_iter = max_iter). Left alone it would have quietly run Altissimo's default 20 outer iterations while the FILL-IN block said 60.

The guidance names SOLVER = :altissimo as the one-line switch and forbids hand-rolling the solve call, stating each trap so the instruction carries its own justification.


Testing

  • amico-run: 938 passed, 12 skipped.
  • extension: 778 passed, 3 skipped (the full CI set, --exclude '**/slow/**').
  • tsc --noEmit clean in both packages.
  • Live, against the real staging service: frames fetched from a presigned URL and landed locally with 5-digit names (task 0fccbbf9); template runs green end to end (fidelity 0.99997, 60 iterations); local run writes norun.log (no duplication), and with TASK_ID set run.log appears with iter lines in exactly the format the poller parses.
  • One incidental fix that matters for CI: the CLI tests were inheriting the developer's~/.amico/amicode/solver-mode.json. On an hp machine nine local-solve tests failed — on correct behaviour. They now spawn with a hermetic ops dir.

CI caught the one consumer I missed on the frame rename (the Δ9 remote state-machine test); the assertion above it — run.log contains iter=7 — passed, which is independent confirmation in a second harness that the stats fix delivers iters end to end.


What this does NOT fix

  • Altissimo on the cloud. The runner AMI bakes a Piccolissimo/Altissimo predating callback support, so it emits nothing there until Altissimo#414 is released and the image rebuilt. The template is ready for that moment.
  • The Altissimo code path has never been executed. It is written against the verified contract and parses, but the local checkouts here are 287/324 commits behind and cannot run it. Worth exercising on a current env before anyone demos it.
  • Cold start. ~9–10 min from submit to first frame, for ~55s of compute. Real, and unaddressed here.
  • Existing agent-authored scripts in ~/.amico/problems/ predate emit(), so they still show frames and no numbers until re-authored from the updated template.

solverModeSection is exported to make the guidance testable (matching its already-exported sibling routingSection). The same one-line export is on #225 — identical change, so either merge order is fine.

Reviewer note

This narrows behaviour: a local Piccolissimo run that "worked" before — slowly, usually fatally — is now refused outright. That is the intent of a cloud-only paid tier, but it is a real change for anyone relying on the local path, and the way out is one click on the solver control.

…forced
Selecting Piccolissimo + Altissimo was supposed to mean "this solve runs in
Harmoniqs Cloud". In practice the agent kept dispatching it LOCALLY: the laptop
precompiled the whole HP stack (IPOPT included) until amico-run's process-group
timeout SIGTERMed Julia mid-precompile.
Three things allowed that, none of them the pieces people assumed were missing
(the API-key prompt and the entitlement flip were already built and work):
1. Nothing refused a local HP launch. The gate's tier=hpc rules are solid, but
they only fire on a spec that SAYS tier="hpc" — and runGate only runs for
--spec launches at all. A bare `amico-run script.jl` never reached them.
2. Selecting HP grants the `issimo` entitlement, so the import scan happily
admits a local `using Piccolissimo`. The entitlement that unlocks the tier
was also unlocking the failure mode.
3. The agent was told two contradictory things. solverModeSection() said "launch
HP solves in the cloud" while the routing section beside it said "routing is
PER-SOLVE and EXPLICIT, you confirm where EVERY solve runs, default local" —
and the base AGENTS.md step 5 said the same. Given a cloud-only tier and an
instruction to default local, it defaulted local.
The fix, in the same two-layer shape the tier already uses:
- ENFORCEMENT: amico-run refuses `--executor local` while HP is selected, at the
one choke point every run passes through (launch.ts, before the gate), so it
covers spec and no-spec runs alike. Reads the extension's solver-mode.json —
status only, never a token — and fails SAFE to piccolo on an absent or corrupt
file, so a fresh install and every free-tier local run behave exactly as now.
- GUIDANCE: the routing section now states the cloud-only contract instead of
asking a routing question, and AGENTS.md step 5 makes the injected section
authoritative over its local default. Piccolo sessions are untouched: the
section is still "" unless mode is hp AND the cloud is connected.
The estimate keeps its reporting role and loses its deciding role — an estimate
that fits in local RAM does not make an HP solve local.
Also names the service "Harmoniqs Cloud" everywhere a user can see it (gate
refusals, routing prose, AGENTS.md, the HP row's tooltips) instead of the
internal "company compute" / bare "the cloud". The wire id stays
`company-compute` — renaming it would break the credential route, the status
cache, and the fork's CONNECTION_IDS for no user-visible gain.
Tests: 9 new (the reader's fail-safe directions; the refusal, including on the
no-spec path and that it leaves no run dir; that a REMOTE HP launch still
completes, against FakeCloud) + the routing/AGENTS contract updated to pin the
new copy, with an explicit regression test that the per-solve question does not
come back. amico-run 934 passed, extension suite green apart from two live-model
E2Es (interview_e2e, scores_e2e) that fail identically with this change stashed —
pre-existing, tracked separately.
Incidental but load-bearing: the CLI tests were inheriting the DEVELOPER's
~/.amico/amicode/solver-mode.json, so on an hp machine nine local-solve tests
failed on correct behaviour. They now spawn with a hermetic ops dir.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Rchari1and others added 4 commits July 28, 2026 20:39
A cloud solve produced an empty Run Inspector. The client-side chain was fully
built — RemoteExecutor polls the cloud, synthesizes AMICODE_ITER lines into
run.log, writes frames, and touches run.log's mtime for the stall logic — but it
read the WRONG FIELDS, so it dropped everything:
stats client read `body.iters` · service returns `{task_id, stats[], submitter}`
frames client read `png_base64` · service returns `{task_id, iter, key, url, submitter}`
Both reads sit inside best-effort try/catch blocks ("stats are advisory"), so
the failure was completely silent: `undefined ?? []` iterated zero times, run.log
stayed 0 bytes, and the Inspector had nothing to tail. Confirmed against task
419a57e6 on staging, which ran 60 IPOPT iterations and banked 11 frames to S3
while the local run.log stayed empty.
The reason nobody caught it: fake_cloud.ts served the shapes the CLIENT read,
not the shapes the SERVICE returns. Every test passed against a fake that agreed
with the bug. That is the real defect here, so the fake is corrected first and
two tests now pin the live payloads by exact key set — a fake that mirrors the
client proves nothing.
- stats: read `stats`, falling back to `iters` so an older runner still works.
- frames: fetch the presigned url (no auth header — the signature IS the
credential) and keep the base64 lane for older runners. FakeCloud now serves
artifact bytes from a route placed BEFORE its auth guard, matching S3.
- frame filenames go 3-digit → 5-digit: iter_00007.png is what both the S3 layout
and the local Julia solve write, so cloud and local frames no longer land under
two different schemes in one run dir.
Also: the HP solver-mode guidance now covers the Altissimo backend. IPOPT stays
the default because it is what streams telemetry; Altissimo is used on request,
but the agent must state the trade first — Piccolissimo's solve!(::AltissimoOptions)
does not forward a caller callback to Altissimo.optimize!, and there is no
intermediate_callback on that path, so an Altissimo run emits no frames and no
AMICODE_ITER and the Inspector stays empty until it finishes. Never switch
silently, never claim live iterations on it.
amico-run: 936 passed. extension: 62 passed in the touched suites, tsc clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CI caught the one consumer I missed when frame filenames went 3-digit → 5-digit.
The Δ9 test waited on iter_007.png and timed out; the assertion above it —
run.log contains "iter=7" — PASSED, which is independent confirmation in a second
harness that the stats-field fix delivers iters end to end.
Also corrects solver-mode guidance shipped earlier today. It asserted that
Piccolissimo's solve!(::AltissimoOptions) does not forward a caller callback —
taken from a report without checking. That is true of the local checkout (287
commits behind) but false of main, which accepts `callback` and forwards it to
Altissimo.optimize!. The guidance now says live iterations depend on the INSTALLED
version and tells the agent not to promise iterations it has not observed.
extension: 776 passed, 3 skipped (the full CI set).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two gaps stood between a cloud solve and a populated Run Inspector. Both are in
the solve template, which ships PER SUBMISSION (the client uploads
<task_id>-solve.jl), so neither needs an AMI rebake.
1. NOTHING WROTE run.log IN THE CLOUD.
/solves/<id>/stats parses AMICODE_ITER lines out of run.log in the artifact
prefix, and the runner's sidecar populates that prefix with `aws s3 sync .` —
whatever is in the solve cwd. But julia's stdout on the runner goes to the SSM
command stream, so run.log was never written there: nothing to sync, stats: [],
empty inspector. The template's own comment ("AMICODE_PULSE lines on stdout (→
run.log)") was true locally and false in the cloud.
Every AMICODE_* line now goes through emit(), which also appends to run.log —
but ONLY when TASK_ID is set (the runner exports it). Locally amico-run's
executor already writes run.log from stdout, so appending unconditionally would
double every line and the inspector would count each iteration twice.
Verified: local run writes no run.log (11 frames, result.toml, fidelity
0.99997); with TASK_ID set, run.log appears with AMICODE_ITER lines in the
exact format the poller parses.
2. THE TELEMETRY WAS BOLTED TO IPOPT.
Frames came off IpoptOptions.intermediate_callback and the iter line off a
callback reading IpoptOptimizerState. Altissimo has neither: its only hook is
`callback` on optimize! (forwarded by Piccolissimo's solve!(::AltissimoOptions))
and it arrives as (x, info). So an Altissimo solve lost the frames TOO, leaving
the inspector completely dark rather than merely numberless.
SOLVER = :ipopt | :altissimo now selects the backend and re-hangs both channels.
`x` is the primal, so pulse_emit's solver-agnostic (primal, iter) contract takes
it unchanged — same frames, same AMICODE_PULSE, same cooperative STOP.
inf_pr/inf_du come from the callback tuple when present (Altissimo#414) and are
otherwise derived from eq_viol/ineq_viol/kkt_error, so it works on an older
Altissimo too instead of emitting NaN.
And a third drift found while proving (1): the poller only json.loads an
AMICODE_ITER payload starting with "{", so template-emitted key=value lines come
back as {raw: "iter=7 f=…"}. The client keyed on it.iter and skipped every one as
NaN — invisible because the smoke test seeds JSON. It now reconstructs the line
from either shape, and drops malformed records rather than emitting NaN.
Still NOT solved by this: Altissimo on the cloud. The runner AMI bakes a
Piccolissimo/Altissimo predating callback support, so it will emit nothing there
until Altissimo#414 is released and the image rebuilt. The template is ready for
that moment and works today on IPOPT (cloud) and both backends locally.
amico-run 938 passed · extension 776 passed · template runs green end to end.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
solve!(::AltissimoOptions) forwards a HARDCODED kwarg list to
Altissimo.optimize! and swallows the rest, so `max_iter = 60` passed to solve!
never arrived: the solve quietly ran Altissimo's default 20 outer iterations
while the FILL-IN block said 60. Silent, not an error — the worst kind.
Budget now rides AltissimoOptions(max_outer_iter = max_iter). Verified against
Piccolissimo main: the qcp-level solve! forwards kwargs... to the prob-level
method, which takes `callback` explicitly, so the callback wiring does reach
optimize! — it is only the iteration budget that was being dropped.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The template gained a SOLVER flag but nothing told the agent it exists, so an
agent asked for Altissimo would hand-roll a solve call — and silently lose both
telemetry channels plus the iteration budget:
- frames come off IpoptOptions.intermediate_callback, which AltissimoOptions
has no equivalent of, so a hand-written call leaves the Run Inspector fully
dark rather than merely numberless;
- a `max_iter` passed to solve!(::AltissimoOptions) lands in kwargs... and is
dropped, so the solve quietly runs Altissimo's default 20 outer iterations;
- inf_pr/inf_du need deriving from eq_viol/ineq_viol/kkt_error on Altissimo
builds that predate #414.
The template already handles all three. The guidance now says the switch is ONE
line — `SOLVER = :altissimo` in the FILL-IN block — and states each trap, so the
instruction carries its own justification rather than reading as arbitrary.
solverModeSection is exported to make it testable, matching its already-exported
sibling routingSection. (The same one-line export is on #225; identical change,
so either merge order is fine.)
extension: 778 passed, tsc clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@Rchari1Rchari1 changed the title feat(hp): Piccolissimo + Altissimo is cloud-only, and now actually enforcedfeat(hp): cloud-only enforcement + cloud iters/frames reach the Run InspectorJul 29, 2026
# Conflicts:
#	packages/extension/src/opencode_config.ts
#	packages/extension/test/agents_md.test.ts
@jack-champagne
jack-champagneforce-pushed the rchari/hp-cloud-only branch 2 times, most recently from 54f4275 to 8aa362fCompareJuly 29, 2026 04:24
@jack-champagne
jack-champagne merged commit 199b23d into mainJul 29, 2026
10 checks passed
jeonghun-jj-lee pushed a commit that referenced this pull request Aug 21, 2026
Add WorkspaceTreeProvider — renders all workspace folders as collapsible
roots, expands recursively via vscode.workspace.fs.readDirectory(),
respects files.exclude, shows theme icons via resourceUri, opens files
on click, context menus (Copy Path, Reveal), and live-updates via
FileSystemWatcher. Replaces amicode.armonia placeholder; Catalog already
removed in #457.
package.json: rename amicode.armonia → amicode.workspace ("Workspace"),
keep runInspector, update activationEvents onView:workspace, add
view/item/context for workspace files.
Follows opencode#215 decision surface: Location.Ref.directories carries
all roots, no external-directory prompts, instructions stacked per dir.
Pairs with opencode PR #219 (engine: schema + boundary + env + instruction
multi-root + DB).
jeonghun-jj-lee added a commit that referenced this pull request Aug 22, 2026
…, #215)
* fix(ui): remove pulse catalog + CATALOG sidebar tab and widgets (#457)
Purge the legacy UX3 session catalog UI that predates the vault-backed
CatalogStore (Q94/Q95, packages/amico-run). The CATALOG activity-bar tab
(SessionCatalogTree → amicode.catalog, amicode.catalog.save/refresh/remove,
catalogCard webview + catalogcard component) is superseded by the mount stack
+ pack interface (WS1 #391) and is also slated for removal in opencode#215 AC7.
- package.json: drop amicode.catalog view + catalog commands/menus
- trees.ts: delete SessionCatalogTree/CATALOG_KEY, keep armonia placeholder
(will be replaced by Workspace tree per 215)
- extension.ts: remove registerCatalogCard + catalog save/refresh/remove
wiring, simplify amicode.savePulse to file-only Save dialog,
drop demo promote-to-catalog prompt, remove runs_manager promote-to-
catalog prompt (now info-only)
- esbuild.config.mjs: drop catalog_card_webview bundle
- delete catalog_card_shell/webview, media catalogcard, test suite
Pulse save stays via Save to file… → savePulseTo; vault CatalogStore
(packages/amico-run) untouched. Closes#457
* feat(workspace): Workspace sidebar for multi-root (opencode#215 AC6)
Add WorkspaceTreeProvider — renders all workspace folders as collapsible
roots, expands recursively via vscode.workspace.fs.readDirectory(),
respects files.exclude, shows theme icons via resourceUri, opens files
on click, context menus (Copy Path, Reveal), and live-updates via
FileSystemWatcher. Replaces amicode.armonia placeholder; Catalog already
removed in #457.
package.json: rename amicode.armonia → amicode.workspace ("Workspace"),
keep runInspector, update activationEvents onView:workspace, add
view/item/context for workspace files.
Follows opencode#215 decision surface: Location.Ref.directories carries
all roots, no external-directory prompts, instructions stacked per dir.
Pairs with opencode PR #219 (engine: schema + boundary + env + instruction
multi-root + DB).
* fix(workspace): full context menus for Workspace view (opencode#215 AC6)
Add New File/Folder, Rename, Delete, Copy Path/Relative, Reveal,
Open in Terminal to amicode.workspace viewItem context. Ensures
AC6 full context-menu coverage; Run Inspector stays as separate
webview (amicode.runInspector) not a directory in the tree.
* fix(workspace): only hide .git directory, not .gitignore/.github/etc
* feat(workspace): Chat with Amico button, full context menus, Remove from Workspace
- Add 'Chat with Amico' as first tree item with custom yellow SVG icon
that mutes (gray) when a chat tab is open
- Register custom workspace commands (newFile, newFolder, rename, delete,
copyPath, copyRelativePath, revealInOS, openInTerminal, openToSide,
removeFromWorkspace, addFolder) since built-in explorer.* commands
don't fire in custom tree views
- Remove stale amicode.runInspector webview declaration (provider was
deleted in PR #351, only the package.json entry remained)
- Add viewsWelcome for empty workspace state
- Listen to onDidChangeWorkspaceFolders to keep tree in sync
- Add ChatPanel.onLiveChange callback for cross-component state tracking
- Fix credential_scanner e2e test: guard opencode-provider assertion
(machine may have creds under different provider names)
- Add comprehensive test suite for workspace tree and context-menu commands
(24 tests covering tree rendering, all commands, muted state)
---------
Co-authored-by: Raghav Chari <raghavchari2021@gmail.com>
Sign up for freeto 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.

2 participants

@Rchari1@jack-champagne
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

feat(hp): cloud-only enforcement + cloud iters/frames reach the Run Inspector - #219

Merged
jack-champagne merged 7 commits into
mainfrom
rchari/hp-cloud-only
Jul 29, 2026
Merged

feat(hp): cloud-only enforcement + cloud iters/frames reach the Run Inspector#219
jack-champagne merged 7 commits into
mainfrom
rchari/hp-cloud-only

Conversation

@Rchari1

@Rchari1Rchari1 commented Jul 28, 2026

Copy link
Copy Markdown
Member

Six commits, one thread: make the paid tier actually run in the cloud, and make what it produces actually reach the Run Inspector. 18 files, +673/−116.


1. Piccolissimo + Altissimo is cloud-only, and now enforced

Selecting the HP tier was supposed to mean "runs in Harmoniqs Cloud." In practice the agent kept dispatching it locally, where the laptop precompiled the whole HP stack (IPOPT included) until amico-run's process-group timeout SIGTERMed julia mid-precompile.

The API-key prompt and entitlement flip were already built and work (amicode#200/#167) — untouched here. Three other things allowed local dispatch:

  1. Nothing refused a local HP launch. The tier="hpc" gate is solid but only fires on a spec that saystier="hpc", and runGate only runs for --spec launches. A bare amico-run script.jl never reached it.
  2. The entitlement that unlocks the tier unlocked the failure — HP grants issimo, so the import scan admitted a local using Piccolissimo.
  3. The agent got contradictory orderssolverModeSection() said "launch in the cloud" while the routing section beside it said "routing is PER-SOLVE and EXPLICIT, default to local." Given a cloud-only tier and an instruction to default local, it defaulted local.

Fix, in the two-layer shape the tier already uses:

  • Enforcementlaunch.ts refuses --executor local while HP is selected, at the one choke point every run passes through, before the gate, so it covers spec and no-spec runs alike. Reads solver mode as status only, never a token, and fails safe to piccolo on a missing or corrupt file: this value can only ever refuse a run, so a bad read must never invent hp and block ordinary free-tier work.
  • Guidance — the routing section states the cloud-only contract instead of asking a routing question, and AGENTS.md step 5 makes the injected section authoritative over its local default. Piccolo sessions stay byte-identical (the section is still "" unless mode is hpand the cloud is connected).

The estimate keeps its reporting role and loses its deciding role: an estimate that fits in local RAM does not make an HP solve local.

Service is named Harmoniqs Cloud in every user-visible string. Wire id stays company-compute — renaming breaks the credential route, status cache, and CONNECTION_IDS for no visible gain.


2. Cloud iters and frames reach the Run Inspector

The client chain was fully built — poll the cloud, synthesize AMICODE_ITER into run.log, write frames, touch mtime for the stall logic. It read the wrong fields:

client readservice returns
statsbody.iters{task_id, **stats**[], submitter}
framespng_base64{task_id, iter, key, **url**, submitter}

Both reads sit in best-effort try/catch ("stats are advisory"), so it failed silentlyundefined ?? [] iterated zero times and run.log stayed 0 bytes. Confirmed on task 419a57e6: 60 IPOPT iterations and 11 frames banked to S3, empty inspector.

Why nobody caught it:fake_cloud.ts served the shapes the client read, not the shapes the service returns. Every test passed against a fake that agreed with the bug. So the fake is corrected first, and two tests pin the live payloads by exact key set — including that the presigned URL is fetchable with no auth header, because the signature is the credential.

Frame filenames also went 3-digit → 5-digit: iter_00007.png is what both the S3 layout and the local julia solve write. The old name matched neither, so cloud and local frames landed under two schemes in one run dir.

A third drift, found while proving the above: the poller only json.loads an AMICODE_ITER payload starting with {, so template-emitted key=value lines come back as {raw: "iter=7 f=…"}. The client keyed on it.iter and skipped every one as NaN — invisible because the smoke test seeds JSON. Now reconstructs from either shape and drops malformed records rather than emitting NaN.


3. The cloud can finally populate /stats

/solves/<id>/stats parses AMICODE_ITER out of run.log in the artifact prefix, and the runner's sidecar populates that prefix with aws s3 sync . — whatever is in the solve cwd. But julia's stdout on the runner goes to the SSM command stream, so run.log was never written there: nothing to sync, stats: [], empty inspector. The template's own comment ("AMICODE_PULSE lines on stdout (→ run.log)") was true locally and false in the cloud.

Telemetry now routes through emit(), which also appends to run.logonly when TASK_ID is set (the runner exports it). Locally amico-run's executor already writes run.log from stdout, so appending unconditionally would double every line and the inspector would count each iteration twice.

No terraform change and no AMI rebake: the solve script is uploaded per submission (<task_id>-solve.jl), so this ships immediately.


4. Altissimo streams like IPOPT

Both channels were bolted to IPOPT — frames off IpoptOptions.intermediate_callback, the iter line off a callback reading IpoptOptimizerState. Altissimo has neither; its only hook is callback on optimize!, arriving as (x, info). So an Altissimo solve lost the frames too, leaving the inspector completely dark rather than merely numberless.

SOLVER = :ipopt | :altissimo re-hangs both channels. xis the primal, so pulse_emit's solver-agnostic (primal, iter) contract takes it unchanged — same frames, same AMICODE_PULSE, same cooperative STOP (returning false stops an Altissimo solve exactly as it stops an Ipopt one). inf_pr/inf_du come from the callback tuple when present (Altissimo#414, merged) and are otherwise derived from eq_viol/ineq_viol/kkt_error, so it works on an older Altissimo instead of emitting NaN.

Two traps verified against Piccolissimo main and handled:

  • the qcp-level solve! forwards kwargs... to the prob-level method, which takes callback explicitly — so the wiring does reach optimize!;
  • but max_iter lands in that kwargs... and is silently dropped, so the budget now rides AltissimoOptions(max_outer_iter = max_iter). Left alone it would have quietly run Altissimo's default 20 outer iterations while the FILL-IN block said 60.

The guidance names SOLVER = :altissimo as the one-line switch and forbids hand-rolling the solve call, stating each trap so the instruction carries its own justification.


Testing

  • amico-run: 938 passed, 12 skipped.
  • extension: 778 passed, 3 skipped (the full CI set, --exclude '**/slow/**').
  • tsc --noEmit clean in both packages.
  • Live, against the real staging service: frames fetched from a presigned URL and landed locally with 5-digit names (task 0fccbbf9); template runs green end to end (fidelity 0.99997, 60 iterations); local run writes norun.log (no duplication), and with TASK_ID set run.log appears with iter lines in exactly the format the poller parses.
  • One incidental fix that matters for CI: the CLI tests were inheriting the developer's~/.amico/amicode/solver-mode.json. On an hp machine nine local-solve tests failed — on correct behaviour. They now spawn with a hermetic ops dir.

CI caught the one consumer I missed on the frame rename (the Δ9 remote state-machine test); the assertion above it — run.log contains iter=7 — passed, which is independent confirmation in a second harness that the stats fix delivers iters end to end.


What this does NOT fix

  • Altissimo on the cloud. The runner AMI bakes a Piccolissimo/Altissimo predating callback support, so it emits nothing there until Altissimo#414 is released and the image rebuilt. The template is ready for that moment.
  • The Altissimo code path has never been executed. It is written against the verified contract and parses, but the local checkouts here are 287/324 commits behind and cannot run it. Worth exercising on a current env before anyone demos it.
  • Cold start. ~9–10 min from submit to first frame, for ~55s of compute. Real, and unaddressed here.
  • Existing agent-authored scripts in ~/.amico/problems/ predate emit(), so they still show frames and no numbers until re-authored from the updated template.

solverModeSection is exported to make the guidance testable (matching its already-exported sibling routingSection). The same one-line export is on #225 — identical change, so either merge order is fine.

Reviewer note

This narrows behaviour: a local Piccolissimo run that "worked" before — slowly, usually fatally — is now refused outright. That is the intent of a cloud-only paid tier, but it is a real change for anyone relying on the local path, and the way out is one click on the solver control.

…forced
Selecting Piccolissimo + Altissimo was supposed to mean "this solve runs in
Harmoniqs Cloud". In practice the agent kept dispatching it LOCALLY: the laptop
precompiled the whole HP stack (IPOPT included) until amico-run's process-group
timeout SIGTERMed Julia mid-precompile.
Three things allowed that, none of them the pieces people assumed were missing
(the API-key prompt and the entitlement flip were already built and work):
1. Nothing refused a local HP launch. The gate's tier=hpc rules are solid, but
they only fire on a spec that SAYS tier="hpc" — and runGate only runs for
--spec launches at all. A bare `amico-run script.jl` never reached them.
2. Selecting HP grants the `issimo` entitlement, so the import scan happily
admits a local `using Piccolissimo`. The entitlement that unlocks the tier
was also unlocking the failure mode.
3. The agent was told two contradictory things. solverModeSection() said "launch
HP solves in the cloud" while the routing section beside it said "routing is
PER-SOLVE and EXPLICIT, you confirm where EVERY solve runs, default local" —
and the base AGENTS.md step 5 said the same. Given a cloud-only tier and an
instruction to default local, it defaulted local.
The fix, in the same two-layer shape the tier already uses:
- ENFORCEMENT: amico-run refuses `--executor local` while HP is selected, at the
one choke point every run passes through (launch.ts, before the gate), so it
covers spec and no-spec runs alike. Reads the extension's solver-mode.json —
status only, never a token — and fails SAFE to piccolo on an absent or corrupt
file, so a fresh install and every free-tier local run behave exactly as now.
- GUIDANCE: the routing section now states the cloud-only contract instead of
asking a routing question, and AGENTS.md step 5 makes the injected section
authoritative over its local default. Piccolo sessions are untouched: the
section is still "" unless mode is hp AND the cloud is connected.
The estimate keeps its reporting role and loses its deciding role — an estimate
that fits in local RAM does not make an HP solve local.
Also names the service "Harmoniqs Cloud" everywhere a user can see it (gate
refusals, routing prose, AGENTS.md, the HP row's tooltips) instead of the
internal "company compute" / bare "the cloud". The wire id stays
`company-compute` — renaming it would break the credential route, the status
cache, and the fork's CONNECTION_IDS for no user-visible gain.
Tests: 9 new (the reader's fail-safe directions; the refusal, including on the
no-spec path and that it leaves no run dir; that a REMOTE HP launch still
completes, against FakeCloud) + the routing/AGENTS contract updated to pin the
new copy, with an explicit regression test that the per-solve question does not
come back. amico-run 934 passed, extension suite green apart from two live-model
E2Es (interview_e2e, scores_e2e) that fail identically with this change stashed —
pre-existing, tracked separately.
Incidental but load-bearing: the CLI tests were inheriting the DEVELOPER's
~/.amico/amicode/solver-mode.json, so on an hp machine nine local-solve tests
failed on correct behaviour. They now spawn with a hermetic ops dir.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Rchari1and others added 4 commits July 28, 2026 20:39
A cloud solve produced an empty Run Inspector. The client-side chain was fully
built — RemoteExecutor polls the cloud, synthesizes AMICODE_ITER lines into
run.log, writes frames, and touches run.log's mtime for the stall logic — but it
read the WRONG FIELDS, so it dropped everything:
stats client read `body.iters` · service returns `{task_id, stats[], submitter}`
frames client read `png_base64` · service returns `{task_id, iter, key, url, submitter}`
Both reads sit inside best-effort try/catch blocks ("stats are advisory"), so
the failure was completely silent: `undefined ?? []` iterated zero times, run.log
stayed 0 bytes, and the Inspector had nothing to tail. Confirmed against task
419a57e6 on staging, which ran 60 IPOPT iterations and banked 11 frames to S3
while the local run.log stayed empty.
The reason nobody caught it: fake_cloud.ts served the shapes the CLIENT read,
not the shapes the SERVICE returns. Every test passed against a fake that agreed
with the bug. That is the real defect here, so the fake is corrected first and
two tests now pin the live payloads by exact key set — a fake that mirrors the
client proves nothing.
- stats: read `stats`, falling back to `iters` so an older runner still works.
- frames: fetch the presigned url (no auth header — the signature IS the
credential) and keep the base64 lane for older runners. FakeCloud now serves
artifact bytes from a route placed BEFORE its auth guard, matching S3.
- frame filenames go 3-digit → 5-digit: iter_00007.png is what both the S3 layout
and the local Julia solve write, so cloud and local frames no longer land under
two different schemes in one run dir.
Also: the HP solver-mode guidance now covers the Altissimo backend. IPOPT stays
the default because it is what streams telemetry; Altissimo is used on request,
but the agent must state the trade first — Piccolissimo's solve!(::AltissimoOptions)
does not forward a caller callback to Altissimo.optimize!, and there is no
intermediate_callback on that path, so an Altissimo run emits no frames and no
AMICODE_ITER and the Inspector stays empty until it finishes. Never switch
silently, never claim live iterations on it.
amico-run: 936 passed. extension: 62 passed in the touched suites, tsc clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CI caught the one consumer I missed when frame filenames went 3-digit → 5-digit.
The Δ9 test waited on iter_007.png and timed out; the assertion above it —
run.log contains "iter=7" — PASSED, which is independent confirmation in a second
harness that the stats-field fix delivers iters end to end.
Also corrects solver-mode guidance shipped earlier today. It asserted that
Piccolissimo's solve!(::AltissimoOptions) does not forward a caller callback —
taken from a report without checking. That is true of the local checkout (287
commits behind) but false of main, which accepts `callback` and forwards it to
Altissimo.optimize!. The guidance now says live iterations depend on the INSTALLED
version and tells the agent not to promise iterations it has not observed.
extension: 776 passed, 3 skipped (the full CI set).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two gaps stood between a cloud solve and a populated Run Inspector. Both are in
the solve template, which ships PER SUBMISSION (the client uploads
<task_id>-solve.jl), so neither needs an AMI rebake.
1. NOTHING WROTE run.log IN THE CLOUD.
/solves/<id>/stats parses AMICODE_ITER lines out of run.log in the artifact
prefix, and the runner's sidecar populates that prefix with `aws s3 sync .` —
whatever is in the solve cwd. But julia's stdout on the runner goes to the SSM
command stream, so run.log was never written there: nothing to sync, stats: [],
empty inspector. The template's own comment ("AMICODE_PULSE lines on stdout (→
run.log)") was true locally and false in the cloud.
Every AMICODE_* line now goes through emit(), which also appends to run.log —
but ONLY when TASK_ID is set (the runner exports it). Locally amico-run's
executor already writes run.log from stdout, so appending unconditionally would
double every line and the inspector would count each iteration twice.
Verified: local run writes no run.log (11 frames, result.toml, fidelity
0.99997); with TASK_ID set, run.log appears with AMICODE_ITER lines in the
exact format the poller parses.
2. THE TELEMETRY WAS BOLTED TO IPOPT.
Frames came off IpoptOptions.intermediate_callback and the iter line off a
callback reading IpoptOptimizerState. Altissimo has neither: its only hook is
`callback` on optimize! (forwarded by Piccolissimo's solve!(::AltissimoOptions))
and it arrives as (x, info). So an Altissimo solve lost the frames TOO, leaving
the inspector completely dark rather than merely numberless.
SOLVER = :ipopt | :altissimo now selects the backend and re-hangs both channels.
`x` is the primal, so pulse_emit's solver-agnostic (primal, iter) contract takes
it unchanged — same frames, same AMICODE_PULSE, same cooperative STOP.
inf_pr/inf_du come from the callback tuple when present (Altissimo#414) and are
otherwise derived from eq_viol/ineq_viol/kkt_error, so it works on an older
Altissimo too instead of emitting NaN.
And a third drift found while proving (1): the poller only json.loads an
AMICODE_ITER payload starting with "{", so template-emitted key=value lines come
back as {raw: "iter=7 f=…"}. The client keyed on it.iter and skipped every one as
NaN — invisible because the smoke test seeds JSON. It now reconstructs the line
from either shape, and drops malformed records rather than emitting NaN.
Still NOT solved by this: Altissimo on the cloud. The runner AMI bakes a
Piccolissimo/Altissimo predating callback support, so it will emit nothing there
until Altissimo#414 is released and the image rebuilt. The template is ready for
that moment and works today on IPOPT (cloud) and both backends locally.
amico-run 938 passed · extension 776 passed · template runs green end to end.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
solve!(::AltissimoOptions) forwards a HARDCODED kwarg list to
Altissimo.optimize! and swallows the rest, so `max_iter = 60` passed to solve!
never arrived: the solve quietly ran Altissimo's default 20 outer iterations
while the FILL-IN block said 60. Silent, not an error — the worst kind.
Budget now rides AltissimoOptions(max_outer_iter = max_iter). Verified against
Piccolissimo main: the qcp-level solve! forwards kwargs... to the prob-level
method, which takes `callback` explicitly, so the callback wiring does reach
optimize! — it is only the iteration budget that was being dropped.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The template gained a SOLVER flag but nothing told the agent it exists, so an
agent asked for Altissimo would hand-roll a solve call — and silently lose both
telemetry channels plus the iteration budget:
- frames come off IpoptOptions.intermediate_callback, which AltissimoOptions
has no equivalent of, so a hand-written call leaves the Run Inspector fully
dark rather than merely numberless;
- a `max_iter` passed to solve!(::AltissimoOptions) lands in kwargs... and is
dropped, so the solve quietly runs Altissimo's default 20 outer iterations;
- inf_pr/inf_du need deriving from eq_viol/ineq_viol/kkt_error on Altissimo
builds that predate #414.
The template already handles all three. The guidance now says the switch is ONE
line — `SOLVER = :altissimo` in the FILL-IN block — and states each trap, so the
instruction carries its own justification rather than reading as arbitrary.
solverModeSection is exported to make it testable, matching its already-exported
sibling routingSection. (The same one-line export is on #225; identical change,
so either merge order is fine.)
extension: 778 passed, tsc clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@Rchari1Rchari1 changed the title feat(hp): Piccolissimo + Altissimo is cloud-only, and now actually enforcedfeat(hp): cloud-only enforcement + cloud iters/frames reach the Run InspectorJul 29, 2026
# Conflicts:
#	packages/extension/src/opencode_config.ts
#	packages/extension/test/agents_md.test.ts
@jack-champagne
jack-champagneforce-pushed the rchari/hp-cloud-only branch 2 times, most recently from 54f4275 to 8aa362fCompareJuly 29, 2026 04:24
@jack-champagne
jack-champagne merged commit 199b23d into mainJul 29, 2026
10 checks passed
jeonghun-jj-lee pushed a commit that referenced this pull request Aug 21, 2026
Add WorkspaceTreeProvider — renders all workspace folders as collapsible
roots, expands recursively via vscode.workspace.fs.readDirectory(),
respects files.exclude, shows theme icons via resourceUri, opens files
on click, context menus (Copy Path, Reveal), and live-updates via
FileSystemWatcher. Replaces amicode.armonia placeholder; Catalog already
removed in #457.
package.json: rename amicode.armonia → amicode.workspace ("Workspace"),
keep runInspector, update activationEvents onView:workspace, add
view/item/context for workspace files.
Follows opencode#215 decision surface: Location.Ref.directories carries
all roots, no external-directory prompts, instructions stacked per dir.
Pairs with opencode PR #219 (engine: schema + boundary + env + instruction
multi-root + DB).
jeonghun-jj-lee added a commit that referenced this pull request Aug 22, 2026
…, #215)
* fix(ui): remove pulse catalog + CATALOG sidebar tab and widgets (#457)
Purge the legacy UX3 session catalog UI that predates the vault-backed
CatalogStore (Q94/Q95, packages/amico-run). The CATALOG activity-bar tab
(SessionCatalogTree → amicode.catalog, amicode.catalog.save/refresh/remove,
catalogCard webview + catalogcard component) is superseded by the mount stack
+ pack interface (WS1 #391) and is also slated for removal in opencode#215 AC7.
- package.json: drop amicode.catalog view + catalog commands/menus
- trees.ts: delete SessionCatalogTree/CATALOG_KEY, keep armonia placeholder
(will be replaced by Workspace tree per 215)
- extension.ts: remove registerCatalogCard + catalog save/refresh/remove
wiring, simplify amicode.savePulse to file-only Save dialog,
drop demo promote-to-catalog prompt, remove runs_manager promote-to-
catalog prompt (now info-only)
- esbuild.config.mjs: drop catalog_card_webview bundle
- delete catalog_card_shell/webview, media catalogcard, test suite
Pulse save stays via Save to file… → savePulseTo; vault CatalogStore
(packages/amico-run) untouched. Closes#457
* feat(workspace): Workspace sidebar for multi-root (opencode#215 AC6)
Add WorkspaceTreeProvider — renders all workspace folders as collapsible
roots, expands recursively via vscode.workspace.fs.readDirectory(),
respects files.exclude, shows theme icons via resourceUri, opens files
on click, context menus (Copy Path, Reveal), and live-updates via
FileSystemWatcher. Replaces amicode.armonia placeholder; Catalog already
removed in #457.
package.json: rename amicode.armonia → amicode.workspace ("Workspace"),
keep runInspector, update activationEvents onView:workspace, add
view/item/context for workspace files.
Follows opencode#215 decision surface: Location.Ref.directories carries
all roots, no external-directory prompts, instructions stacked per dir.
Pairs with opencode PR #219 (engine: schema + boundary + env + instruction
multi-root + DB).
* fix(workspace): full context menus for Workspace view (opencode#215 AC6)
Add New File/Folder, Rename, Delete, Copy Path/Relative, Reveal,
Open in Terminal to amicode.workspace viewItem context. Ensures
AC6 full context-menu coverage; Run Inspector stays as separate
webview (amicode.runInspector) not a directory in the tree.
* fix(workspace): only hide .git directory, not .gitignore/.github/etc
* feat(workspace): Chat with Amico button, full context menus, Remove from Workspace
- Add 'Chat with Amico' as first tree item with custom yellow SVG icon
that mutes (gray) when a chat tab is open
- Register custom workspace commands (newFile, newFolder, rename, delete,
copyPath, copyRelativePath, revealInOS, openInTerminal, openToSide,
removeFromWorkspace, addFolder) since built-in explorer.* commands
don't fire in custom tree views
- Remove stale amicode.runInspector webview declaration (provider was
deleted in PR #351, only the package.json entry remained)
- Add viewsWelcome for empty workspace state
- Listen to onDidChangeWorkspaceFolders to keep tree in sync
- Add ChatPanel.onLiveChange callback for cross-component state tracking
- Fix credential_scanner e2e test: guard opencode-provider assertion
(machine may have creds under different provider names)
- Add comprehensive test suite for workspace tree and context-menu commands
(24 tests covering tree rendering, all commands, muted state)
---------
Co-authored-by: Raghav Chari <raghavchari2021@gmail.com>
Sign up for freeto 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.

2 participants

@Rchari1@jack-champagne