Repository navigation
PRD: Ship canonical opencode with Amicode — retire the fork-built binary from user machines #451
Description
PRD: Ship canonical opencode with Amicode — retire the fork-built binary from user machines
Important
Problem
Amicode vendors one fork-built binary (harmoniqs/opencode, pinned v1.18.10-amicode.11) that plays two roles: the opencode CLI users get in the Amicode terminal, and the chat server carrying the entire Amicode product surface (18 server route modules + the customized app UI the deck iframes). It updates only when a new VSIX ships, so users sit marketplace-cadence behind upstream anomalyco/opencode (3 releases / 5 days behind at harmoniqs/opencode#159) and upstream bugfixes reach them late.
Approach
Migrate the Amicode surface onto layers that run on a stock canonical binary — the standard plugin loader (the amicode tool pack), config injection (persona/skills/permissions), an extension-host HTTP service (the 18 amicode routes + widget runtime), and an extracted app bundle composing upstream app components — then cut over in ONE release with zero fork-built bytes shipped, plus a runtime auto-updater that adopts new canonical releases after a smoke gate. Key reason: this solves the binary-currency problem outright and converts the maintenance coupling from weekly monorepo merges into a CI-pinned overlay against canonical's public client API.
Approaches Considered
- Bridge then flip (updater tracks fork releases now, flip later) — rejected: ships fork bytes indefinitely; the researcher chose one push.
- Flip immediately, migrate later — rejected: the 18 routes and the iframed app UI die on a stock binary; migration must complete first.
- Fork forever, tightly synced (weekly automated upstream merges) — rejected: users always one merge-hop behind; a bad merge stalls the whole fleet; sentinel cost is permanent.
Scope
In: migration manifest + five M0 feasibility gates; extension-host amicode service (18 route modules, 7 widget sources, parallel-run harness); app-bundle extraction incl. the 72 i18n keys; internal server cutover + dogfood soak; runtime updater (managed install, adopt gate, fault-injection + adopt-lag drills); the cutover release, fork archival, and retiring the #159 sync sentinel.
Out: no incremental user-visible rollout (single cutover release); no touching users' opencode installs outside VS Code; no upstream feature PRs beyond what cutover requires; no new platforms (matrix stays darwin-arm64 / linux-arm64 / linux-x64, Windows via WSL).
Assumptions / Open Qs
Five unknowns are M0 gates, each answered with evidence before code moves: (1) canonical release assets carry verifiable digests; (2) the per-boot route-auth password flow has a stock-binary equivalent; (3) the widget/deck iframe origin model tolerates an extension-host service origin; (4) plugin registration is assertable on a stock binary; (5) every adopt-gate check has a confirmed signal source on stock canonical. The plan refuses to proceed on an unverified guess.
User Stories
- Onboarding — a researcher installs Amicode on a clean machine (even offline): the Amicode terminal's
opencodeis canonical upstream, amicode-aware (persona, skills, tools active), seeing the same sessions as the app. - Currency — upstream ships a release; within 48h a connected user runs it, without a VSIX release or any action on their part.
- Broken release — an upstream release breaks our surface: the adopt gate fails, the user keeps last-known-good silently, sessions intact.
- Existing user — upgrades across the cutover: every session written by the fork-built server remains readable; no capability they use today regresses.
Acceptance Criteria
fresh_install_fork_binaries == 0— clean VM per platform (incl. WSL), offline: terminal opencode resolves to the vendored canonical bootstrap; no file matches any fork release-asset sha256.vsix_fork_binary_refs == 0— shipped lock names anomalyco/opencode, no-amicode.tag, no vendored binary sha from the fork (docs/skills textual mentions out of scope).route_parity_pct == 100— 18/18 amicode route modules pass fork-derived contract tests against the extension-host service (golden fixtures from the live fork server for modules known-red on the fork).app_e2e_unexpected_failures == 0— ported app e2e suite: every failure on the quarantine list frozen at M0; anything else blocks.drill_adopt_lag_h <= 48— a gate-passing upstream release goes from publish to adoption marker within 48h on a connected test machine.failed_update_data_loss == 0— corrupted-download, plugin-load-failure, and app-health-failure drills each end with sessions intact and last-known-good current.cutover_session_retention_pct == 100— dogfood DB + synthetic corpus (≥500 sessions, all part types) structurally identical pre/post cutover.
Key Decisions
- One push — no incremental user-visible rollout; the cutover release is the first user-visible change and ships zero fork-built bytes.
- Managed canonical wins — inside the Amicode terminal the extension-managed binary resolves first on PATH; user installs elsewhere untouched.
- Auto-adopt after smoke gate — activation + daily checks; adoption is an atomic symlink swap only after: digest verification, boot smoke, plugin-registration assert for the amicode tool pack, and app + service health against a consistent (sqlite-backup) copy of the live DB.
- Port, don't rewrite — the 18 route modules move wholesale into the extension host; parity is judged by fork-derived contract tests, with golden fixtures recorded from the running fork server for the modules whose tests are known-red on the fork itself.
- Vocabulary — upstream =
anomalyco/opencode("canonical opencode"); the fork is never user-facing. Code comments that call the fork binary "canonical" are corrected in the app-bundle milestone.
Modules & Interfaces
- Plugin pack (unchanged) — amicode tools via opencode's standard plugin loader; single-export contract.
- Config injection (unchanged) — spawn-time config content carrying persona, skills paths, permissions, plugin paths.
- Extension-host amicode service (new) — the ported routes serving widgets, vault browser, connections, and amicode app calls; the deck shell's iframe origin check widens to it.
- App bundle (new, extracted from the fork) — composes upstream app components; CI-pinned against canonical releases; owns the amicode i18n keys.
- Runtime updater (new) — managed install under the amico state dir with a
currentsymlink; the VSIX vendors one canonical copy per platform as offline bootstrap.
Testing Decisions
Parity via fork-derived contract tests + golden fixtures; app e2e ported with a frozen quarantine list; three fault-injection drills plus a live adopt-lag drill; retention corpus ≥500 sessions; clean-VM matrix per platform including WSL; parallel-run harness (extension service alongside the fork server) as the migration's verification backbone.
Risks
- App↔canonical client-API drift becomes the recurring post-cutover cost; CI-pinned overlay is the mitigation, and breaking releases hold users on last-known-good by design.
- The app-bundle extraction is the long pole; the parallel-run harness de-risks taking it slowly.
- Post-adoption breakage (passes gate, breaks later) heals on the next release; the existing binary-override setting remains the manual escape hatch.
- Supply chain: TLS + GitHub-API digest only, no third-party mirrors; digest absence refuses adoption.
Constraints & Invariants
- Last-known-good is never deleted; adoption only via the full adopt gate.
- The chat DB stays pinned by the extension across every spawn — the binary never picks its DB by build channel (the 2026-08-08 channel-flip incident).
- No coupling to canonical internals; supported surfaces are the plugin API, config injection, and the documented client HTTP API. No feature is added by re-forking.
- The fork's default branch is never force-pushed; the repo is archived, not deleted.
- All work on Harmoniqs repos goes through issue + PR (development gate).
Prior Art / Patterns
- Vault spec + compiled plan (durable record):
armonia-aaron-trowbridge/amicode/specs/spec-20260820-044920-ship-canonical-opencode.mdandamicode/plans/plan-20260820-044920-ship-canonical-opencode.md - Automate upstream sync: merge anomalyco/opencode v1.18.12 → v1.18.15+ before next amicode release opencode#159 — fork-side upstream-sync automation (retired by this work)
- AMICODE-PATCHES.md in the fork — authoritative patch log and sync history
- The extension's binary-resolution override (config → vendored) and the vendoring lock/script that already parameterize the source repo
- The staging-project config-injection pattern (persona + skills + plugins onto a server binary) — proof the amicode-info layer is already binary-agnostic
Source
Durable record: personal Armonia vault, amicode/specs/spec-20260820-044920-ship-canonical-opencode.md (deliberated 2026-08-20, manual adversarial review — 1 blocking contradiction fixed, 9 advisories) · compiled plan at amicode/plans/plan-20260820-044920-ship-canonical-opencode.md · related: harmoniqs/opencode#159
Notes
Decisions locked with the researcher 2026-08-20: one-push cutover; managed-canonical PATH precedence inside the Amicode terminal; auto-adopt after smoke gate. The spec's deliberation was hand-run (spec tooling unavailable on the host) — a weaker review claim than a tooled one, noted for the record.
Activity
docs(migration): M0 manifest — canonical opencode cutover; sst/openco…
docs(adr): Harness Contract v1 — the fixture-pinned opencode API subs…