Back to feed

sledtools/pika branch #80

pika-orch-incus-cleanup-16

Simplify Incus guest request contract

Target branch: master

Merge Commit: d8cc80470a6daba38b448908d124a2057235d37f

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 #105 success

10 passed

head 363950d8eba96ca4f5b7873cfef2688065cd5650 · queued 2026-03-26 00:35:29 · 10 lane(s)

queued 15s · ran 1m 55s

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 simplifies the Incus guest run request contract by removing five directory-path fields (workspace_dir, cargo_home_dir, target_dir, xdg_state_home_dir, home_dir) from the IncusGuestRunRequest struct and the JSON wire protocol. Instead of the host orchestrator dictating these paths on every request, the guest image itself now owns their definitions as local constants in the pikaci-image.nix boot script. The schema version is bumped from 1 to 2 in both the Rust constant and the Nix image to keep the two sides in lockstep. Associated host-side constants, request-building logic, and test assertions are cleaned up, and a new test is added to verify that the runtime contract paths (log, status, events, result) still match the expected image defaults.

Tutorial Steps

Bump the wire-protocol schema version to 2

Intent: Signal a breaking change in the IncusGuestRunRequest JSON contract so that mismatched host/guest pairs fail fast on version mismatch rather than silently misinterpreting the payload.

Affected files: crates/pika-cloud/src/lib.rs, nix/incus/pikaci-image.nix

Evidence
@@ -34,7 +34,7 @@ pub const CMD_SCHEMA_V1: &str = "agent.control.cmd.v1";
-pub const INCUS_GUEST_RUN_REQUEST_SCHEMA_VERSION: u32 = 1;
+pub const INCUS_GUEST_RUN_REQUEST_SCHEMA_VERSION: u32 = 2;
@@ -14,7 +14,7 @@ let
-    INCUS_GUEST_RUN_REQUEST_SCHEMA_VERSION = 1
+    INCUS_GUEST_RUN_REQUEST_SCHEMA_VERSION = 2

The constant INCUS_GUEST_RUN_REQUEST_SCHEMA_VERSION is bumped from 1 to 2 in both the Rust shared-types crate (pika-cloud/src/lib.rs:37) and the Nix guest image script (nix/incus/pikaci-image.nix:17). Because the guest validates the incoming version before processing a request, any host still sending version-1 payloads will be cleanly rejected. This is the coordination point that makes the rest of the cleanup safe to deploy.

Remove directory-path fields from IncusGuestRunRequest struct

Intent: Eliminate the five host-supplied directory paths from the shared Rust data model, shrinking the request surface and preventing the host from influencing guest filesystem layout.

Affected files: crates/pika-cloud/src/lib.rs

