//! 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 directly (no approval). Use /// this from privileged contexts (operator on the host); it bypasses the /// approval queue intentionally so test scripts and one-off recoveries /// don't need a separate approve step. Spawn { name: String }, /// Submit a spawn request for the user to approve. On approval the host /// creates and starts the container. Mirrors the manager's /// `RequestSpawn` — exposed on the admin socket so the dashboard and CLI /// can also queue spawns through the approval flow. RequestSpawn { name: String }, /// Stop a managed container (graceful). Kill { name: String }, /// Tear down a sub-agent container: stop + remove + drop the systemd /// drop-in, purge pending approvals. Persistent state (proposed/applied /// repos, Claude credentials) is KEPT by default — recreating the agent /// with the same name reuses prior config + login. With `purge=true` /// the agent's `/var/lib/hyperhive/{agents,applied}//` trees are /// also wiped (config history + creds + notes gone forever). Manager /// not destroyable. Destroy { name: String, #[serde(default)] purge: bool, }, /// 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, #[serde(default)] pub kind: ApprovalKind, /// For `ApplyCommit`: the git sha the manager submitted. For `Spawn`: /// empty. Note that this is the manager's *claimed* ref — the /// canonical, hive-c0re-vouched sha after the proposal fetch lives /// in `fetched_sha`. pub commit_ref: String, /// The sha hive-c0re fetched from the proposed repo into applied at /// submission time, then tagged `proposal/`. Stable for the /// lifetime of the approval — manager amends in proposed don't /// change what gets built. Only set for `ApplyCommit` after the /// successful fetch. #[serde(default, skip_serializing_if = "Option::is_none")] pub fetched_sha: Option, 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, /// Optional free-text description the manager attached at submission /// time — shown on the dashboard approval card so the operator can /// understand the change without opening the diff. #[serde(default, skip_serializing_if = "Option::is_none")] pub description: Option, } /// What action the approval, when granted, will trigger. #[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum ApprovalKind { /// Apply a manager-proposed config commit (existing flow). #[default] ApplyCommit, /// Create + start a new sub-agent container with the given name. Spawn, } #[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, } /// One row of a broker inbox query — what the dashboard renders in /// its operator-inbox section and what a per-agent web UI returns /// from a `Recent` request. Lives in `hive_sh4re` so it can travel /// over both the dashboard's `/api/state` and the agent socket /// without an internal-to-wire conversion. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct InboxRow { pub id: i64, pub from: String, pub body: String, pub at: i64, } /// Reminder timing: either relative (wait N seconds) or absolute (at unix /// timestamp). #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "timing_type", rename_all = "snake_case")] pub enum ReminderTiming { /// Remind after this many seconds from now. InSeconds { seconds: u64 }, /// Remind at this unix timestamp (seconds since epoch). At { unix_timestamp: i64 }, } /// One row in the response to `GetLooseEnds`. Tagged enum so new /// thread kinds (forge PRs, long-running approvals from a privileged /// bot, etc) can land later without breaking existing handlers. The /// caller (claude in the agent harness) is expected to render these /// as a short bulleted list — the per-row fields are all the context /// needed without a follow-up fetch. /// /// `Question` and `Reminder` rows are cancellable via the /// `CancelLooseEnd` request (and the `cancel_loose_end` MCP tool); /// `Approval` rows are not (operator approves/denies via the /// dashboard, manager has no withdraw path today). #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "kind", rename_all = "snake_case")] pub enum LooseEnd { /// A pending approval. For agent-flavour `GetLooseEnds` calls /// this only surfaces when the agent itself is the manager /// (sub-agents don't submit approvals). For manager-flavour calls /// it lists every pending approval in the swarm. `agent` is the /// affected agent (target of the spawn / config commit). Approval { id: i64, agent: String, commit_ref: String, #[serde(default, skip_serializing_if = "Option::is_none")] description: Option, /// Wall-clock seconds since `requested_at`. Saturates at zero on /// any clock anomaly (back-step etc). age_seconds: u64, }, /// An unanswered question. For agent-flavour calls: only threads /// where the agent is `asker` OR `target`. For manager-flavour /// calls: every unanswered question in the swarm. `target = None` /// means the question is addressed to the operator (dashboard /// path); `Some(agent)` is a peer-to-peer thread. Question { id: i64, asker: String, #[serde(default, skip_serializing_if = "Option::is_none")] target: Option, question: String, /// Wall-clock seconds since `asked_at`. Saturates at zero. age_seconds: u64, }, /// A scheduled but un-delivered reminder. For agent-flavour calls: /// only the agent's own reminders (where `owner == self`). For /// manager-flavour calls: every pending reminder in the swarm. /// `owner` is the agent who scheduled it; `due_at` is the absolute /// unix timestamp the scheduler is targeting. Reminder { id: i64, owner: String, message: String, due_at: i64, /// Wall-clock seconds since the reminder was scheduled. Saturates /// at zero on clock anomalies. (For time-until-fire, compute /// `due_at - now` client-side from the wire timestamp.) age_seconds: u64, }, } /// Kind discriminator for `CancelLooseEnd`. Maps to which underlying /// store the dispatcher reaches into (`OperatorQuestions` vs /// `Broker::reminders`). Approvals are deliberately not cancellable /// — the operator approves/denies via the dashboard, manager has no /// withdraw path today. #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum CancelLooseEndKind { Question, Reminder, } /// 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. Long-polls /// up to `wait_seconds` (capped at 60s server-side, default 30s /// when None) before returning `Empty`. Recv { #[serde(default)] wait_seconds: Option, }, /// Non-mutating: how many pending messages are addressed to me? /// Used by the harness to render a status line after each tool call. Status, /// Operator-injected message TO this agent (from this agent's own web /// UI). Recipient is implicit — `from` is `"operator"`. Effectively the /// per-agent equivalent of the old dashboard T4LK form, but scoped to /// the agent whose page the operator is on. OperatorMsg { body: String }, /// Wake-up event injected from inside the container — typically an /// extra MCP server (matrix, scraper, webhook bridge) signalling /// that external work has arrived for this agent. Recipient is /// implicit (this agent); `from` is caller-chosen so the wake /// prompt can label the source ("matrix: new message in /// #general"). Identity = socket means anything that can connect /// to `/run/hive/mcp.sock` is implicitly trusted to inject these, /// which is fine: the bind-mount is restricted to the agent's /// own container. Wake { from: String, body: String }, /// Last `limit` messages addressed to this agent, newest-first. /// Non-mutating — pulls from the broker without delivering. The /// per-agent web UI uses this to render its own inbox section. Recent { limit: u64 }, /// Surface a question to either the operator or another agent. /// `to = None` (or `Some("operator")`) routes the question to the /// dashboard's operator-question queue (legacy `AskOperator` /// behaviour). `to = Some()` routes it to that agent's /// inbox as a `HelperEvent::QuestionAsked` so the recipient can /// answer back via `AgentRequest::Answer` (or /// `ManagerRequest::Answer`); the answer threads back to the asker /// as a `HelperEvent::QuestionAnswered` event. Either way the /// response shape is `QuestionQueued { id }` — the asker uses the /// id to correlate the asynchronous answer event. Ask { question: String, #[serde(default)] options: Vec, #[serde(default)] multi: bool, #[serde(default)] ttl_seconds: Option, /// Recipient of the question. `None` or `Some("operator")` = /// the human operator (dashboard); `Some()` = a /// peer agent (their inbox). #[serde(default)] to: Option, }, /// Answer a question previously routed to this agent via /// `HelperEvent::QuestionAsked`. The caller is implicitly the /// answerer; only the question's `target` agent (or the operator, /// via the dashboard) is authorised. Wires through to /// `HelperEvent::QuestionAnswered` in the asker's inbox. Answer { id: i64, answer: String }, /// Schedule a reminder message to be delivered to this agent at a /// future time. The reminder lands in the agent's inbox as an auto-sent /// message from `"reminder"`. Use for agent follow-ups (e.g. check task /// status, retry failed operation). Message length is limited; pass /// `file_path` to store in a file and get a path-reference message /// instead. Remind { message: String, timing: ReminderTiming, #[serde(default)] file_path: Option, }, /// Loose-ends view: pending approvals + unanswered questions /// pending against THIS agent. Approvals only surface if this /// agent submitted them (which only ever happens for the /// manager); questions surface where the agent is `asker` or /// `target`. Cheap O(n) sweep server-side — no caching. GetLooseEnds, /// Count of this agent's pending (un-delivered) reminders. Used /// by the harness's per-turn stats sink to snapshot "what was /// queued at turn-end time" without paying for a full list. CountPendingReminders, /// Self-introspection: who am I, what role, what rev. All values /// derive from coord state (no env access required); useful for /// agents to stamp notes / commits / messages with a trustworthy /// identity after a rename or session-continue boundary where the /// system-prompt-substituted label is no longer reliable. Whoami, /// Cancel an open thread the agent owns: a `Question` they asked /// (returns `[cancelled by ]` as the answer to the asker) /// or a `Reminder` they scheduled (hard-deletes the row). /// Authorisation on the sub-agent surface: caller must own the /// row. The manager surface uses the same wire variant but /// accepts any id. CancelLooseEnd { kind: CancelLooseEndKind, id: i64 }, /// Mark every message popped by this agent since the last `AckTurn` /// as fully handled. Fired by the harness after `TurnOutcome::Ok` /// — claude doesn't see this surface, it's harness↔broker only. /// On `TurnOutcome::Failed` the harness intentionally skips this /// call, so the unacked rows stay in-flight in the DB and get /// requeued by the next `RequeueInflight` on harness boot. Tracks /// the popped-id list in-memory on the broker side; no payload /// needed (the broker knows which ids it handed to this /// recipient). AckTurn, /// Requeue every message the broker handed to this agent that /// never got acked. Fired by the harness exactly once at boot, /// before entering the serve loop — catches the /// crashed-mid-turn / OOM-killed / container-restarted cases /// where a previous harness session popped messages but never /// drove them to a clean turn-end. Resets `delivered_at` on each /// row back to NULL (so the next `Recv` pops it) and remembers /// the id in a per-recipient in-memory set so the next `Recv` /// can tag the message with `redelivered: true` (the harness /// then prepends a "may already be handled" hint to the wake /// prompt). Idempotent + cheap when there's nothing in flight. RequeueInflight, } /// 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. `id` is the broker's row id — opaque /// to claude (the MCP surface strips it before handing the body /// to the model) but tracked by the harness so the broker's /// in-memory unacked list can be drained on `AckTurn`. When /// `redelivered = true` this row was popped earlier, never /// acked (turn crash / OOM / restart), and resurfaced by /// `RequeueInflight` — the harness prepends a "may already be /// handled" hint to the wake prompt so claude can DTRT. Message { from: String, body: String, #[serde(default)] id: i64, #[serde(default)] redelivered: bool, }, /// `Recv` found nothing pending. Empty, /// `Status` result: how many pending messages are in this agent's inbox. Status { unread: u64 }, /// `Recent` result: newest-first inbox rows. Recent { rows: Vec }, /// `Ask` result: the queued question id. The answer lands later /// as `HelperEvent::QuestionAnswered` in this agent's inbox. QuestionQueued { id: i64 }, /// `GetLooseEnds` result: list of loose ends pending against /// this agent. Ordered newest-first within each kind. LooseEnds { loose_ends: Vec }, /// `CountPendingReminders` result. PendingRemindersCount { count: u64 }, /// `Whoami` result: identity + role + the current hyperhive rev /// hive-c0re is running against. `role` is `"agent"` for /// sub-agents (the only path that reaches this variant of the /// response). `hyperhive_rev` is `None` only when the configured /// flake URL has no canonical path. Whoami { name: String, role: String, #[serde(default, skip_serializing_if = "Option::is_none")] hyperhive_rev: Option, }, } // ----------------------------------------------------------------------------- // 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"; /// Logical name the broker uses for the human operator. Messages with /// `to = OPERATOR_RECIPIENT` accumulate in sqlite and surface on the /// dashboard's inbox view — they are never `recv`'d by an agent harness. pub const OPERATOR_RECIPIENT: &str = "operator"; /// 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/denied/failed; if approved, the underlying /// action (rebuild or spawn) has already run by the time this lands. ApprovalResolved { id: i64, agent: String, commit_ref: String, status: ApprovalStatus, #[serde(default, skip_serializing_if = "Option::is_none")] note: Option, /// Canonical sha hive-c0re fetched into applied at submission /// time. `git show ` against `/agents//applied.git` /// inside the manager container yields the exact tree being /// referenced. #[serde(default, skip_serializing_if = "Option::is_none")] sha: Option, /// Terminal tag name in the applied repo for this approval — /// `deployed/`, `failed/`, or `denied/` (and /// `approved/` for the rare bare-approval case where /// no underlying action runs). #[serde(default, skip_serializing_if = "Option::is_none")] tag: Option, }, /// A new container was spawned (post-approval or via the admin CLI /// bypass path). `ok=false` means the spawn failed. Spawned { agent: String, ok: bool, #[serde(default, skip_serializing_if = "Option::is_none")] note: Option, /// Sha of the `deployed/0` commit seeded by hive-c0re on /// first spawn (Some on success, None on failure). #[serde(default, skip_serializing_if = "Option::is_none")] sha: Option, }, /// A container was rebuilt (auto-update on flake rev change, or a /// manual rebuild from CLI/dashboard). Rebuilt { agent: String, ok: bool, #[serde(default, skip_serializing_if = "Option::is_none")] note: Option, /// Sha that ended up at `deployed/` on success, or the /// proposal sha that just got tagged `failed/` on /// failure. None for the (rare) rebuild path that doesn't go /// through an approval (e.g. `auto_update::rebuild_agent` /// reapplying the existing main). #[serde(default, skip_serializing_if = "Option::is_none")] sha: Option, /// `deployed/` or `failed/` for approval-driven /// rebuilds; None for auto-update / dashboard rebuilds that /// don't change the deployed commit. #[serde(default, skip_serializing_if = "Option::is_none")] tag: Option, }, /// A sub-agent's container was stopped (the systemd unit is down; /// persistent state is unchanged). Killed { agent: String }, /// A sub-agent's container was torn down (container removed; state /// dirs preserved per `destroy` semantics). Destroyed { agent: String }, /// A sub-agent's container has no claude session yet (first /// spawn, or `--purge` wiped creds). Manager can't do anything /// about it directly — login is interactive OAuth — but it /// surfaces so the manager knows the agent is in partial-run /// mode and can flag the operator. NeedsLogin { agent: String }, /// An agent successfully completed claude login — the session /// dir now contains creds. Transition fires once per login. LoggedIn { agent: String }, /// An agent's recorded flake rev is stale relative to the /// current hyperhive rev. The manager has the `update` tool to /// trigger a rebuild without operator approval (it's a no-op /// when nothing actually changed). NeedsUpdate { agent: String }, /// Container exited without an operator-initiated stop. Fired by /// the crash watcher when an agent's container transitions from /// running → stopped and no `Stopping` / `Restarting` / /// `Destroying` transient was set, so the operator (or the /// manager) knows it crashed rather than was killed on purpose. ContainerCrash { agent: String, #[serde(default, skip_serializing_if = "Option::is_none")] note: Option, }, /// A question queued via `Ask` was answered (by the operator via /// the dashboard, or by another agent via `Answer`). `id` matches /// the `QuestionQueued.id` returned to the asker; `question` /// echoes the original prompt so the asker can stitch the answer /// back to context across compactions; `answerer` is who answered /// (`"operator"` or a peer agent name). QuestionAnswered { id: i64, question: String, answer: String, answerer: String, }, /// A peer (or the manager) asked this agent a question via /// `Ask { to: Some(), ... }`. The recipient should /// answer via `Answer { id, answer }` on their socket; the answer /// will route back to the asker as a `QuestionAnswered` event. /// `options` + `multi` mirror the original `Ask` args so the /// answerer knows what shape of reply is expected. QuestionAsked { id: i64, asker: String, question: String, #[serde(default)] options: Vec, #[serde(default)] multi: bool, }, } /// 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, }, /// Same shape as `AgentRequest::Recv` — caller-tunable long-poll /// duration, capped at 60s server-side, default 30s when None. Recv { #[serde(default)] wait_seconds: Option, }, /// Non-mutating: pending message count, used to render a status line /// after each MCP tool call (mirrors `AgentRequest::Status`). Status, /// Operator-injected message TO the manager (from the manager's own web /// UI). Same shape as `AgentRequest::OperatorMsg`. OperatorMsg { body: String, }, /// Last `limit` messages addressed to the manager, newest-first. /// Non-mutating; mirror of `AgentRequest::Recent`. Recent { limit: u64, }, /// Submit a spawn request for the user to approve. On approval the host /// creates and starts the container. Brand-new agent names only — if an /// agent of the same name already exists, the approval will fail. RequestSpawn { name: String, /// Optional description shown on the dashboard approval card. #[serde(default, skip_serializing_if = "Option::is_none")] description: Option, }, /// Stop a sub-agent (graceful). Kill { name: String, }, /// Start a previously-stopped sub-agent container. Start { name: String, }, /// Restart a sub-agent container (stop + start). Restart { name: String, }, /// Rebuild a sub-agent: re-applies the current hyperhive flake + /// agent.nix, restarts the container. No approval required — /// it's idempotent and the manager owns its own update cadence. Update { 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, /// Optional description shown on the dashboard approval card so the /// operator knows what the change does without opening the diff. #[serde(default, skip_serializing_if = "Option::is_none")] description: Option, }, /// Surface a question to either the operator or another agent. /// Mirrors `AgentRequest::Ask` exactly — see that doc for the /// routing semantics (operator = dashboard queue; agent = the /// peer's inbox via `HelperEvent::QuestionAsked`). /// /// - `options` is advisory: empty = free-text only; non-empty = the /// dashboard renders the choices alongside a free-text fallback /// ("Other…") so the operator is never trapped. /// - `multi=true` lets the operator pick multiple options (rendered /// as checkboxes). The answer is returned as a single string with /// selections joined by ", ". /// - `ttl_seconds`: optional auto-cancel after that many seconds. On /// expiry the question is resolved with answer `[expired]` and the /// asker gets the usual `QuestionAnswered` event. None = wait /// forever for an answer (or manual cancel). /// - `to`: recipient (None / `Some("operator")` = operator; /// `Some()` = peer agent). Ask { question: String, #[serde(default)] options: Vec, #[serde(default)] multi: bool, #[serde(default)] ttl_seconds: Option, #[serde(default)] to: Option, }, /// Answer a question previously routed to the manager via /// `HelperEvent::QuestionAsked` (i.e. an agent asked the manager /// for input). Mirror of `AgentRequest::Answer`. Answer { id: i64, answer: String }, /// Fetch recent journal lines for a sub-agent container. hive-c0re /// runs `journalctl -M -n --no-pager` and returns /// the output as a string. Useful for diagnosing MCP registration /// failures, startup crashes, and harness errors. /// /// `lines` defaults to 50 when omitted. GetLogs { agent: String, #[serde(default)] lines: Option, }, /// Mirror of `AgentRequest::Remind` on the manager surface — schedule /// a reminder addressed to the manager itself. Same semantics: body /// soft-caps at 4 KiB, oversize bodies auto-persist to /// `/state/reminders/auto-.md` (the manager container's own state /// mount) and the inbox sees a pointer. Remind { message: String, timing: ReminderTiming, #[serde(default)] file_path: Option, }, /// Hive-wide loose-ends view: EVERY pending approval + EVERY /// unanswered question in the swarm. Used by the manager to scan /// for stalled coordination — the per-agent equivalent on the /// sub-agent surface is `AgentRequest::GetLooseEnds` which /// only returns rows where the agent itself is asker / target. GetLooseEnds, /// Count of the manager's own pending reminders. Mirror of /// `AgentRequest::CountPendingReminders` on the manager surface. CountPendingReminders, /// Manager-flavour self-introspection. Same wire shape as /// `AgentRequest::Whoami`, but `role` is always `"manager"`. Whoami, /// Cancel an open thread (question or reminder). Manager surface /// can cancel any row (no owner check) — same dispatch as /// `AgentRequest::CancelLooseEnd` but with privileged auth. CancelLooseEnd { kind: CancelLooseEndKind, id: i64 }, /// Mirror of `AgentRequest::AckTurn` on the manager surface — fired /// by the manager harness after `TurnOutcome::Ok` to close out /// every message popped during the turn. AckTurn, /// Mirror of `AgentRequest::RequeueInflight` on the manager /// surface — fired exactly once on manager harness boot. RequeueInflight, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "kind", rename_all = "snake_case")] pub enum ManagerResponse { Ok, Err { message: String, }, /// Same delivery shape as `AgentResponse::Message` — `id` + /// `redelivered` carry the broker's row id and the /// "previously popped, not acked" flag through the manager /// surface so the manager harness drives the same /// requeue-with-hint flow as a sub-agent. Message { from: String, body: String, #[serde(default)] id: i64, #[serde(default)] redelivered: bool, }, Empty, Status { unread: u64, }, /// Result of `Ask`: the queued question id. The actual answer /// arrives later as a `HelperEvent::QuestionAnswered` in the /// asker's inbox, so this returns immediately rather than blocking /// the turn. QuestionQueued { id: i64, }, /// `Recent` result: mirror of `AgentResponse::Recent`. Recent { rows: Vec, }, /// `GetLogs` result: journal lines for the requested container. Logs { content: String, }, /// `GetLooseEnds` result: hive-wide loose ends (approvals + /// unanswered questions). Same `LooseEnd` variants as the /// agent surface; the manager's view is unfiltered. LooseEnds { loose_ends: Vec, }, /// `CountPendingReminders` result. PendingRemindersCount { count: u64, }, /// `Whoami` result: manager identity. `role` is always /// `"manager"`. Mirror of `AgentResponse::Whoami`. Whoami { name: String, role: String, #[serde(default, skip_serializing_if = "Option::is_none")] hyperhive_rev: Option, }, }