From 90f49f561e31e7d027d3e9daad571d8dfd5d7fc5 Mon Sep 17 00:00:00 2001 From: aaron Date: Sun, 9 Aug 2026 09:59:59 -0400 Subject: [PATCH 1/3] feat: vault ecosystem auto-setup on first activate (personal + public vault, mounts.toml) (#312) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - vault_setup.ts: ensureVaultEcosystem() — idempotent, never throws: ensureArmoniaDataDirs, ensurePersonalVault, ensurePublicVault (shallow clone vault-public, 10s cap, offline placeholder), ensureMountsToml (absent-only, personal rw first, public ro second) - extension.ts: call ensureVaultEcosystem() before first prepareOpencodeProject so mount stack is correct on boot; remove now-redundant ensureDefaultPersonalVault - trees.ts: update Armonia placeholder to reflect auto-provision - tools/bootstrap-armonia.sh: mirror same three vault steps as idempotent block (CLI/extension order agnostic), curl-able - tools/migrate-to-armonia.sh: three-bucket repos routing (packages/, demos/, flat), bucket-converging on re-run - skills/amico-vault/SKILL.md: vault-* naming in kinds table, new First-run lifecycle subsection for vault-public (kind=public, ro) --- .../extension/skills/amico-vault/SKILL.md | 24 +- packages/extension/src/extension.ts | 43 ++- .../extension/src/substrate/vault_setup.ts | 216 ++++++++++++++ packages/extension/src/trees.ts | 9 +- tools/bootstrap-armonia.sh | 265 ++++++++++++++++++ tools/migrate-to-armonia.sh | 237 ++++++++++++++++ 6 files changed, 761 insertions(+), 33 deletions(-) create mode 100755 tools/bootstrap-armonia.sh create mode 100755 tools/migrate-to-armonia.sh diff --git a/packages/extension/skills/amico-vault/SKILL.md b/packages/extension/skills/amico-vault/SKILL.md index 351fc02f..b11c2b7c 100644 --- a/packages/extension/skills/amico-vault/SKILL.md +++ b/packages/extension/skills/amico-vault/SKILL.md @@ -65,14 +65,24 @@ The vault is no longer a single directory. A user's **Armonia** is the set of va | Kind | Marker | Repo naming | Holds | Writable | |------|--------|-------------|-------|----------| -| **personal** | `kind = "personal"` | `armonia-` | Own research notes, hopper, solo specs/plans, session distillates, experiments-in-progress | single-writer (you) | -| **engagement** | `kind = "engagement"` | `armonia-` | Engagement-scoped notes; lab state (`lab.toml`, device/model-of-lab notes, calibration, per-lab catalog) once hardware deploys | engagement staff | -| **project** | `kind = "project"` | `armonia-` | Proprietary-package knowledge (your private package internals, hopper, insights) | per-person grant | -| **team** | `kind = "team"` | `armonia-` | Your team knowledge tier: hardware/control context, methods + patterns, strategy/specs/plans, experiments + insights, papers, people/orgs, central pulse catalog (git-lfs) | PR-gated promotion | -| **public** | `kind = "public"` | `/armonia` | Best-practice usage patterns for public packages, hazard notes, platform cards, recipes | world read-only | +| **personal** | `kind = "personal"` | `vault-` (e.g. `vault-aaron`) | Own research notes, hopper, solo specs/plans, session distillates, experiments-in-progress | single-writer (you) | +| **engagement** | `kind = "engagement"` | `vault-` | Engagement-scoped notes; lab state (`lab.toml`, device/model-of-lab notes, calibration, per-lab catalog) once hardware deploys | engagement staff | +| **project** | `kind = "project"` | `vault-` | Proprietary-package knowledge (your private package internals, hopper, insights) | per-person grant | +| **team** | `kind = "team"` | `vault-` (e.g. `vault-team` / `armonissima`) | Your team knowledge tier: hardware/control context, methods + patterns, strategy/specs/plans, experiments + insights, papers, people/orgs, central pulse catalog (git-lfs) | PR-gated promotion | +| **public** | `kind = "public"` | `harmoniqs/vault-public` | Best-practice usage patterns for public packages, hazard notes, platform cards, recipes | world read-only | **Read precedence: personal → engagement → project(s) → team → public.** Queries search the **union** of all mounts; on a path collision the higher-precedence mount wins (first hit). `mounts.toml` (in `~/.amico/`) overrides order and writability; absent, kind-order applies. A dir with no marker, a duplicate id, or a manifest `path` that doesn't exist is dropped from the mount set with a warning in the hook summary — never guessed at, never fatal. +### First-run lifecycle (auto-provision) + +A fresh Marketplace install gets a working vault ecosystem with zero commands: + +1. **Personal vault** — `ensureVaultEcosystem()` creates `~/armonia/data/vaults//` (`kind="personal"`, local `git init`, no remote) if no personal mount resolves. Offline-tolerant, never throws; activation continues unpersonalized on failure. +2. **Public vault** — shallow-clones `harmoniqs/vault-public` to `~/armonia/data/vaults/vault-public/` (`kind="public"`, `writable=false`, 10s timeout, anonymous https). On offline / no-git / timeout it creates a placeholder dir with a `kind="public"` marker so the mount stack still resolves. +3. **`mounts.toml`** — written *only if absent* (personal rw first, public ro second). Presence means user-managed — never overwritten. The same three steps are mirrored in `tools/bootstrap-armonia.sh` so CLI-first or extension-first order is safe (second run is a no-op). + +The canonical on-disk root is `~/armonia/` (`repos/` = versioned source, `data/` = managed state); `~/.amico/vaults` is a symlink into `~/armonia/data/vaults` for backward compat. + ### Write routing (Claude is the resolver pre-Amicode) Route every note-write by intent: @@ -81,7 +91,7 @@ Route every note-write by intent: |---|---|---| | spec / plan for **shared** work | **team** (your team vault) | PR flow | | lab state, calibration, device params, engagement notes | **engagement** vault | direct commit | -| proprietary-package knowledge, solver hopper items | **project** vault (e.g. `armonia-`) | direct commit | +| proprietary-package knowledge, solver hopper items | **project** vault (e.g. `vault-`) | direct commit | | personal research, sessions, scratch, solo specs | **personal** vault | direct commit (auto-synced) | | **ambiguous** | ask the user once → default personal | — | @@ -102,7 +112,7 @@ Never put the mechanism in a `team`/`public` note expecting a later scrub — au Crystallization to the company vault is double-gated: **gate 1** = author tags `visibility: team`; **gate 2** = a human merges the dream-promote PR. On promotion: -- The **copy** lands in the **team** vault carrying `promoted_from: armonia-` + `promoted_date: YYYY-MM-DD`. +- The **copy** lands in the **team** vault carrying `promoted_from: vault-` + `promoted_date: YYYY-MM-DD`. - The **original stays put** in its source vault and gains `promoted_to: "[[]]"` (a frontmatter-only stamp written back *only after* the PR merges). It is never re-proposed. - Move is wrong — copy preserves the source vault's local graph and the provenance backlink. `promoted_from`/`promoted_date`/`promoted_to` are the charter/12 provenance fields, carried over unchanged. diff --git a/packages/extension/src/extension.ts b/packages/extension/src/extension.ts index 2f77b71d..423d4b39 100644 --- a/packages/extension/src/extension.ts +++ b/packages/extension/src/extension.ts @@ -42,7 +42,12 @@ import { runSetCloudKeyCommand } from "./cloud_key"; import { amicodeOpsDir } from "./substrate/vault_store"; import { stagePasqalConnector } from "./pasqal_assets"; import { needsProvision, pasqalVenvDir, provisionPasqalPython } from "./pasqal_python"; -import { createLocalPersonalVault, sanitizeVaultName, suggestVaultName } from "./substrate/vault_setup"; +import { + createLocalPersonalVault, + sanitizeVaultName, + suggestVaultName, + ensureVaultEcosystem, +} from "./substrate/vault_setup"; import { pinnedJuliaMinor, hasJuliaup, @@ -431,6 +436,20 @@ export async function activate(ctx: vscode.ExtensionContext): Promise { const parsed = parseLibraryRootSpecs(raw); return parsed.length ? parsed : undefined; }; + // Vault ecosystem — first-run auto-provision (personal + public + mounts.toml). + // Idempotent and never throws; runs before the first project prep so the + // mount stack is correct on boot. The named `amicode.setupVault` command + // remains for manual re-entry. + try { + const eco = ensureVaultEcosystem(); + if (eco.personal) opencodeChannel.appendLine(`[vault] auto-provisioned local personal vault: ${eco.personal.path} (git=${eco.personal.gitInit})`); + if (eco.publicCloned) opencodeChannel.appendLine(`[vault] public vault cloned: vault-public`); + else if (eco.publicPlaceholder) opencodeChannel.appendLine(`[vault] public vault placeholder (offline or no git)`); + if (eco.mountsWritten) opencodeChannel.appendLine(`[vault] mounts.toml written`); + } catch (e) { + opencodeChannel.appendLine(`[vault] ecosystem ensure failed: ${(e as Error).message}`); + } + const opencodeProject = prepareOpencodeProject({ agentsSrc: path.resolve(ctx.extensionPath, "AGENTS.md"), // MODE-SELECTED vetted template: HP sessions get the Piccolissimo variant @@ -851,28 +870,6 @@ export async function activate(ctx: vscode.ExtensionContext): Promise { void vscode.window.showInformationMessage(`Amicode: personal vault "${created.name}" created and active.`); }; ctx.subscriptions.push(vscode.commands.registerCommand("amicode.setupVault", () => void runVaultSetup(true))); - // Personal vault by default. The onboarding wizard (opencode-side) writes the - // profile but NOT a vault, and a genuine first-timer has none — so Amico would - // have nowhere to remember them (distiller disabled, session unpersonalized). - // Silently provision a LOCAL personal vault on first run when none resolves — - // no modal, like the Julia project. The `amicode.setupVault` command remains - // for naming / re-creating; the wizard finale offers attaching other vaults. - // Failure-tolerant: a creation error just leaves the session unpersonalized. - const ensureDefaultPersonalVault = async (): Promise => { - if (personalMount(resolveMountStack())) return; - let created; - try { - created = createLocalPersonalVault(defaultVaultsRoot(), suggestVaultName()); - } catch (e) { - opencodeChannel.appendLine(`[vault] default personal vault not created: ${(e as Error).message}`); - return; - } - opencodeChannel.appendLine( - `[vault] auto-provisioned local personal vault: ${created.path} (git=${created.gitInit})`, - ); - await respawnForVault(); - }; - void ensureDefaultPersonalVault(); // Julia setup (#8): amicode manages the Julia toolchain via juliaup — install // juliaup if absent, add the channel pinned to the Manifest's MINOR, and diff --git a/packages/extension/src/substrate/vault_setup.ts b/packages/extension/src/substrate/vault_setup.ts index f1093316..1d3a3cba 100644 --- a/packages/extension/src/substrate/vault_setup.ts +++ b/packages/extension/src/substrate/vault_setup.ts @@ -40,12 +40,33 @@ function safeUsername(): string { } } +export const PUBLIC_VAULT_DIR = "vault-public"; +export const PUBLIC_VAULT_REPO = "https://github.com/harmoniqs/vault-public.git"; +export const PUBLIC_VAULT_KIND = "public"; + +function defaultVaultsRoot(): string { + return path.join(os.homedir(), ".amico", "vaults"); +} +function defaultMountsTomlPath(): string { + return path.join(os.homedir(), ".amico", "mounts.toml"); +} +function armoniaDataRoot(): string { + return path.join(os.homedir(), "armonia", "data"); +} + export interface CreatedVault { path: string; name: string; gitInit: boolean; } +export interface VaultEcosystemResult { + personal?: CreatedVault; + publicCloned: boolean; + publicPlaceholder: boolean; + mountsWritten: boolean; +} + /** * Create a local personal vault. Refuses to clobber an existing directory. * `git init` is best-effort (the vault is fully functional without git). @@ -73,3 +94,198 @@ export function createLocalPersonalVault( } return { path: dir, name, gitInit }; } + +/** Ensure ~/armonia/data/{env,problems,runs,vaults} and ~/.amico symlinks. Never throws. */ +export function ensureArmoniaDataDirs(): void { + try { + const dataRoot = armoniaDataRoot(); + for (const sub of ["env", "problems", "runs", "vaults"]) { + try { + fs.mkdirSync(path.join(dataRoot, sub), { recursive: true }); + } catch {} + } + // Wire ~/.amico/ -> ~/armonia/data/ when safe. + const links: Array<[string, string]> = [ + ["julia", "env"], + ["problems", "problems"], + ["runs", "runs"], + ["vaults", "vaults"], + ]; + const amico = path.join(os.homedir(), ".amico"); + try { + fs.mkdirSync(amico, { recursive: true }); + } catch {} + for (const [srcName, targetName] of links) { + const src = path.join(amico, srcName); + const dest = path.join(dataRoot, targetName); + try { + if (fs.lstatSync(src).isSymbolicLink()) continue; + } catch {} + try { + if (fs.existsSync(src)) { + // Real dir with content → migration case, don't clobber. + const entries = fs.readdirSync(src); + if (entries.length > 0) continue; + fs.rmdirSync(src); + } + fs.symlinkSync(dest, src); + } catch {} + } + } catch {} +} + +/** Ensure the public vault (vault-public, kind=public, ro). Never throws. */ +export function ensurePublicVault(opts: { + vaultsRoot?: string; + repo?: string; + timeoutMs?: number; +} = {}): { cloned: boolean; placeholder: boolean } { + const vaultsRoot = opts.vaultsRoot ?? defaultVaultsRoot(); + const repo = opts.repo ?? PUBLIC_VAULT_REPO; + const timeoutMs = opts.timeoutMs ?? 10_000; + const dir = path.join(vaultsRoot, PUBLIC_VAULT_DIR); + const marker = path.join(dir, ".amico-vault.toml"); + + // Already present (any kind) → no-op. + try { + if (fs.existsSync(marker)) return { cloned: false, placeholder: false }; + } catch {} + if (fs.existsSync(dir)) { + // Dir exists without marker → seed marker as public. + try { + fs.writeFileSync(marker, `kind = "${PUBLIC_VAULT_KIND}"\nname = "${PUBLIC_VAULT_DIR}"\n`); + return { cloned: false, placeholder: true }; + } catch { + return { cloned: false, placeholder: false }; + } + } + + // Try shallow clone. + try { + execFileSync("git", ["clone", "--depth", "1", "--single-branch", repo, dir], { + stdio: "ignore", + timeout: timeoutMs, + }); + // Ensure marker is public (repo should already carry it). + try { + const text = fs.readFileSync(marker, "utf8"); + if (!text.includes(`kind = "${PUBLIC_VAULT_KIND}"`)) { + fs.writeFileSync(marker, `kind = "${PUBLIC_VAULT_KIND}"\nname = "${PUBLIC_VAULT_DIR}"\n`); + } + } catch { + try { + fs.writeFileSync(marker, `kind = "${PUBLIC_VAULT_KIND}"\nname = "${PUBLIC_VAULT_DIR}"\n`); + } catch {} + } + return { cloned: true, placeholder: false }; + } catch {} + + // Offline / no-git / timeout → placeholder. + try { + fs.mkdirSync(dir, { recursive: true }); + fs.writeFileSync(marker, `kind = "${PUBLIC_VAULT_KIND}"\nname = "${PUBLIC_VAULT_DIR}"\n`); + try { + fs.writeFileSync(path.join(dir, "README.md"), "# vault-public (offline placeholder)\n\nCloned on next online activate.\n"); + } catch {} + return { cloned: false, placeholder: true }; + } catch { + return { cloned: false, placeholder: false }; + } +} + +/** Ensure ~/.amico/mounts.toml exists with personal + public precedence. Absent-only. */ +export function ensureMountsToml(opts: { + mountsTomlPath?: string; + vaultsRoot?: string; +} = {}): boolean { + const mountsTomlPath = opts.mountsTomlPath ?? defaultMountsTomlPath(); + const vaultsRoot = opts.vaultsRoot ?? defaultVaultsRoot(); + try { + if (fs.existsSync(mountsTomlPath)) return false; + } catch {} + // Resolve personal name from actual dirs after ensure steps. + let personalId: string | undefined; + try { + const entries = fs.readdirSync(vaultsRoot); + for (const base of entries) { + const marker = path.join(vaultsRoot, base, ".amico-vault.toml"); + try { + const text = fs.readFileSync(marker, "utf8"); + if (text.includes('kind = "personal"')) { + const m = text.match(/name\s*=\s*"([^"]+)"/); + personalId = m ? m[1] : base; + break; + } + } catch {} + } + } catch {} + + const lines: string[] = []; + if (personalId) { + lines.push("[[mount]]"); + lines.push(`id = "${personalId}"`); + lines.push(`kind = "personal"`); + lines.push(`writable = true`); + lines.push(""); + } + // Public entry — always emitted if vault-public dir exists (even placeholder). + const publicDir = path.join(vaultsRoot, PUBLIC_VAULT_DIR); + let hasPublic = false; + try { + hasPublic = fs.existsSync(path.join(publicDir, ".amico-vault.toml")); + } catch {} + if (hasPublic) { + lines.push("[[mount]]"); + lines.push(`id = "${PUBLIC_VAULT_DIR}"`); + lines.push(`kind = "${PUBLIC_VAULT_KIND}"`); + lines.push(`writable = false`); + lines.push(""); + } + if (lines.length === 0) return false; + try { + fs.mkdirSync(path.dirname(mountsTomlPath), { recursive: true }); + fs.writeFileSync(mountsTomlPath, lines.join("\n")); + return true; + } catch { + return false; + } +} + +/** Full first-run ecosystem: data dirs + personal + public + mounts.toml. Never throws. */ +export function ensureVaultEcosystem(opts: { + vaultsRoot?: string; + mountsTomlPath?: string; + publicRepo?: string; + hint?: string; +} = {}): VaultEcosystemResult { + const vaultsRoot = opts.vaultsRoot ?? defaultVaultsRoot(); + const mountsTomlPath = opts.mountsTomlPath ?? defaultMountsTomlPath(); + const result: VaultEcosystemResult = { publicCloned: false, publicPlaceholder: false, mountsWritten: false }; + + ensureArmoniaDataDirs(); + + // Personal — silent auto-provision if none resolves. + let hasPersonal = false; + try { + const entries = fs.readdirSync(vaultsRoot); + for (const base of entries) { + try { + const text = fs.readFileSync(path.join(vaultsRoot, base, ".amico-vault.toml"), "utf8"); + if (text.includes('kind = "personal"')) { hasPersonal = true; break; } + } catch {} + } + } catch {} + if (!hasPersonal) { + try { + const created = createLocalPersonalVault(vaultsRoot, suggestVaultName(opts.hint)); + result.personal = created; + } catch {} + } + + const pub = ensurePublicVault({ vaultsRoot, repo: opts.publicRepo }); + result.publicCloned = pub.cloned; + result.publicPlaceholder = pub.placeholder; + + result.mountsWritten = ensureMountsToml({ mountsTomlPath, vaultsRoot }); + return result; +} diff --git a/packages/extension/src/trees.ts b/packages/extension/src/trees.ts index 51576122..e7835585 100644 --- a/packages/extension/src/trees.ts +++ b/packages/extension/src/trees.ts @@ -107,9 +107,12 @@ export function registerTrees(ctx: vscode.ExtensionContext): { armonia: PlaceholderTree; } { const catalog = new SessionCatalogTree(ctx); - // amicode#204: the single Armonia panel. Its roots are the mounted Vaults; - // until ArmoniaService lands, a product empty state names what collects here. - const armonia = new PlaceholderTree("Your vaults collect here — run Amicode: Set up a vault"); + // amicode#204: single Armonia panel. Until ArmoniaService lands, a product + // empty state names the canonical location. First-run auto-provisions + // personal + vault-public (offline → placeholder) and writes mounts.toml. + const armonia = new PlaceholderTree( + "Your vaults live in ~/armonia/data/vaults — personal + vault-public after first run", + ); ctx.subscriptions.push( vscode.window.registerTreeDataProvider("amicode.catalog", catalog), diff --git a/tools/bootstrap-armonia.sh b/tools/bootstrap-armonia.sh new file mode 100755 index 00000000..ff106596 --- /dev/null +++ b/tools/bootstrap-armonia.sh @@ -0,0 +1,265 @@ +#!/usr/bin/env bash +set -euo pipefail + +# bootstrap-armonia.sh +# Fresh-machine setup for the canonical Amicode workspace layout. +# Any user — Harmoniqs dev or external contributor — gets the same tree: +# +# ~/armonia/repos/packages/ Julia libraries (Piccolo.jl, …) +# ~/armonia/repos/demos/ demo galleries (atoms-demo, …) +# ~/armonia/repos/ apps, forks, projects (amicode, …) +# ~/armonia/data/{env,problems,runs,vaults} +# +# Usage: +# bootstrap-armonia.sh [--minimal|--standard|--full] +# +# --minimal layout + data dirs only (extension users who never touch source) +# --standard + public packages and demos via plain git clone (default) +# --full + private Harmoniqs repos via `gh` (requires gh auth with access) +# +# Curl-able for users who have not cloned anything: +# bash <(curl -fsSL https://raw.githubusercontent.com/harmoniqs/amicode/main/tools/bootstrap-armonia.sh) +# +# Idempotent: existing clones are skipped (git pull --ff-only is attempted), +# existing symlinks are left alone. + +ARMONIA="${HOME}/armonia" +AMICO="${HOME}/.amico" +TIER="standard" + +for arg in "$@"; do + case "$arg" in + --minimal|--standard|--full) TIER="${arg#--}" ;; + -h|--help) + sed -n '2,22p' "$0"; exit 0 ;; + *) echo "unknown arg: $arg" >&2; exit 64 ;; + esac +done + +# Public Julia libraries (registered packages; plain git clone works). +PUBLIC_PACKAGES=( + Piccolo.jl + NamedTrajectories.jl + DirectTrajOpt.jl +) + +# Public demo galleries. +PUBLIC_DEMOS=( + atoms-demo + fluxonium-demo + ions +) + +# Private Harmoniqs repos (cloned with gh; requires org access). +PRIVATE_PACKAGES=( + Piccolissimo.jl +) +PRIVATE_APPS=( + amicode +) + +# =================================================================== +main() { + echo "==> bootstrap-armonia tier=${TIER}" + echo + + make_layout + wire_amico_links + ensure_vault_ecosystem + + case "$TIER" in + minimal) echo "tier=minimal — no repos cloned" ;; + standard) clone_public ;; + full) clone_public; clone_private ;; + esac + + echo + echo "==> Done." + echo " Layout: ~/armonia/{repos/{packages,demos,...}, data/{env,problems,runs,vaults}}" + echo " Open it: open ~/armonia/ (or add ~/armonia to your VS Code workspace)" +} + +# ------------------------------------------------------------------- +make_layout() { + echo "--- layout ---" + mkdir -p "${ARMONIA}/repos/packages" "${ARMONIA}/repos/demos" \ + "${ARMONIA}/data/env" "${ARMONIA}/data/problems" \ + "${ARMONIA}/data/runs" "${ARMONIA}/data/vaults" + echo " ~/armonia/{repos/{packages,demos}, data/{env,problems,runs,vaults}} ready" +} + +# ------------------------------------------------------------------- +# ~/.amico/ → ~/armonia/data/, created only when safe. +# If ~/.amico/ already exists as a REAL directory with content, that is +# the migration case — point the user at migrate-to-armonia.sh instead of +# clobbering it. +wire_amico_links() { + echo "--- ~/.amico links ---" + local pairs=("julia:env" "problems:problems" "runs:runs" "vaults:vaults") + for pair in "${pairs[@]}"; do + local name="${pair%%:*}" target="${pair##*:}" + local src="${AMICO}/${name}" dest="${ARMONIA}/data/${target}" + mkdir -p "$AMICO" + if [[ -L "$src" ]]; then + echo " (symlink) ~/.amico/${name}" + elif [[ -d "$src" && -n "$(ls -A "$src" 2>/dev/null)" ]]; then + echo " (real dir, not empty) ~/.amico/${name} — run tools/migrate-to-armonia.sh first" + elif [[ -d "$src" ]]; then + rmdir "$src" && ln -s "$dest" "$src" + echo " linked ~/.amico/${name} → data/${target}" + else + ln -s "$dest" "$src" + echo " linked ~/.amico/${name} → data/${target}" + fi + done +} + +# ------------------------------------------------------------------- +# Vault ecosystem — mirrors extension's ensureVaultEcosystem (idempotent, +# offline-tolerant, never throws). Either order (CLI first or extension first) +# is safe — second run is a no-op. +ensure_vault_ecosystem() { + echo "--- vaults ---" + local vaults_root="${AMICO}/vaults" + # Resolve symlink if present (~/armonia/data/vaults) + if [ -L "$vaults_root" ]; then vaults_root=$(readlink "$vaults_root"); fi + # Fallback if symlink broken or relative + if [[ "$vaults_root" != /* ]]; then vaults_root="${AMICO}/vaults"; fi + # Canonical: ~/armonia/data/vaults if symlink else ~/.amico/vaults + if [ -L "${AMICO}/vaults" ]; then + vaults_root=$(readlink "${AMICO}/vaults") + [[ "$vaults_root" != /* ]] && vaults_root="${AMICO}/vaults" + else + vaults_root="${AMICO}/vaults" + fi + # Ensure data/vaults exists (make_layout already did) + mkdir -p "$vaults_root" 2>/dev/null || true + + # Personal — create if none with kind=personal exists. + local has_personal=0 + for d in "$vaults_root"/*/.amico-vault.toml 2>/dev/null; do + [ -f "$d" ] && grep -q 'kind = "personal"' "$d" 2>/dev/null && has_personal=1 && break + done + if [ $has_personal -eq 0 ]; then + local raw="${USER:-personal}" + local name=$(echo "$raw" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9._-]+/-/g; s/^[-.]+|[-.]+$//g') + [ -z "$name" ] && name="personal" + local dir="$vaults_root/$name" + if [ ! -e "$dir" ]; then + echo " create personal vault: $name" + mkdir -p "$dir" + printf 'kind = "personal"\nname = "%s"\n' "$name" > "$dir/.amico-vault.toml" + git -C "$dir" init -q 2>/dev/null || true + else + echo " (exists) personal vault: $name" + fi + else + echo " (exists) personal vault" + fi + + # Public — shallow clone vault-public if absent, else placeholder if offline. + local pub_dir="$vaults_root/vault-public" + local pub_marker="$pub_dir/.amico-vault.toml" + if [ -f "$pub_marker" ]; then + echo " (exists) public vault: vault-public" + elif [ -d "$pub_dir" ]; then + echo " (exists dir without marker) vault-public — seeding marker" + printf 'kind = "public"\nname = "vault-public"\n' > "$pub_marker" 2>/dev/null || true + else + echo " clone public vault: vault-public" + if git clone --depth 1 --single-branch https://github.com/harmoniqs/vault-public.git "$pub_dir" 2>/dev/null; then + # Ensure marker is public + if ! grep -q 'kind = "public"' "$pub_marker" 2>/dev/null; then + printf 'kind = "public"\nname = "vault-public"\n' > "$pub_marker" 2>/dev/null || true + fi + echo " cloned vault-public" + else + echo " (offline or no git) placeholder for vault-public" + mkdir -p "$pub_dir" + printf 'kind = "public"\nname = "vault-public"\n' > "$pub_marker" 2>/dev/null || true + printf '# vault-public (offline placeholder)\n\nCloned on next online run.\n' > "$pub_dir/README.md" 2>/dev/null || true + fi + fi + + # mounts.toml — absent-only. + local mounts_toml="${AMICO}/mounts.toml" + if [ -f "$mounts_toml" ]; then + echo " (exists) mounts.toml" + else + local personal_id="" + for d in "$vaults_root"/*/.amico-vault.toml 2>/dev/null; do + [ -f "$d" ] && grep -q 'kind = "personal"' "$d" 2>/dev/null && personal_id=$(grep -E '^name = ' "$d" 2>/dev/null | sed -E 's/.*"(.*)".*/\1/' | head -1) && [ -n "$personal_id" ] && break + done + # Fallback to dir basename if marker name parse failed + if [ -z "$personal_id" ]; then + for d in "$vaults_root"/*/; do + [ -f "$d/.amico-vault.toml" ] && grep -q 'kind = "personal"' "$d/.amico-vault.toml" 2>/dev/null && personal_id=$(basename "$d") && break + done + fi + { + if [ -n "$personal_id" ]; then + printf '[[mount]]\nid = "%s"\nkind = "personal"\nwritable = true\n\n' "$personal_id" + fi + if [ -f "$pub_marker" ]; then + printf '[[mount]]\nid = "vault-public"\nkind = "public"\nwritable = false\n\n' + fi + } > "$mounts_toml" 2>/dev/null && echo " wrote mounts.toml" || echo " (skip) mounts.toml not written" + fi +} + +# ------------------------------------------------------------------- +clone_public() { + echo "--- clone (public) ---" + for repo in "${PUBLIC_PACKAGES[@]}"; do + clone_or_update "https://github.com/harmoniqs/${repo}.git" "${ARMONIA}/repos/packages/${repo}" + done + for repo in "${PUBLIC_DEMOS[@]}"; do + clone_or_update "https://github.com/harmoniqs/${repo}.git" "${ARMONIA}/repos/demos/${repo}" + done +} + +# ------------------------------------------------------------------- +clone_private() { + echo "--- clone (private, via gh) ---" + if ! command -v gh >/dev/null 2>&1; then + echo " gh not installed — skipping private tier"; return 0 + fi + if ! gh auth status >/dev/null 2>&1; then + echo " gh not authenticated — skipping private tier (run: gh auth login)"; return 0 + fi + for repo in "${PRIVATE_PACKAGES[@]}"; do + gh_clone_or_update "harmoniqs/${repo}" "${ARMONIA}/repos/packages/${repo}" + done + for repo in "${PRIVATE_APPS[@]}"; do + gh_clone_or_update "harmoniqs/${repo}" "${ARMONIA}/repos/${repo}" + done +} + +# ------------------------------------------------------------------- +clone_or_update() { + local url="$1" dest="$2" + if [[ -d "${dest}/.git" ]]; then + echo " (exists) $(basename "$dest") — pulling" + git -C "$dest" pull --ff-only 2>/dev/null || echo " (pull skipped: not fast-forwardable)" + elif [[ -e "$dest" ]]; then + echo " (exists, not a git repo — left alone) $(basename "$dest")" + else + echo " clone $(basename "$dest")" + git clone "$url" "$dest" + fi +} + +gh_clone_or_update() { + local repo="$1" dest="$2" + if [[ -d "${dest}/.git" ]]; then + echo " (exists) $(basename "$dest") — pulling" + git -C "$dest" pull --ff-only 2>/dev/null || echo " (pull skipped: not fast-forwardable)" + elif [[ -e "$dest" ]]; then + echo " (exists, not a git repo — left alone) $(basename "$dest")" + else + echo " clone $repo" + gh repo clone "$repo" "$dest" + fi +} + +main diff --git a/tools/migrate-to-armonia.sh b/tools/migrate-to-armonia.sh new file mode 100755 index 00000000..029c5636 --- /dev/null +++ b/tools/migrate-to-armonia.sh @@ -0,0 +1,237 @@ +#!/usr/bin/env bash +set -euo pipefail + +# migrate-to-armonia.sh +# Idempotent migration: consolidate repos + Amico data into ~/armonia/. +# Safe to run multiple times — skips what is already in place, and converges +# the repos/ buckets on re-run (flat .jl packages → packages/, demo dirs → demos/). +# +# Canonical layout: +# ~/armonia/repos/packages/ Julia libraries (Piccolo.jl, …) +# ~/armonia/repos/demos/ demo galleries (atoms-demo, …) +# ~/armonia/repos/ apps, forks, research projects (amicode, passaggio, …) +# ~/armonia/data/{env,problems,runs,vaults} + +ARMONIA="${HOME}/armonia" +AMICO="${HOME}/.amico" + +# ---- discover source repos ---- +# Directories that might hold git checkouts to move. +REPO_SOURCES=( + "${HOME}/_dev/harmoniqs" + "${HOME}/harmoniqs" + "${HOME}/AmicodeProjects" + "${HOME}/_dev" +) + +# ---- discover data dirs to migrate ---- +# Each entry: "amico_dir armonia_target" +DATA_DIRS=( + "julia env" + "problems problems" + "runs runs" + "vaults vaults" +) + +# Known demo repo names → routed to repos/demos/. A source dir literally named +# "demos" is moved as-is (its contents are already grouped). +is_demo() { [[ "$1" == *demo* || "$1" == "atoms" || "$1" == "fluxonium" || "$1" == "ions" ]]; } + +# Julia packages route to repos/packages/ by the .jl suffix convention. +is_package() { [[ "$1" == *.jl || "$1" == *.jl-* ]]; } + +# =================================================================== +main() { + echo "==> migrate-to-armonia (idempotent)" + echo + + mkdir -p "${ARMONIA}/repos/packages" "${ARMONIA}/repos/demos" \ + "${ARMONIA}/data/env" "${ARMONIA}/data/problems" \ + "${ARMONIA}/data/runs" "${ARMONIA}/data/vaults" + + converge_buckets + migrate_repos + migrate_data + cleanup_empty_parents + + echo + echo "==> Done." + echo " Run: open ~/armonia/" +} + +# ------------------------------------------------------------------- +# Re-run convergence: repos/ that already migrated flat get bucketed. +converge_buckets() { + local moved=0 + for child in "${ARMONIA}/repos"/*/; do + [[ -d "$child" ]] || continue + local name; name=$(basename "$child") + case "$name" in packages|demos) continue ;; esac + if is_package "$name"; then + echo " bucket: repos/$name → repos/packages/$name" + mv "$child" "${ARMONIA}/repos/packages/$name" + moved=1 + elif is_demo "$name"; then + echo " bucket: repos/$name → repos/demos/$name" + mv "$child" "${ARMONIA}/repos/demos/$name" + moved=1 + fi + done + # A shared Julia dev env (Project.toml/Manifest.toml) stranded at repos/ + # belongs with the packages it references. + for f in Project.toml Manifest.toml; do + if [[ -f "${ARMONIA}/repos/$f" && ! -f "${ARMONIA}/repos/packages/$f" ]]; then + echo " bucket: repos/$f → repos/packages/$f" + mv "${ARMONIA}/repos/$f" "${ARMONIA}/repos/packages/$f" + fi + done + [[ $moved -eq 1 ]] && echo + return 0 +} + +# ------------------------------------------------------------------- +migrate_repos() { + echo "--- repos ---" + + for src_dir in "${REPO_SOURCES[@]}"; do + if [[ ! -d "$src_dir" ]]; then + echo " (skip) not found: $src_dir" + continue + fi + local src_children + src_children=$(find "$src_dir" -mindepth 1 -maxdepth 1 ! -name 'node_modules' 2>/dev/null || true) + if [[ -z "$src_children" ]]; then + echo " (skip) empty: $src_dir" + continue + fi + echo " source: $src_dir" + while IFS= read -r child; do + [[ -z "$child" ]] && continue + local name + name=$(basename "$child") + + # Shell scripts and loose files stay; only directories move. A root-level + # Project.toml/Manifest.toml accompanies the packages. + if [[ ! -d "$child" ]]; then + case "$name" in + Project.toml|Manifest.toml) + if [[ ! -f "${ARMONIA}/repos/packages/$name" ]]; then + echo " mv $name → packages/" + mv "$child" "${ARMONIA}/repos/packages/$name" + fi + ;; + *) echo " (skip file) $name" ;; + esac + continue + fi + + # Route to the right bucket. + local bucket="${ARMONIA}/repos" + if is_package "$name"; then + bucket="${ARMONIA}/repos/packages" + elif is_demo "$name" || [[ "$name" == "demos" ]]; then + bucket="${ARMONIA}/repos/demos" + fi + # A "demos" source dir lands AS repos/demos (contents grouped inside); + # merging into it rather than nesting demos/demos. + local dest + if [[ "$name" == "demos" ]]; then + dest="$bucket" + else + dest="${bucket}/${name}" + fi + + if [[ -e "$dest" && "$name" != "demos" ]]; then + echo " (exists) $name" + continue + fi + if [[ "$name" == "demos" && -d "$dest" ]]; then + # merge contents into the existing demos bucket + local demo_children + demo_children=$(find "$child" -mindepth 1 -maxdepth 1 2>/dev/null || true) + while IFS= read -r d; do + [[ -z "$d" ]] && continue + local dname; dname=$(basename "$d") + if [[ -e "${dest}/${dname}" ]]; then + echo " (exists) demos/$dname" + else + echo " mv demos/$dname" + mv "$d" "${dest}/${dname}" + fi + done <<< "$demo_children" + continue + fi + + echo " mv $name → ${bucket#"$ARMONIA"/}" + mv "$child" "$dest" + done <<< "$src_children" + done +} + +# ------------------------------------------------------------------- +migrate_data() { + echo "--- data ---" + + for entry in "${DATA_DIRS[@]}"; do + read -r amico_name armonia_name <<< "$entry" + local src="${AMICO}/${amico_name}" + local dest="${ARMONIA}/data/${armonia_name}" + + # already a symlink → done + if [[ -L "$src" ]]; then + echo " (symlink) ~/.amico/${amico_name}" + continue + fi + + # dest already populated → assume already migrated + if [[ -d "$dest" && -n "$(ls -A "$dest" 2>/dev/null)" ]]; then + # source still a real dir → just symlink it + if [[ -d "$src" && ! -L "$src" ]]; then + echo " (dest exists) ~/.amico/${amico_name} → symlink" + rm -rf "$src" + ln -s "$dest" "$src" + else + echo " (ok) ~/.amico/${amico_name}" + fi + continue + fi + + # source is a real dir, dest does not exist → move + symlink + if [[ -d "$src" && ! -L "$src" ]]; then + echo " mv ~/.amico/${amico_name} → data/${armonia_name}" + mv "$src" "$dest" + ln -s "$dest" "$src" + else + echo " (skip) ~/.amico/${amico_name} does not exist" + fi + done +} + +# ------------------------------------------------------------------- +cleanup_empty_parents() { + echo "--- cleanup ---" + for src_dir in "${REPO_SOURCES[@]}"; do + # never remove HOME or root-level dirs + case "$src_dir" in + "$HOME"|"$HOME/Desktop"|"$HOME/Documents"|"$HOME/Downloads") continue ;; + esac + if [[ -d "$src_dir" ]]; then + local remaining + remaining=$(find "$src_dir" -mindepth 1 -maxdepth 1 2>/dev/null || true) + if [[ -z "$remaining" ]]; then + echo " rmdir $src_dir" + rmdir "$src_dir" + # try to remove the parent if it is now empty + local parent + parent=$(dirname "$src_dir") + local parent_remaining + parent_remaining=$(find "$parent" -mindepth 1 -maxdepth 1 2>/dev/null || true) + if [[ -z "$parent_remaining" && "$parent" != "$HOME" ]]; then + rmdir "$parent" 2>/dev/null || true + fi + fi + fi + done +} + +main From b6cebc8e1ed701bc80da1e315d976b7775969843 Mon Sep 17 00:00:00 2001 From: aaron Date: Sun, 9 Aug 2026 10:17:42 -0400 Subject: [PATCH 2/3] docs: reframe READMEs around open autonomous research studio - GitHub README: Open autonomous research, starting with quantum control hero + studio lens (workspace vs managed state) + 5 pillars (autonomous research, quantum intelligence, vaults, fleet, system management -> hardware) + How it scales solo->team->fleet + Skills are the product + Physical intelligence generalization - Marketplace README: short discovery version, same pillars, no mermaid - package.json: description + keywords add autonomous research, open science, fleet management, knowledge graph, physical intelligence Co-authored-by: internal-model --- README.md | 117 ++++++++++++++++++++++---------- packages/extension/README.md | 38 ++++++----- packages/extension/package.json | 9 ++- 3 files changed, 109 insertions(+), 55 deletions(-) diff --git a/README.md b/README.md index afa85092..5f4e69ea 100644 --- a/README.md +++ b/README.md @@ -7,10 +7,9 @@ # Amicode -### Quantum optimal control, driven by conversation. +### Open autonomous research, starting with quantum control. -Describe the gate you want in plain language. Amicode designs the pulse, runs the -solve, and shows you the result — without leaving your editor. +Your vaults, your fleet, your devices, your pulses — composed by conversation. A VS Code extension · built on [Piccolo.jl](https://github.com/harmoniqs/Piccolo.jl) · chat harness vendored from [opencode](https://github.com/sst/opencode) @@ -18,46 +17,92 @@ solve, and shows you the result — without leaving your editor. --- -Amicode turns a natural-language description of a control problem into an -LLM-authored Julia optimization, runs it, and streams the result back into native -editor panels. The physics, the solver idioms, and your lab's accumulated -knowledge all ride along as context — so the script it writes is correct by -construction, not by luck. +Amicode is an **open autonomous research studio** that lives in your editor. -**This repo is the whole product**: the VS Code extension (`packages/extension`), -the `amico` / `amico-run` CLI (`packages/amico-run`), and the public skill -library (`packages/extension/skills/`) — skills are product content, versioned -with the extension and bundled into every vsix. Additional skills load from your -own Armonia vault mounts (they are never shipped), and package skills ride their -Julia repos behind entitlements. (The old `amico-plugin` repo is retired; the -skill library lives here now.) +Describe what you want in plain language — a gate, a state preparation, a calibration sweep — and Amicode designs the pulse, runs the solve, and shows you the result. Every run is captured, every pulse versioned for warm-start, and every session distilled into durable knowledge. The loop gets smarter as you use it. -## What it does +We start with **quantum control** because it is the hardest physical system to prove the loop on. If the studio works here — arbitrary Hamiltonians, hard constraints, hardware in the loop — it generalizes to any physical system you can model. Bring your own Hamiltonian; the loop is the same. That's **physical intelligence**: not one device or platform, but a composable way to do experimental science. -**Conversational solves.** Ask for a gate or a state preparation; Amicode writes a -self-contained Piccolo script, runs the Ipopt solve, and captures the result. No -boilerplate, no parameter-guessing. +**This repo is the whole product:** the VS Code extension (`packages/extension`), the `amico` / `amico-run` CLI (`packages/amico-run`), and the public skill library (`packages/extension/skills/`) — skills are product content, versioned with the extension and bundled into every vsix. Additional skills load from your own vault mounts (never shipped), and package skills ride their Julia repos behind entitlements. (The old `amico-plugin` repo is retired; the skill library lives here now.) -**Physics that ships with the tool.** Platform references for neutral-atom Rydberg, -transmon, fluxonium, trapped-ion, and bosonic systems load on demand — the -Hamiltonians, drive conventions, and construction patterns are inlined into each -script so it stands on its own. +## The studio -**Your knowledge, mounted.** Amicode reads your **Armonia** — the stack of vaults -you mount (personal, team, public). Notes, specs, experiment history, and your -pulse catalog become first-class context the assistant plans against. +**The workspace is what you edit. `~/.amico` is what the system manages. Amicode is the lens.** -**A live run inspector.** Watch a solve converge in real time: overlaid pulse -plots, fidelity and constraint-violation traces, per-run metrics. Every run is -captured and revisitable. +Most of what used to clutter the workspace is not working material but state — visible only because a raw file tree was the only lens. Amicode replaces the tree with native surfaces: -**A pulse catalog.** A versioned, warm-startable library of your best pulses — -retrieve the incumbent for a `(platform, gate)`, warm-start from it, and promote a -new best when you beat it. +| What the system manages | Where you see it | +|---|---| +| Vaults (`~/.amico/vaults/`) — notes, specs, experiment history | Vault tree + Armonia view | +| Runs (`~/.amico/runs/`) — per-solve capture | Run Inspector | +| Pulse catalog — versioned warm-start memory | Catalog view | +| Devices + calibration (`~/.amico/amicode/devices/`) | Device Inspector | +| Fleet + sessions — sync, locks, traces | Healthcheck, status bar, Learn | -**Straight to hardware.** Drive real RFSoC devices through the QICK backend, or run -the *entire* closed loop against a pure-Julia mock with zero hardware for -development and CI. +The extension renders managed state semantically instead of exposing it as folders. The underlying files stay on disk and stay yours. + +## What you get + +### Open autonomous research + +Amico, your research copilot, turns plain language into a self-contained Julia optimization, runs it, and streams the result back into native panels. Physics, solver idioms, and your lab's accumulated knowledge ride along as context — so the script it writes is correct by construction, not by luck. A short guided exchange settles anything it needs (levels, drive bounds, constraints) before committing to code. Every run is captured to disk and revisitable; when you beat a previous best, the catalog promotes and the next solve warm-starts from your last good answer. + +### Open quantum intelligence + +The physics ships with the tool. Platform references for **neutral-atom Rydberg**, **transmon**, **fluxonium**, **trapped-ion**, and **bosonic** systems load on demand — Hamiltonians, drive conventions, and construction patterns are inlined into each script so it stands alone. You never hand the assistant a Hamiltonian; naming the platform is enough. Searched papers, ingested notes, and your own experiment history plan alongside the physics. + +### Vaults — your knowledge, mounted + +Amicode reads your **vaults** — the stack you mount under `~/.amico/vaults/` (personal, team, project). Notes, specs, experiment history, and your pulse catalog become first-class context the assistant plans against. Armonia is one vault in that set — your personal research memory. Vaults layer by scope and by `visibility` (`local` → `team` → `public`); dream-promotion carries provenance so every insight traces to its source. + +### Fleet management — one logical studio across machines + +Your machines form one logical studio. Vault mounts sync via `armonia-sync-once` (launchd, every 15 min); the chat database stays canonical via an SSH mesh; WIP follows you between hosts with `leave`/`arrive`. No second writer ever touches the same SQLite file, no live `.git` is file-synced — the invariants are enforced, not assumed. Check it with `/fleet`; the skill is the playbook. Solo still works fully offline — the fleet simply means you never have to choose which machine holds the truth. + +### Open system management — from pulse to device + +The extension manages the system around the solve, not just the solve itself. Lab profiles (`lab.toml`), device calibration graphs, and run capture are all rendered natively instead of buried in config files. You see system state where you act on it. + +### Straight to hardware + +Drive real RFSoC devices through the QICK backend, or run the *entire* closed loop against a pure-Julia mock with zero hardware for development and CI. + +## How it scales + +``` +solo ──────────► team ──────────► fleet + +personal vault + team / project + SSH mesh, canonical DB, +local runs vault mounts WIP-sync, device locks +catalog shared catalog shared catalog, warm-starts + visibility-gated every run feeds the next + dream-promotion +``` + +**Solo** — personal vault, local runs, versioned catalog. Works fully offline. + +**Team** — mount team and project vaults alongside your personal one. Vaults layer with `local | team | public` visibility; promotion to the team vault is a PR with provenance, not a copy-paste. Two researchers never clobber: one-file-per-note, a computed catalog index, per-session result files, and device locks make concurrent work safe by construction. + +**Fleet** — the mesh above. One canonical chat database, vault sync on a timer, WIP-sync across hosts. The same invariants that make team-safe make fleet-safe. Every run — whoever ran it, wherever — feeds the same knowledge base, so new work starts from the best prior answer. + +## Skills are the product + +Skills are not configuration — they are the capability surface. The **37 public skills** in `packages/extension/skills/` ship in the vsix, versioned with the product. Additional skills load from your own vault mounts and from co-located Julia packages behind entitlements. + +| Surface | What it covers | +|---|---| +| Physics | `transmon`, `fluxonium`, `atoms` (Rydberg), `bosonic`, `ions` — Hamiltonians, drives, construction patterns | +| Lab + catalog + vault | `amico-lab`, `amico-catalog`, `amico-vault`, `amico-strategy`, `amico-schema-check` | +| Analysis + synthesis | `analyze`, `structural-analysis`, `hypothesis-review`, `dream-reflect` | +| System | `fleet`, `setup`, `solve`, `simulate`, `warm-start`, `constraints`, `objectives` | +| Delivery | `demo`, `pasqal`, `plot`, `compose`, `multistart` | +| Engineering | `debugging`, `tdd`, `verification`, `brainstorming`, `deliberate`, `grill-me`, `report-a-bug` | + +The extension stages the union of the public bundle and your vault mounts at startup; mount presence is the eligibility proof. + +## Physical intelligence + +Quantum control is the first domain, not the ceiling. The studio's loop — *describe → author a self-contained optimization → run → capture → distill → warm-start the next run* — does not care what the Hamiltonian is, only that you can write it down. If you can model the system, the same vault, the same catalog, the same fleet carries the work. That's why we lead with the hardest physical system: if the loop is trustworthy here, it composes outward. ## Hardware — QICK / RFSoC @@ -103,6 +148,8 @@ Open the Amicode panel and paste: > leakage to |2⟩). Then run the optimized pulse through the IntonatoQICK mock > backend, read out the populations, and plot both the pulse and the readout. +This one prompt exercises the whole studio: it names a platform, a target, a constraint, and a hardware step — chat to physics skill to solve to mock readout to plot — without you writing the Piccolo API, the Hamiltonian, or the QICK verbs. Swap `transmon` for your own system and the loop is the same. + ## Open core The extension and its platform skills are open. **Entitled builds** add diff --git a/packages/extension/README.md b/packages/extension/README.md index af6e1bc5..f7797ad3 100644 --- a/packages/extension/README.md +++ b/packages/extension/README.md @@ -1,34 +1,36 @@ # Amicode -### Quantum optimal control, driven by conversation. +### Open autonomous research, starting with quantum control. -Describe the gate you want in plain language. Amicode designs the pulse, runs the -solve, and shows you the result — without leaving your editor. +Your vaults, your fleet, your devices, your pulses — composed by conversation. -Amicode turns a natural-language description of a control problem into an -LLM-authored [Piccolo.jl](https://github.com/harmoniqs/Piccolo.jl) (Julia) -optimization, runs it, and streams the result back into native VS Code panels. -The physics, the solver idioms, and your lab's accumulated knowledge ride along -as context — so the script it writes is correct by construction, not by luck. +Amicode is an **open autonomous research studio** in VS Code. Describe what you want — a gate, a state preparation, a calibration sweep — and it designs the pulse, runs the solve, and shows you the result. Every run is captured, every pulse versioned for warm-start, and every session distilled into durable knowledge. We start with quantum control — the hardest physical system to prove the loop on — and generalize to any physical system you can model. ## What you get -- **Chat-driven pulse design** — an in-editor copilot (Amico) that authors and - runs the Julia optimization for the gate or state you describe. -- **Live Run Inspector** — watch the solve converge in a native panel: objective, - constraints, and the resulting pulse. -- **Managed Julia toolchain** — first run offers to install Julia (via juliaup), - pin the right version, and provision the Piccolo environment for you. -- **A personal vault** — your systems, pulses, and problems are remembered across - sessions, stored locally under `~/.amico`. +- **Open autonomous research** — Amico, your copilot, authors a self-contained [Piccolo.jl](https://github.com/harmoniqs/Piccolo.jl) optimization from plain language, runs it, and streams the result to native panels. A short guided exchange settles parameters before code is committed. You can stop a solve in flight with **Amicode: Stop current solve**. +- **Open system management, not just a chat panel** — the workspace is what you edit; `~/.amico` is what the system manages (vaults, runs, catalog, devices). Amicode is the lens: Vault tree, Run Inspector, Catalog, and Device Inspector render managed state semantically instead of as file trees. +- **Vaults — your knowledge, mounted** — personal + team + project vaults stacked under `~/.amico/vaults/` (your notes, specs, experiment history, pulse catalog) become first-class context the assistant plans against. Works fully offline; scales to a team via vault mounts with `local | team | public` visibility and PR-based promotion. +- **Fleet management — one logical studio across machines** — vault sync, canonical chat DB, and WIP that follows you between hosts over an SSH mesh. Solo, team, and fleet share the same invariants: one-file-per-note, a computed catalog index, per-session captures, and device locks. +- **Live execution + versioned memory** — the **Run Inspector** overlays pulse plots and traces fidelity and constraint violation as they fall; the **pulse catalog** keeps a warm-startable, versioned library of your best pulses and promotes when you beat the incumbent. +- **Straight to hardware** — drive real RFSoC boards via [IntonatoQICK.jl](https://github.com/harmoniqs/IntonatoQICK.jl) over a coarse three-verb boundary (`upload_pulse!` / `trigger!` / `readout`), or run the *entire* closed loop against a pure-Julia mock with zero hardware for dev and CI. Same script, mock or metal. +- **Physics that ships with the tool** — platform references for **Rydberg**, **transmon**, **fluxonium**, **trapped-ion**, and **bosonic** systems inline the Hamiltonians and drive conventions into each script so it stands alone. +- **Skills are the product** — 37 public skills ship in the vsix, versioned with the product; your own vault mounts and Julia-package skills layer on top behind entitlements. + +Full studio tour, hardware docs, and the skill index → [github.com/harmoniqs/amicode](https://github.com/harmoniqs/amicode) + +## Try it + +Open the Amicode panel and paste: + +> Design a minimum-time single-qubit X gate for a transmon (3 levels, penalize leakage to |2⟩). Then run the optimized pulse through the IntonatoQICK mock backend, read out the populations, and plot both the pulse and the readout. ## Requirements - An LLM provider configured for the chat engine. - Julia — Amicode manages the install on first run; nothing to set up by hand. -Run **Amicode: Healthcheck** from the Command Palette any time to verify your -setup (Julia environment, chat server, and provider). +Run **Amicode: Healthcheck** from the Command Palette any time to verify your setup (Julia environment, chat server, and provider). --- diff --git a/packages/extension/package.json b/packages/extension/package.json index 237fc52a..e351e355 100644 --- a/packages/extension/package.json +++ b/packages/extension/package.json @@ -1,7 +1,7 @@ { "name": "amicode", "displayName": "Amicode", - "description": "Amico research IDE — opencode-backed chat + native VS Code surfaces for vault, catalog, and live solve inspection.", + "description": "Open autonomous research in VS Code — vaults, fleet, and live solves for quantum control and physical intelligence.", "version": "0.2.1", "publisher": "harmoniqs", "license": "Apache-2.0", @@ -23,9 +23,14 @@ "keywords": [ "quantum", "quantum computing", - "pulse design", "optimal control", "Piccolo", + "autonomous research", + "open science", + "fleet management", + "knowledge graph", + "physical intelligence", + "pulse design", "opencode", "AI agent" ], From 661778a16ec5b8a427f5fe0fe93138b4cd0594fd Mon Sep 17 00:00:00 2001 From: aaron Date: Sun, 9 Aug 2026 10:34:48 -0400 Subject: [PATCH 3/3] docs: use face logo in README hero MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The tab SVGs (amico-tab-*.svg) are the wordmark without the face — just the outer frame. The README hero should show the actual Amico face (amico.svg). Add explicit light/dark face variants (amico-face-*.svg) derived from amico.svg with hardcoded fills (#424242 / #CCCCCC) so GitHub dark/light rendering has correct contrast, and point the README picture at them. --- README.md | 4 ++-- packages/extension/media/amico-face-dark.svg | 22 +++++++++++++++++++ packages/extension/media/amico-face-light.svg | 22 +++++++++++++++++++ 3 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 packages/extension/media/amico-face-dark.svg create mode 100644 packages/extension/media/amico-face-light.svg diff --git a/README.md b/README.md index 5f4e69ea..eeb4c522 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@
- - Amicode + + Amicode # Amicode diff --git a/packages/extension/media/amico-face-dark.svg b/packages/extension/media/amico-face-dark.svg new file mode 100644 index 00000000..695e90dd --- /dev/null +++ b/packages/extension/media/amico-face-dark.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/extension/media/amico-face-light.svg b/packages/extension/media/amico-face-light.svg new file mode 100644 index 00000000..91f74b57 --- /dev/null +++ b/packages/extension/media/amico-face-light.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file