Back to feed

sledtools/pika branch #76

vfkit

Remove pikaci vfkit runner path

Target branch: master

Merge Commit: 22ba1584dd07692c393bfb0bd676baf0e6135fac

branch: merged tutorial: ready ci: success
Open CI Details

Continuous Integration

CI: success

Compact status on the review page, with full logs on the CI page.

Open CI Details

Latest run #97 success

10 passed

head 188f4f1961e4a36a0c0e157fa369277a369767cb · queued 2026-03-26 00:01:40 · 10 lane(s)

queued 7s · ran 2m 20s

check-pika-rust · success check-pika-followup · success check-notifications · success check-agent-contracts · success check-rmp · success check-pikachat · success check-pikachat-typescript · success check-apple-host-sanity · success check-pikachat-openclaw-e2e · success check-fixture · success

Summary

This branch removes the vfkit local runner path from pikaci, the Pika CI orchestrator. Previously, pikaci supported three VM-based execution backends: vfkit (local aarch64-linux microVMs on Apple Silicon via nix-darwin linux-builder), remote Linux VMs (x86_64-linux on pika-build), and Tart (macOS guests). The vfkit path is now fully excised—its RunnerKind variant, job definitions, runner materialization, socket handling, builder validation, flake rendering helpers, and all associated tests are deleted. Jobs that previously defaulted to vfkit are migrated to the remote Linux VM backend with staged build lanes, and the runner_kind() fallback now panics instead of silently selecting vfkit. Supporting infrastructure (justfile recipes, Nix flake comments, linux-builder scripts) is updated to reflect that the local linux-builder is no longer required for CI execution.

Tutorial Steps

Remove the VfkitLocal variant from RunnerKind and PlanExecutorKind enums

Intent: Eliminate the vfkit runner as a recognized execution backend at the type level, ensuring no code path can reference it. The runner_kind() method now panics for jobs that would have previously fallen through to vfkit, forcing every job to explicitly declare a supported runner.

Affected files: crates/pikaci/src/model.rs