Evidence
@@ -65,11 +65,6 @@ pub struct IncusGuestRunRequest {
     pub command: String,
     pub timeout_secs: u64,
     pub run_as_root: bool,
-    pub workspace_dir: String,
-    pub cargo_home_dir: String,
-    pub target_dir: String,
-    pub xdg_state_home_dir: String,
-    pub home_dir: String,

The IncusGuestRunRequest struct in crates/pika-cloud/src/lib.rs:65-70 previously carried five String fields that told the guest which directories to use for the workspace, Cargo home, build target, XDG state, and user home. These are removed entirely. The struct now contains only schema_version, command, timeout_secs, and run_as_root — the minimal information the host legitimately needs to supply. The corresponding round-trip serialization test is updated to match.

Remove host-side constants and request-building logic for the deleted fields

Intent: Clean up the orchestrator code that previously populated the now-removed fields, along with the constants they referenced.

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

Evidence
@@ -227,10 +227,6 @@ const REMOTE_LINUX_VM_INCUS_PROFILE_DEFAULT: &str = "pika-agent-dev";
-const REMOTE_LINUX_VM_INCUS_CARGO_HOME_DIR: &str = "/cargo-home";
-const REMOTE_LINUX_VM_INCUS_TARGET_DIR: &str = "/cargo-target";
-const REMOTE_LINUX_VM_INCUS_XDG_STATE_HOME_DIR: &str = "/run/pika-cloud/xdg-state";
-const REMOTE_LINUX_VM_INCUS_NON_ROOT_HOME_DIR: &str = "/home/pikaci";
@@ -46,15 +46,6 @@ pub(super) fn build_remote_incus_guest_request(job: &JobSpec) -> IncusGuestRunRe
-        workspace_dir: REMOTE_LINUX_VM_INCUS_SNAPSHOT_MOUNT_PATH.to_string(),
-        cargo_home_dir: REMOTE_LINUX_VM_INCUS_CARGO_HOME_DIR.to_string(),
-        target_dir: REMOTE_LINUX_VM_INCUS_TARGET_DIR.to_string(),
-        xdg_state_home_dir: REMOTE_LINUX_VM_INCUS_XDG_STATE_HOME_DIR.to_string(),
-        home_dir: if run_as_root {
-            "/root".to_string()
-        } else {
-            REMOTE_LINUX_VM_INCUS_NON_ROOT_HOME_DIR.to_string()
-        },

Four host-side constants (REMOTE_LINUX_VM_INCUS_CARGO_HOME_DIR, REMOTE_LINUX_VM_INCUS_TARGET_DIR, REMOTE_LINUX_VM_INCUS_XDG_STATE_HOME_DIR, REMOTE_LINUX_VM_INCUS_NON_ROOT_HOME_DIR) are deleted from crates/pikaci/src/executor.rs:230-233.

The build_remote_incus_guest_request function in crates/pikaci/src/executor/incus.rs:46-57 no longer populates any directory fields, including the conditional home_dir logic that previously selected /root vs /home/pikaci based on run_as_root. That decision now lives exclusively inside the guest image.

Move directory ownership into the guest image Nix script

Intent: Make the guest image the single source of truth for its own filesystem layout, so paths can be changed in one place without coordinating a host release.

Affected files: nix/incus/pikaci-image.nix

Evidence
@@ -123,6 +123,12 @@ let
     state_dir = pathlib.Path("/run/pika-cloud")
     logs_dir = state_dir / "logs"
     artifacts_dir = state_dir / "artifacts"
+    workspace_dir = pathlib.Path("/workspace/snapshot")
+    cargo_home_dir = pathlib.Path("/cargo-home")
+    target_dir = pathlib.Path("/cargo-target")
+    xdg_state_home_dir = state_dir / "xdg-state"
+    root_home_dir = pathlib.Path("/root")
+    non_root_home_dir = pathlib.Path("/home/pikaci")
@@ -147,13 +153,6 @@ let
-            workspace_dir = validate_absolute_dir(request.get("workspace_dir"), "workspace_dir")
-            cargo_home_dir = validate_absolute_dir(request.get("cargo_home_dir"), "cargo_home_dir")
-            target_dir = validate_absolute_dir(request.get("target_dir"), "target_dir")
-            xdg_state_home_dir = validate_absolute_dir(
-                request.get("xdg_state_home_dir"), "xdg_state_home_dir"
-            )
-            home_dir = validate_absolute_dir(request.get("home_dir"), "home_dir")
@@ -199,10 +198,10 @@ let
-                child_env["HOME"] = str(home_dir)
+                child_env["HOME"] = str(root_home_dir)
                 child_command = command_prefix
             else:
-                child_env["HOME"] = str(home_dir)
+                child_env["HOME"] = str(non_root_home_dir)

Six path constants are defined at the top of the guest boot script in nix/incus/pikaci-image.nix:126-131: workspace_dir, cargo_home_dir, target_dir, xdg_state_home_dir, root_home_dir, and non_root_home_dir.

The request-parsing block no longer extracts or validates these paths from the JSON payload (lines 153-159 removed). The HOME environment variable is now set from the locally defined root_home_dir or non_root_home_dir rather than the request's home_dir field. The remaining directory variables (workspace_dir, cargo_home_dir, target_dir, xdg_state_home_dir) were already referenced downstream in the script for environment setup, so they continue to work unchanged — they simply draw from local constants now instead of untrusted input.

Update and add tests to enforce the runtime contract

Intent: Ensure existing tests compile after the field removal and add a new contract test that pins the well-known runtime paths the guest image exposes.

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

Evidence
@@ -2593,17 +2591,6 @@ mod tests {
         assert_eq!(request.command, "bash --noprofile --norc -lc 'actionlint'");
         assert_eq!(request.timeout_secs, 120);
         assert!(!request.run_as_root);
-        assert_eq!(
-            request.workspace_dir,
-            REMOTE_LINUX_VM_INCUS_SNAPSHOT_MOUNT_PATH
-        );
-        assert_eq!(request.cargo_home_dir, REMOTE_LINUX_VM_INCUS_CARGO_HOME_DIR);
-        assert_eq!(request.target_dir, REMOTE_LINUX_VM_INCUS_TARGET_DIR);
@@ -2616,7 +2603,14 @@ mod tests {
         assert!(root_request.run_as_root);
-        assert_eq!(root_request.home_dir, "/root");
+    }
+
+    #[test]
+    fn remote_linux_incus_runtime_contract_paths_match_image_defaults() {
+        assert_eq!(CLOUD_GUEST_LOG_PATH, "/run/pika-cloud/logs/guest.log");
+        assert_eq!(STATUS_PATH, "/run/pika-cloud/status.json");
+        assert_eq!(EVENTS_PATH, "/run/pika-cloud/events.jsonl");
+        assert_eq!(RESULT_PATH, "/run/pika-cloud/result.json");

The existing remote_linux_incus_guest_request_round_trip test has all assertions against the removed fields deleted, keeping only the checks for command, timeout_secs, and run_as_root. The conditional home_dir assertion on root requests is also removed.

A new test remote_linux_incus_runtime_contract_paths_match_image_defaults is introduced at crates/pikaci/src/executor.rs:2608-2614. It imports CLOUD_GUEST_LOG_PATH, STATUS_PATH, EVENTS_PATH, and RESULT_PATH from pika_cloud and asserts their exact string values. This acts as a compile-time and CI-time contract: if the shared crate ever changes these paths, the test fails, alerting developers that the guest image must be updated in tandem. The imports at the top of the test module are adjusted accordingly to bring in these additional constants.

Diff