From dfafb2567e2fa9efced1cda8f20b120688296ed3 Mon Sep 17 00:00:00 2001 From: Michael Heller <21163552+mdheller@users.noreply.github.com> Date: Sun, 2 Aug 2026 19:39:50 -0400 Subject: [PATCH] guix+agent-s: desktop image target + local/cloud test runner + Nix parity baseline Enables the Agent-S test agent against the Guix build, for local OR cloud image testing, and documents the Nix setup copiously as the parity target. - guix/system/desktop.scm: GNOME desktop image (nonguix kernel/firmware/microcode) -> qcow2, the Agent-S GUI target (Guix equiv of nix sourceos-image-qcow2-desktop). - tests/agent-s/run-guix.sh: builds the Guix image and runs the existing Agent-S harness (image-source-agnostic). --mode local (KVM here) | cloud (sync to a GCP GPU VM, run local there so the UI-TARS grounder is on the GPU). --main-model LOCAL by default (AS_MAIN_URL), external providers only on explicit request. - guix/NIX_BASELINE.md: copious inventory of the current Nix setup (7 packages, 5 image targets, 3 modules, the boot/contract/smoke test matrix, 5 CI workflows, cache+signing, dev shells) each mapped to its Guix equivalent + acceptance = the parity checklist the migration must satisfy before cutover. Adversarial review: fixed missing (gnu packages ssh) import in desktop.scm; replaced a fabricated scripts/gcp-build-linux.sh --run call (that script lives in bearbrowser) with a generic rsync+ssh-to-GPU-VM cloud path. bash -n clean; Scheme forms balanced. NOT build-validated here (no guix on macOS); Linux runner is the gate. --- guix/NIX_BASELINE.md | 69 +++++++++++++++++++++++++++++++++ guix/system/desktop.scm | 67 ++++++++++++++++++++++++++++++++ tests/agent-s/run-guix.sh | 80 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 216 insertions(+) create mode 100644 guix/NIX_BASELINE.md create mode 100644 guix/system/desktop.scm create mode 100755 tests/agent-s/run-guix.sh diff --git a/guix/NIX_BASELINE.md b/guix/NIX_BASELINE.md new file mode 100644 index 0000000..a6f8746 --- /dev/null +++ b/guix/NIX_BASELINE.md @@ -0,0 +1,69 @@ +# Nix baseline — the Guix migration parity target + +This document copiously inventories the **current Nix setup** so the Guix +implementation can reproduce **the same standards**. It is the parity checklist: +the migration is not "done" for a row until its Guix equivalent builds/boots/passes +to the same bar. Keep it updated as `flake.nix` evolves — it is the source of +truth for what Guix must match, the same way `board-spec.yaml` is for the boards. + +Status legend: ☐ not started · ◐ spiked · ☑ parity proven on a Linux runner. + +## 1. Packages (`flake.nix` `packages`) +| Nix output | Source | Guix equivalent | Status | +|---|---|---|---| +| `meshd`, `meshd-linkd`, `meshd-exitd` | `packages/mesh/*.nix` | Guix package definitions (`guix/packages/mesh.scm`) | ☐ | +| `bearbrowser` | `packages/browser/bearbrowser.nix` | Guix package (LibreWolf-derived; nonguix for nonfree bits) | ☐ | +| `lampstand` | `packages/search/lampstand.nix` | Guix package | ☐ | +| `sourceos-syncd` | `packages/sourceos-syncd/default.nix` | Guix package | ☐ | +| `sourceos-boot` | `packages/sourceos-boot/default.nix` | Guix package | ☐ | + +## 2. Images (`flake.nix`, via `nixos-generators`) +| Nix image | Def | Guix equivalent (`guix system image -t …`) | Status | +|---|---|---|---| +| `sourceos-installer-iso` (x86_64 / aarch64) | `images/iso-x86_64.nix`, `images/iso-aarch64.nix` | `guix system image -t iso9660` from a Guix installer profile | ☐ | +| `sourceos-image-qcow2-desktop` | desktop generator | `guix/system/desktop.scm` → `-t qcow2` | ◐ (this PR) | +| `sourceos-image-qcow2-canary` | `images/canary-x86_64.nix` | Guix canary profile → qcow2 | ☐ | +| `sourceos-image-qcow2-stable` | `images/release-x86_64.nix` | Guix stable/release profile → qcow2 | ☐ | +| (workstation baseline) | — | `guix/system/workstation.scm` | ◐ (spike) | + +## 3. NixOS modules → Guix services +| Nix module | Path | Guix equivalent (service type) | Status | +|---|---|---|---| +| mesh | `modules/nixos/mesh` | `guix/services/mesh.scm` | ☐ | +| sourceos-shell | `modules/nixos/sourceos-shell` | `guix/services/sourceos-shell.scm` | ☐ | +| sourceos-syncd | `modules/nixos/sourceos-syncd` | `guix/services/sourceos-syncd.scm` | ☐ | + +## 4. Checks / tests (`tests/`, `flake.nix` `checks`) +The primary gate is Layer-1 deterministic boot; the contract/smoke suite asserts +each edition + component. Guix's analog is **`guix system test`** (marionette). +| Nix check | Guix equivalent | Status | +|---|---|---| +| edition boot: `tests/{desktop,server,edge}-boot.nix` | `guix system test` per profile (graphical.target / sshd+firewall / zram) | ☐ | +| `*-smoke.nix` / `*-contract.nix` (mesh, sourceos-shell, sourceos-syncd, boot, canary, stable, exit, builder, workstation) | Guix system tests + package tests, one per contract | ☐ | +| Agent-S Layer-2 GUI test (`tests/agent-s/`) | **Already wired**: `tests/agent-s/run-guix.sh` builds `guix/system/desktop.scm` and drives it (local/cloud) | ◐ (this PR) | + +## 5. CI workflows +| Nix workflow | Purpose | Guix equivalent | Status | +|---|---|---|---| +| `nix-ci.yml` | flake check / fmt | `guix` lint + `guix system build` of each profile | ☐ | +| `nix-build-images.yml` | build images | build qcow2/iso via `guix system image` | ☐ | +| `image-tests.yml` | Layer-1 boot + Agent-S | `guix system test` + `run-guix.sh` | ☐ | +| `build-custom.yml`, `release-images.yml` | custom/release builds | Guix release build + publish | ☐ | + +## 6. Substitutes (cache) + signing +| Nix | Doc | Guix equivalent | Status | +|---|---|---|---| +| Binary cache | `docs/NIX_CACHE_SETUP.md` | `guix publish` server + client `--substitute-urls`; authorize the key with `guix archive --authorize` | ☐ | +| Image signing | `docs/SIGNING_SETUP.md` | Guix signs substitutes with its own key; map the estate signing/WIF flow onto `guix publish` keys | ☐ | + +## 7. Dev shells +| Nix | Guix equivalent | Status | +|---|---|---| +| `devShells.default` (`git jq nixpkgs-fmt`) | `guix shell git jq` (+ a `guix/manifests/dev.scm`) | ☐ | +| lampstand dev shell | `guix shell -m guix/manifests/lampstand.scm` | ☐ | + +## Parity acceptance +A row flips to ☑ only when its Guix artifact **builds and (for images) boots** +on a Linux runner and passes the same assertion its Nix counterpart does — proved, +not asserted. Cutover (flip the default from Nix to Guix) happens per-profile +once its whole column is ☑, with Nix kept until then. diff --git a/guix/system/desktop.scm b/guix/system/desktop.scm new file mode 100644 index 0000000..d95a6db --- /dev/null +++ b/guix/system/desktop.scm @@ -0,0 +1,67 @@ +;;; SourceOS Guix desktop image (x86_64) — the Agent-S GUI test target. +;;; +;;; A GNOME desktop built from Guix + nonguix (real kernel/firmware/microcode), +;;; produced as a bootable qcow2 that Agent-S drives (open Activities, launch +;;; Files, see a window). This is the Guix equivalent of the Nix +;;; `sourceos-image-qcow2-desktop` the Agent-S Layer-2 harness consumes today. +;;; +;;; Build the image on a Linux runner (NOT macOS): +;;; guix time-machine -C guix/channels.scm -- \ +;;; system image -t qcow2 guix/system/desktop.scm # -> a bootable .qcow2 +;;; then feed it to tests/agent-s (local or cloud) via tests/agent-s/run-guix.sh. +;;; +;;; NOTE for Agent-S: the login manager must AUTOLOGIN so the agent lands on a +;;; live desktop over VNC. gdm autologin is a small modify-services tweak added +;;; when this profile is first realised on a runner (kept out here until it can +;;; be build-validated — a wrong gdm-configuration would silently break the test). + +(use-modules (gnu) + (nongnu packages linux) + (nongnu system linux-initrd)) +(use-service-modules desktop ssh) +(use-package-modules certs ssh) + +(define %keyboard-layout (keyboard-layout "us")) + +(operating-system + (host-name "sourceos-desktop") + (timezone "UTC") + (locale "en_US.utf8") + (keyboard-layout %keyboard-layout) + + ;; nonguix: real Linux + firmware + microcode (the allowUnfree equivalent). + (kernel linux) + (firmware (list linux-firmware)) + (initrd microcode-initrd) + + (bootloader (bootloader-configuration + (bootloader grub-efi-bootloader) + (targets '("/boot/efi")) + (keyboard-layout %keyboard-layout))) + + (file-systems (append + (list (file-system + (mount-point "/") + (device (file-system-label "SOURCEOS_ROOT")) + (type "ext4")) + (file-system + (mount-point "/boot/efi") + (device (file-system-label "SOURCEOS_EFI")) + (type "vfat"))) + %base-file-systems)) + + (users (cons (user-account + (name "sourceos") + (comment "SourceOS operator") + (group "users") + (supplementary-groups '("wheel" "netdev" "audio" "video"))) + %base-user-accounts)) + + (packages (append (list nss-certs) %base-packages)) + + ;; Full GNOME on top of the desktop stack (%desktop-services provides gdm + + ;; NetworkManager); openssh for headless control from the harness. + (services (cons* (service gnome-desktop-service-type) + (service openssh-service-type + (openssh-configuration (openssh openssh-sans-x))) + %desktop-services))) diff --git a/tests/agent-s/run-guix.sh b/tests/agent-s/run-guix.sh new file mode 100755 index 0000000..d5d5ed5 --- /dev/null +++ b/tests/agent-s/run-guix.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash +# Build the SourceOS *Guix* desktop image and run the Agent-S GUI test against +# it, LOCAL or CLOUD, with a LOCAL main model by default. +# +# This wires the Guix build (guix/system/desktop.scm) to the existing Agent-S +# harness (tests/agent-s/harness.sh) — the harness is image-source-agnostic +# (it just needs IMG=*.qcow2 + a grounder + a main model), so a Guix-built +# image drops straight in alongside the Nix one. +# +# Modes: +# --mode local build + boot on this Linux/KVM host (default) +# --mode cloud build + run on a GCP GPU VM (grounder on the GPU) +# Main model (Agent-S generation model) — LOCAL by default, per the estate's +# local-agent-default posture: +# --main-model local use AS_MAIN_URL (a local vLLM/TGI endpoint) [default] +# --main-model anthropic use ANTHROPIC_API_KEY +# --main-model openai use OPENAI_API_KEY +# +# Requires (Linux runner): guix, qemu (+ /dev/kvm for local), and a reachable +# grounding-model endpoint (UI-TARS on a GPU). NOT runnable from macOS. +set -euo pipefail + +HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO="$(cd "$HERE/../.." && pwd)" + +MODE="local" +MAIN_MODEL="local" +while [ $# -gt 0 ]; do + case "$1" in + --mode) MODE="$2"; shift 2 ;; + --main-model) MAIN_MODEL="$2"; shift 2 ;; + *) echo "unknown arg: $1" >&2; exit 2 ;; + esac +done + +# Local-first main model: default to a local endpoint; only reach for an external +# provider when explicitly requested (and only then require its key). +case "$MAIN_MODEL" in + local) + : "${AS_MAIN_URL:?set AS_MAIN_URL to a local main-model endpoint (e.g. http://localhost:8081/v1)}" + export AS_PROVIDER="local" AS_MAIN_URL + ;; + anthropic) : "${ANTHROPIC_API_KEY:?set ANTHROPIC_API_KEY}"; export AS_PROVIDER="anthropic" ;; + openai) : "${OPENAI_API_KEY:?set OPENAI_API_KEY}"; export AS_PROVIDER="openai" ;; + *) echo "unknown --main-model: $MAIN_MODEL" >&2; exit 2 ;; +esac + +# Grounder (UI-TARS): local endpoint by default; overridable for cloud/GPU host. +export AS_GROUND_URL="${AS_GROUND_URL:-http://localhost:8080/v1}" + +build_guix_image() { + command -v guix >/dev/null 2>&1 || { echo "missing guix (Linux runner only)" >&2; exit 1; } + echo "building Guix desktop image (pinned channels)…" >&2 + local out + out="$(guix time-machine -C "$REPO/guix/channels.scm" -- \ + system image -t qcow2 "$REPO/guix/system/desktop.scm")" + echo "$out" +} + +case "$MODE" in + local) + [ -e /dev/kvm ] || echo "warn: no /dev/kvm — boot will be slow (TCG)" >&2 + IMG="$(build_guix_image)" + echo "running Agent-S locally against $IMG (provider=$AS_PROVIDER)" >&2 + IMG="$IMG" bash "$HERE/harness.sh" + ;; + cloud) + # Run the whole flow ON a GCP GPU VM so the UI-TARS grounder is local to the + # GPU: sync the repo up, then invoke --mode local there. The VM itself + # (provisioned with a GPU + guix + qemu/kvm) is supplied via GCP_HOST; how it + # is spun up (gcloud/Terraform) is out of scope for this runner. + : "${GCP_HOST:?set GCP_HOST=user@vm — a reachable GCP GPU VM with guix + kvm}" + echo "cloud mode: sync repo to $GCP_HOST and run --mode local there (grounder on the GPU)." >&2 + rsync -a --delete --exclude '.git' "$REPO/" "$GCP_HOST:sourceos/" + # AS_GROUND_URL defaults to the VM's local grounder; forward the model choice. + exec ssh "$GCP_HOST" \ + "cd sourceos && AS_GROUND_URL='${AS_GROUND_URL}' bash tests/agent-s/run-guix.sh --mode local --main-model '${MAIN_MODEL}'" + ;; + *) echo "unknown --mode: $MODE" >&2; exit 2 ;; +esac