Evidence
@@ -41,7 +41,6 @@ pub struct JobSpec {
 #[serde(rename_all = "snake_case")]
 pub enum RunnerKind {
     HostLocal,
-    VfkitLocal,
     #[serde(alias = "microvm_remote")]
@@ -51,7 +50,6 @@ impl RunnerKind {
     pub fn as_str(self) -> &'static str {
         match self {
             Self::HostLocal => "host_local",
-            Self::VfkitLocal => "vfkit_local",
@@ -62,7 +60,6 @@ impl RunnerKind {
 #[serde(rename_all = "snake_case")]
 pub enum PlanExecutorKind {
     HostLocal,
-    VfkitLocal,
@@ -109,7 +104,10 @@ impl JobSpec {
         } else if self.staged_linux_rust_lane().is_some() {
             RunnerKind::RemoteLinuxVm
         } else {
-            RunnerKind::VfkitLocal
+            panic!(
+                "job `{}` no longer maps to the removed vfkit runner; assign staged_linux_rust_lane, use HostShellCommand, or use a tart-* target",
+                self.id
+            )

The RunnerKind::VfkitLocal and PlanExecutorKind::VfkitLocal variants are removed from the two central enums that define which executor a job runs on. Every match arm that handled VfkitLocal is deleted throughout the codebase.

Critically, the fallback in JobSpec::runner_kind() at crates/pikaci/src/model.rs:104 no longer returns VfkitLocal for jobs that lack a staged_linux_rust_lane and aren't host-local or Tart jobs. Instead it panics with a descriptive message, making it impossible to accidentally route a job to the removed backend.

The corresponding From<RunnerKind> for PlanExecutorKind impl also drops the VfkitLocal arm, and as_str() on both enums no longer includes "vfkit_local".

A new test unstaged_non_host_jobs_panic_after_vfkit_removal replaces the old standalone_agent_contract_jobs_can_stay_on_vfkit test, verifying that calling runner_kind() on an unstaged, non-host job correctly panics with a message containing "removed vfkit runner".

Delete the run_vfkit_job executor and all vfkit-specific helpers

Intent: Remove the entire vfkit execution pipeline—VM spawning, log pumping, timeout handling, guest result loading, runner link preparation, flake materialization, socket path computation, builder validation, and guest runner config resolution—totaling roughly 300 lines of dead code.

Affected files: crates/pikaci/src/executor.rs

Evidence
@@ -350,7 +348,6 @@ pub fn run_job_on_runner(job: &JobSpec, ctx: &HostContext) -> anyhow::Result<JobOutcome> {
     match job.runner_kind() {
         RunnerKind::HostLocal => run_host_local_job(job, ctx),
-        RunnerKind::VfkitLocal => run_vfkit_job(job, ctx),
@@ -1031,118 +1028,6 @@ fn join_output_copy_pump(
-pub fn run_vfkit_job(job: &JobSpec, ctx: &HostContext) -> anyhow::Result<JobOutcome> {
@@ -1242,40 +1127,6 @@ fn run_remote_linux_vm_job(job: &JobSpec, ctx: &HostContext) -> anyhow::Result<J
-pub(crate) fn prepare_runner_link(
-pub(crate) fn prepare_vfkit_runner_link(
@@ -1321,61 +1172,6 @@ pub(crate) fn remote_linux_vm_prepare_artifact(
-pub(crate) fn materialize_runner_flake(
-pub(crate) fn materialize_vfkit_runner_flake(
@@ -1633,49 +1428,6 @@ fn ensure_supported_host() -> anyhow::Result<()> {
-fn ensure_linux_builder() -> anyhow::Result<()> {
@@ -2144,34 +1896,6 @@ fn command_stdout(command: &mut Command) -> Option<String> {
-fn builders_supports_aarch64_linux(raw: &str) -> bool {
-fn setting_contains(raw: &str, needle: &str) -> bool {
-fn guest_runner_config_for(kind: RunnerKind) -> GuestRunnerConfig {

The following functions and constants are deleted from crates/pikaci/src/executor.rs:

  • run_vfkit_job — The main vfkit executor that spawned the microvm-run binary via /usr/bin/script, pumped stdout/stderr to log files, enforced timeouts, and parsed guest result JSON.
  • prepare_runner_link / prepare_vfkit_runner_link — Nix build wrappers that created symlinks to built runner derivations.
  • materialize_runner_flake / materialize_vfkit_runner_flake — Created the vm/flake/ directory, rendered the guest Nix flake, and returned a Nix installable string.
  • render_local_guest_flake — Convenience wrapper around render_guest_flake that resolved host UID/GID for local (non-remote) flake rendering.
  • ensure_linux_builder — Checked nix config show builders and extra-platforms for aarch64-linux support.
  • ensure_staged_linux_rust_lane_matches_vfkit_guest — Rejected x86_64-linux staged lanes when running in the aarch64-linux vfkit guest.
  • guest_runner_config_for — Mapped RunnerKind to guest system / host pkgs / hypervisor tuples.
  • builders_supports_aarch64_linux / setting_contains — Nix config parsing helpers.
  • vfkit_socket_path — Computed /tmp/pikaci-<run_stub>-<job_id>.sock paths.
  • VFKIT_GUEST_SYSTEM constant ("aarch64-linux").
  • socket_path field from GuestFlakePaths.

The run_job_on_runner dispatch match at line 348 no longer includes a VfkitLocal arm.

Remove socketPath from guest flake rendering

Intent: The vfkit hypervisor was the only consumer of the socketPath parameter in the generated Nix flake. With vfkit removed, the field is stripped from both the Rust rendering logic and the generated Nix expression template.

Affected files: crates/pikaci/src/executor.rs

Evidence
@@ -85,7 +85,6 @@ struct GuestFlakePaths<'a> {
     target_dir: &'a Path,
     staged_linux_rust_workspace_deps_dir: Option<&'a Path>,
     staged_linux_rust_workspace_build_dir: Option<&'a Path>,
-    socket_path: Option<&'a Path>,
@@ -3004,10 +2728,6 @@ fn render_guest_flake(
-    let socket_path = paths
-        .socket_path
-        .map(|path| format!("\"{}\"", nix_escape(&path.display().to_string())))
-        .unwrap_or_else(|| "null".to_string());
@@ -3041,7 +2761,6 @@ fn render_guest_flake(
           hypervisor = "{hypervisor}";
-          socketPath = {socket_path};

The GuestFlakePaths struct loses its socket_path: Option<&'a Path> field. Inside render_guest_flake, the interpolation of socketPath = {socket_path}; into the Nix template is removed, along with the let socket_path = ... binding that serialized it.

All call sites that constructed GuestFlakePaths (the remote microvm materializer and the test helpers) no longer pass socket_path. The Nix guest module (nix/pikaci/guest-module.nix) will need a corresponding update to stop expecting this attribute (covered in a later step).

Rename vfkit_runner_installable to declared_runner_installable

Intent: The function was vfkit-specific only in name. It constructs a generic Nix flake installable path for the declared microvm runner, which is now used exclusively by the remote Linux VM backend. The rename reflects its actual purpose.

Affected files: crates/pikaci/src/executor.rs

Evidence
@@ -3194,7 +2894,7 @@ pub(crate) fn compiled_guest_command(job: &JobSpec) -> (String, bool) {
-pub(crate) fn vfkit_runner_installable(flake_dir: &Path) -> String {
+pub(crate) fn declared_runner_installable(flake_dir: &Path) -> String {
@@ -1400,14 +1196,13 @@ fn materialize_remote_microvm_runner_flake(
-    Ok(vfkit_runner_installable(&flake_dir))
+    Ok(declared_runner_installable(&flake_dir))

The function vfkit_runner_installable is renamed to declared_runner_installable at crates/pikaci/src/executor.rs:2894. Its body is unchanged—it formats the path:{flake_dir}#nixosConfigurations.pikaci-wave1.config.microvm.declaredRunner installable string.

The single remaining call site in materialize_remote_microvm_runner_flake is updated accordingly. The old name was a historical artifact from when vfkit was the only consumer of this installable pattern.

Fix microvm runtime to call materialize_remote_microvm_runner_flake directly

Intent: The microvm runtime module previously called the now-deleted materialize_runner_flake dispatcher, which switched on RunnerKind. Since only the remote microvm path survives, the call is replaced with a direct invocation of materialize_remote_microvm_runner_flake, passing the remote context explicitly.

Affected files: crates/pikaci/src/executor/microvm.rs

Evidence
@@ -100,7 +100,7 @@ pub(super) fn ensure_remote_microvm_runtime(
     let local_flake_dir = ctx.job_dir.join("vm").join("flake");
-    let _installable = materialize_runner_flake(job, ctx)?;
+    let _installable = materialize_remote_microvm_runner_flake(job, ctx, remote)?;

In crates/pikaci/src/executor/microvm.rs:100, the call to materialize_runner_flake(job, ctx) is replaced with materialize_remote_microvm_runner_flake(job, ctx, remote). The old function was a dispatcher that matched on runner_kind() and called different materializers; with vfkit gone, the microvm module calls the specific remote materializer directly, also gaining access to the remote context parameter it needs for remote path resolution.

Migrate job definitions from vfkit to remote Linux VM backend

Intent: All standalone job target specs that previously ran on the local vfkit runner are migrated to use the remote Linux VM backend via existing staged build lane functions. The 'beachhead' smoke-test target is removed entirely as it was vfkit-only.

Affected files: crates/pikaci/src/main.rs

Evidence
@@ -441,22 +440,6 @@ fn target_spec(name: &str) -> anyhow::Result<TargetSpec> {
     match name {
-        "beachhead" => Ok(TargetSpec {
@@ -466,83 +449,30 @@ fn target_spec(name: &str) -> anyhow::Result<TargetSpec> {
-        "agent-control-plane-unit" => Ok(TargetSpec {
+        "agent-control-plane-unit" => single_job_target_spec(
+            "agent-control-plane-unit",
+            "Run all pika-agent-control-plane unit tests in a remote Linux VM",
+            &[],
+            agent_contract_jobs(),
+        ),
@@ -793,51 +723,12 @@ fn target_spec(name: &str) -> anyhow::Result<TargetSpec> {
-        "pikachat-ui-e2e-local-desktop" => Ok(TargetSpec {
-        "pika-desktop-e2e-compile" => Ok(TargetSpec {
-        "pika-desktop-package-tests" => Ok(TargetSpec {
+        "pika-desktop-package-tests" => single_job_target_spec(
+            "pika-desktop-package-tests",
+            "Run pika-desktop package tests in a remote Linux VM",

The target_spec function in crates/pikaci/src/main.rs undergoes significant changes:

Removed targets:

  • "beachhead" — The vfkit smoke-test that ran a single ExactCargoTest is deleted entirely.
  • "pikachat-ui-e2e-local-desktop" — Desktop E2E test that ran in vfkit.
  • "pika-desktop-e2e-compile" — Desktop E2E compile-only check that ran in vfkit.
  • "agent-http-ensure-local" — Agent HTTP deterministic test that ran in vfkit.

Migrated targets (from inline JobSpec with implicit vfkit to single_job_target_spec with shared job pools):

  • "agent-control-plane-unit" → delegates to agent_contract_jobs()
  • "agent-microvm-tests" → delegates to agent_contract_jobs()
  • "server-agent-api-tests" → delegates to agent_contract_jobs()
  • "core-agent-nip98-test" → delegates to agent_contract_jobs()
  • "pika-desktop-package-tests" → delegates to pikachat_rust_jobs()

The agent_contract_jobs() function's descriptions are updated from "in a vfkit guest" to "in a remote Linux VM". The run subcommand's job argument loses its default_value = "beachhead", making the job name required.

The test standalone_agent_contract_target_uses_staged_remote_linux_lane replaces the old test, asserting that standalone targets now resolve to RunnerKind::RemoteLinuxVm with appropriate staged lanes.

Simplify the run plan builder to remove vfkit prepare actions

Intent: The plan builder no longer needs to handle vfkit runner preparation as a distinct action type. The VfkitRunner prepare action variant and its execution handler are removed, and the conditional that guarded runner preparation is simplified to only check for RemoteLinuxVm.

Affected files: crates/pikaci/src/run.rs

Evidence
@@ -1125,11 +1124,6 @@ enum PrepareAction {
-    VfkitRunner {
-        installable: String,
-        runner_link: PathBuf,
-        log_paths: Vec<PathBuf>,
-    },
@@ -1591,66 +1585,29 @@ fn build_run_plan(
-        if matches!(
-            job.runner_kind(),
-            RunnerKind::VfkitLocal | RunnerKind::RemoteLinuxVm
-        ) {
+        if job.runner_kind() == RunnerKind::RemoteLinuxVm {
@@ -4207,18 +4164,6 @@ fn run_prepare_nodes(
-            PrepareAction::VfkitRunner {
-                installable,
-                runner_link,
-                log_paths,
-            } => (|| -> Result<(), String> {

Three areas of crates/pikaci/src/run.rs are simplified:

  1. PrepareAction enum — The VfkitRunner variant (carrying installable string, runner_link path, and log paths) is removed.

  2. build_run_plan — The condition matches!(job.runner_kind(), RunnerKind::VfkitLocal | RunnerKind::RemoteLinuxVm) becomes a simple job.runner_kind() == RunnerKind::RemoteLinuxVm. The nested match that branched between vfkit and remote-linux-vm preparation is flattened into straight-line code that only handles the remote backend.

  3. run_prepare_nodes — The PrepareAction::VfkitRunner match arm that called prepare_vfkit_runner_link is deleted.

Imports are also cleaned up: materialize_vfkit_runner_flake and prepare_vfkit_runner_link are removed from the import block.

Delete vfkit-related tests across executor and model modules

Intent: Remove all test cases that exercised vfkit-specific code paths, and update remaining tests to use remote Linux VM references instead of vfkit references.

Affected files: crates/pikaci/src/executor.rs, crates/pikaci/src/model.rs, crates/pikaci/src/run.rs

Evidence
@@ -3372,47 +3058,6 @@ mod tests {
-    #[test]
-    fn guest_flake_targets_vfkit_exact_test_beachhead() {
@@ -3450,65 +3094,11 @@ mod tests {
-    fn builder_parser_detects_supported_builder_lines() {
-    fn staged_linux_rust_lane_rejects_x86_64_outputs_in_aarch64_vfkit_guest() {
-    fn socket_path_uses_short_tmp_location() {
@@ -3916,211 +3506,6 @@ mod tests {
-    fn guest_flake_can_run_all_package_unit_tests() {
-    fn guest_flake_can_run_filtered_and_full_package_tests() {
-    fn guest_flake_can_run_shell_commands() {
-    fn guest_flake_can_run_root_shell_commands() {
-    fn guest_flake_mounts_staged_linux_rust_outputs_for_pika_core_lane() {
@@ -791,10 +789,10 @@ mod tests {
-    fn standalone_agent_contract_jobs_can_stay_on_vfkit() {
+    fn unstaged_non_host_jobs_panic_after_vfkit_removal() {

A large number of tests are deleted or rewritten:

Deleted from executor.rs:

  • guest_flake_targets_vfkit_exact_test_beachhead — Tested vfkit flake rendering for the beachhead job.
  • builder_parser_detects_supported_builder_lines — Tested the now-deleted builders_supports_aarch64_linux parser.
  • staged_linux_rust_lane_rejects_x86_64_outputs_in_aarch64_vfkit_guest — Tested the vfkit/x86_64 guard.
  • socket_path_uses_short_tmp_location — Tested vfkit_socket_path computation.
  • guest_flake_can_run_all_package_unit_tests — Tested vfkit flake rendering for PackageUnitTests.
  • guest_flake_can_run_filtered_and_full_package_tests — Tested vfkit flake for filtered/package tests.
  • guest_flake_can_run_shell_commands — Tested vfkit flake for ShellCommand.
  • guest_flake_can_run_root_shell_commands — Tested vfkit flake for ShellCommandAsRoot.
  • guest_flake_mounts_staged_linux_rust_outputs_for_pika_core_lane — Tested staged output mounting in vfkit.

Updated in executor.rs:

  • guest_flake_targets_remote_linux_microvm_backend — Drops socket_path: None from GuestFlakePaths and removes the socketPath = null assertion.

Replaced in model.rs:

  • standalone_agent_contract_jobs_can_stay_on_vfkitunstaged_non_host_jobs_panic_after_vfkit_removal — Now verifies that runner_kind() panics with a message about the removed vfkit runner.

Updated in run.rs:

  • Plan record tests switch from beachhead/VfkitLocal to agent-control-plane-unit/RemoteLinuxVm.
  • remote_request_consumer_is_rejected_for_real_staged_vfkit_jobs renamed to remote_request_consumer_is_rejected_for_real_staged_remote_linux_vm_jobs.
  • Sample job records use executor: "remote_linux_vm" instead of "vfkit_local".

Update Nix flake comments and infrastructure scripts

Intent: Remove references to the local nix-darwin linux-builder and vfkit execution path from Nix flake comments and infrastructure justfile recipes, reflecting that the local linux-builder is no longer a CI prerequisite.

Affected files: flake.nix, just/infra.just, justfile, nix/pikaci/guest-module.nix, scripts/linux-builder-recreate.sh, scripts/linux-builder-repair.sh

Evidence
@@ -1010,10 +1010,9 @@ EOF
       # The preferred staged Linux Rust target is now x86_64-linux so the
-      # deterministic Rust lane can align with pika-build instead of the local
-      # nix-darwin linux-builder. Keep the aarch64-linux namespace as a legacy
-      # compatibility output while the local vfkit execute path still hard-codes
-      # an aarch64-linux guest.
+      # deterministic Rust lane can align with pika-build instead of the old
+      # local nix-darwin linux-builder path. Keep the aarch64-linux namespace
+      # only as a legacy compatibility output for any remaining callers.
@@ -63,16 +63,6 @@ pika

flake.nix — The comment block above ci.x86_64-linux is reworded to remove the mention of "the local vfkit execute path still hard-codes an aarch64-linux guest," replacing it with a simpler note that aarch64-linux is kept only as a legacy compatibility output.

just/infra.just — Infrastructure recipes related to the linux-builder (recreate/repair) that were vfkit prerequisites are cleaned up (the diff shows removal of recipes starting around line 63).

nix/pikaci/guest-module.nix — Expected to have socketPath removed from its module options, aligning with the Rust-side removal of that flake parameter.

scripts/linux-builder-recreate.sh and scripts/linux-builder-repair.sh — These scripts managed the local nix-darwin linux-builder VM that vfkit depended on. Changes here reflect that they are no longer critical CI infrastructure.

justfile — Top-level just recipes referencing vfkit or linux-builder workflows are updated or removed.

Diff