Create the new `pika-managed-agent-contract` crate
Intent: Introduce a minimal crate whose only job is to own the serializable contract types shared between the managed-agent provisioning server and its clients. By keeping dependencies to just `serde` and `serde_json`, the crate stays lightweight and avoids pulling in cloud-provider specifics.
Affected files: crates/pika-managed-agent-contract/Cargo.toml, crates/pika-managed-agent-contract/src/lib.rs
Evidence
@@ -0,0 +1,8 @@
+[package]
+name = "pika-managed-agent-contract"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies]
+serde = { workspace = true }
+serde_json = { workspace = true }
@@ -0,0 +1,99 @@
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
+#[serde(rename_all = "snake_case")]
+pub enum AgentStartupPhase {
+ Requested,
+ ProvisioningVm,
+ BootingGuest,
+ WaitingForServiceReady,
+ WaitingForKeypackagePublish,
+ Ready,
+ Failed,
+}
A new crate pika-managed-agent-contract is created under crates/. Its Cargo.toml declares only serde and serde_json as dependencies, both pulled from the workspace.
src/lib.rs contains the four types that were previously defined in pika-cloud:
AgentStartupPhase – enum representing the lifecycle states of a managed agent.
IncusProvisionParams – struct carrying optional Incus-specific provisioning fields.
ManagedVmProvisionParams – thin wrapper that flattens IncusProvisionParams.
AgentProvisionRequest – the top-level request type, with a helper managed_vm_params() method.
All serde attributes (rename_all, skip_serializing_if, deny_unknown_fields, flatten) are preserved exactly, so the wire format is unchanged.
Move tests into the new crate
Intent: Relocate the unit tests that validate serialization round-tripping and legacy-field rejection so they live next to the types they exercise. An additional test (`managed_vm_params_preserve_incus_request_shape`) is added to cover the `managed_vm_params()` conversion.
Affected files: crates/pika-managed-agent-contract/src/lib.rs, crates/pika-cloud/src/lib.rs
Evidence
@@ -0,0 +1,99 @@
+ #[test]
+ fn agent_provision_request_round_trips_incus_backend() {
@@ -0,0 +1,99 @@
+ #[test]
+ fn managed_vm_params_preserve_incus_request_shape() {
@@ -0,0 +1,99 @@
+ #[test]
+ fn agent_provision_request_rejects_removed_legacy_fields() {
@@ -121,34 +67,6 @@ mod tests {
- #[test]
- fn agent_provision_request_round_trips_incus_backend() {
- ...
- #[test]
- fn agent_provision_request_rejects_removed_legacy_fields() {
The two existing tests (agent_provision_request_round_trips_incus_backend and agent_provision_request_rejects_removed_legacy_fields) are removed from pika-cloud/src/lib.rs and placed in pika-managed-agent-contract/src/lib.rs.
A new test, managed_vm_params_preserve_incus_request_shape, asserts that calling managed_vm_params() on an AgentProvisionRequest preserves the inner IncusProvisionParams exactly. This was previously untested behavior.
Remove the types from `pika-cloud`
Intent: Delete the type definitions and their associated tests from `pika-cloud` so the crate no longer owns them. This eliminates duplication and makes `pika-cloud` focused solely on cloud-infrastructure concerns (e.g., `ProviderKind`, `IncusGuestRunRequest`).
Affected files: crates/pika-cloud/src/lib.rs
Evidence
@@ -36,60 +36,6 @@ pub enum ProviderKind {
Incus,
}
-#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
-#[serde(rename_all = "snake_case")]
-pub enum AgentStartupPhase {
- Requested,
- ...
- Failed,
-}
-
-#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Default)]
-pub struct IncusProvisionParams {
54 lines of type definitions and 28 lines of tests are deleted from crates/pika-cloud/src/lib.rs. After this change pika-cloud retains ProviderKind, IncusGuestRunRequest, and its own tests for those types, but no longer defines any managed-agent contract types.
Register the new crate in the workspace
Intent: Add `pika-managed-agent-contract` to the Cargo workspace member list and declare a workspace-level path dependency so that consumer crates can reference it with `{ workspace = true }`.
Affected files: Cargo.toml, Cargo.lock
Evidence
@@ -7,6 +7,7 @@ members = [
"crates/pika-forge-model",
"crates/pikaci",
"crates/pika-agent-protocol",
+ "crates/pika-managed-agent-contract",
@@ -33,6 +34,7 @@ mdk-storage-traits = ...
+pika-managed-agent-contract = { path = "crates/pika-managed-agent-contract" }
@@ -6010,6 +6010,14 @@ dependencies = [
+[[package]]
+name = "pika-managed-agent-contract"
+version = "0.1.0"
+dependencies = [
+ "serde",
+ "serde_json",
+]
The root Cargo.toml is updated in two places:
members array — includes crates/pika-managed-agent-contract so it participates in workspace builds.
[workspace.dependencies] — adds a path dependency entry so downstream crates can write pika-managed-agent-contract = { workspace = true }.
Cargo.lock is regenerated with the new package and its two dependencies (serde, serde_json).
Re-point `cli` to the new contract crate
Intent: The CLI binary only needs the contract types for constructing provisioning requests. Switching it from `pika-cloud` to `pika-managed-agent-contract` removes an unnecessary transitive dependency on all of `pika-cloud`'s cloud-infrastructure code.
Affected files: cli/Cargo.toml, cli/src/main.rs
Evidence
@@ -17,7 +17,7 @@ mdk-storage-traits = { workspace = true }
-pika-cloud = { path = "../crates/pika-cloud" }
+pika-managed-agent-contract = { workspace = true }
@@ -14,7 +14,7 @@ use clap::{Args, Parser, Subcommand, ValueEnum};
-use pika_cloud::{AgentProvisionRequest, AgentStartupPhase, IncusProvisionParams};
+use pika_managed_agent_contract::{AgentProvisionRequest, AgentStartupPhase, IncusProvisionParams};
cli/Cargo.toml swaps pika-cloud for pika-managed-agent-contract. In cli/src/main.rs the single use statement is updated to import from the new crate path. No other code changes are needed because the types themselves are identical.
Re-point `rust` (uniffi bridge) to the new contract crate
Intent: Same rationale as the CLI: the uniffi bridge only imports `AgentProvisionRequest`, `AgentStartupPhase`, and `IncusProvisionParams`. It should depend on the contract crate, not the full cloud crate.
Affected files: rust/Cargo.toml, rust/src/core/agent.rs
Evidence
@@ -26,7 +26,7 @@ keyring-core = { workspace = true }
-pika-cloud = { path = "../crates/pika-cloud" }
+pika-managed-agent-contract = { workspace = true }
@@ -2,7 +2,7 @@ use std::time::Duration;
-use pika_cloud::{AgentProvisionRequest, AgentStartupPhase, IncusProvisionParams};
+use pika_managed_agent_contract::{AgentProvisionRequest, AgentStartupPhase, IncusProvisionParams};
rust/Cargo.toml replaces the direct path dependency on pika-cloud with the workspace reference to pika-managed-agent-contract. The import in rust/src/core/agent.rs is updated accordingly. The change is a one-line diff in each file.
Add `pika-managed-agent-contract` as a dependency of `pika-server`
Intent: `pika-server` still needs `pika-cloud` for other functionality (e.g., `IncusGuestRunRequest`), but it now re-exports the contract types from the new crate. Adding `pika-managed-agent-contract` alongside `pika-cloud` makes the dependency graph explicit.
Affected files: crates/pika-server/Cargo.toml, crates/pika-server/src/managed_runtime_contract.rs
Evidence
@@ -19,6 +19,7 @@ futures = { workspace = true }
+pika-managed-agent-contract = { workspace = true }
pika-cloud = { path = "../pika-cloud" }
@@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize};
-pub(crate) use pika_cloud::{
+pub(crate) use pika_managed_agent_contract::{
AgentProvisionRequest, AgentStartupPhase, IncusProvisionParams, ManagedVmProvisionParams,
};
pika-server is the one consumer that keeps pika-cloud as a dependency (it uses other types from that crate). However its managed_runtime_contract.rs module, which re-exports the contract types with pub(crate) use, is switched to import from pika-managed-agent-contract instead. This means the server's internal contract surface now comes from the dedicated crate, while pika-cloud is retained only for cloud-specific types like IncusGuestRunRequest.