//! Wire types shared between `hive-c0re` and the in-container harness. use serde::{Deserialize, Serialize}; // ----------------------------------------------------------------------------- // Host admin socket — /run/hyperhive/host.sock // ----------------------------------------------------------------------------- /// Requests on the host admin socket. /// /// Wire format: one JSON object per line. #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "cmd", rename_all = "snake_case")] pub enum HostRequest { /// Create and start a sub-agent container `hive-agent-`. Spawn { name: String }, /// Stop a managed container (graceful). Kill { name: String }, /// Apply pending config to a managed container. Rebuild { name: String }, /// List managed containers. List, /// List pending approval requests. Pending, /// Approve a pending request by id; the action runs immediately. Approve { id: i64 }, /// Deny a pending request by id. Deny { id: i64 }, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct HostResponse { pub ok: bool, #[serde(default, skip_serializing_if = "Option::is_none")] pub error: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub agents: Option>, #[serde(default, skip_serializing_if = "Option::is_none")] pub approvals: Option>, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Approval { pub id: i64, pub agent: String, pub commit_ref: String, pub requested_at: i64, pub status: ApprovalStatus, #[serde(default, skip_serializing_if = "Option::is_none")] pub resolved_at: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub note: Option, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum ApprovalStatus { Pending, Approved, Denied, Failed, } impl HostResponse { pub fn success() -> Self { Self { ok: true, error: None, agents: None, approvals: None, } } pub fn error(message: impl Into) -> Self { Self { ok: false, error: Some(message.into()), agents: None, approvals: None, } } pub fn list(agents: Vec) -> Self { Self { ok: true, error: None, agents: Some(agents), approvals: None, } } pub fn pending(approvals: Vec) -> Self { Self { ok: true, error: None, agents: None, approvals: Some(approvals), } } } // ----------------------------------------------------------------------------- // Per-agent socket — /run/hyperhive/agents//mcp.sock on the host, // bind-mounted into the container at /run/hive/mcp.sock. // ----------------------------------------------------------------------------- /// A logical message between agents. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Message { pub from: String, pub to: String, pub body: String, } /// Requests on a per-agent socket. The agent's identity is the socket /// it came in on; `Send.from` is filled in by the server, not the client. #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "cmd", rename_all = "snake_case")] pub enum AgentRequest { /// Send a message to another agent. Send { to: String, body: String }, /// Pop one pending message from this agent's inbox. Recv, } /// Responses on a per-agent socket. #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "kind", rename_all = "snake_case")] pub enum AgentResponse { /// `Send` succeeded. Ok, /// Either `Send` failed or `Recv` errored. Err { message: String }, /// `Recv` produced a message. Message { from: String, body: String }, /// `Recv` found nothing pending. Empty, } // ----------------------------------------------------------------------------- // Manager socket — /run/hyperhive/manager/mcp.sock on the host, bind-mounted // into the manager container at /run/hive/mcp.sock. // ----------------------------------------------------------------------------- /// Logical name the broker uses for the manager. pub const MANAGER_AGENT: &str = "manager"; /// Sender hive-c0re uses for events it pushes into the manager's inbox. /// Manager harness recognises this and parses the body as a `HelperEvent`. pub const SYSTEM_SENDER: &str = "system"; /// Out-of-band events the host-side daemon pushes to the manager's inbox. /// Serialised as JSON in `Message::body` (sender = `SYSTEM_SENDER`). #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "event", rename_all = "snake_case")] pub enum HelperEvent { /// An approval was approved or denied; if approved, the rebuild has /// already run (status = Approved on success, Failed on error). ApprovalResolved { id: i64, agent: String, commit_ref: String, status: ApprovalStatus, #[serde(default, skip_serializing_if = "Option::is_none")] note: Option, }, } /// Requests on the manager socket. Manager has the agent surface (send/recv) /// plus privileged lifecycle verbs. #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "cmd", rename_all = "snake_case")] pub enum ManagerRequest { Send { to: String, body: String, }, Recv, /// Spawn a sub-agent. Phase 5 will gate this on user approval. Spawn { name: String, }, /// Stop a sub-agent (graceful). Kill { name: String, }, /// Submit a config commit for the user to approve. `commit_ref` is opaque /// to the host (typically a git sha pointing into the agent's config repo). /// On approval the host applies the change via `nixos-container update`. RequestApplyCommit { agent: String, commit_ref: String, }, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "kind", rename_all = "snake_case")] pub enum ManagerResponse { Ok, Err { message: String }, Message { from: String, body: String }, Empty, }