//! Wire types for the *in-agent* socket, served by the hive-agent harness //! to the in-container producers. Matrix / bash MCP daemons and //! `forge_notify` are the built-in ones today, but `subsystem` on the todo //! ops below is a plain string, not a closed set: any user-configured MCP //! server declared in an agent's `agent.nix` can dial this socket and push //! its own todos the same way. Carries the loose-ends-v2 *todo* op family //! plus the harness-local *reminder* and *question* op families; more //! in-agent request families may be added over time (the socket is //! deliberately named for the agent, not the todos). //! //! Distinct from `hive-core-agent-sock`, the *host*-served core↔agent //! protocol on `/run/hive/mcp.sock`: this socket never leaves the //! container. The harness owns the todo + reminder stores locally and //! signals its own turn loop directly, so hive-c0re is not in either path — //! no broker round-trip, no long-poll, no marker files. use serde::{Deserialize, Serialize}; use hive_sh4re::inbox::LooseEnd; pub mod paths; /// In-container path of the harness-served in-agent socket. The harness /// binds it on boot; the in-container producers dial it for todo ops. /// (Placeholder default — the harness + producers resolve the real path /// from config; kept here so a producer with no override has a sane one.) pub const DEFAULT_AGENT_SOCKET: &str = "/run/hive/agent.sock"; /// A request on the in-agent socket. Serialised with a `cmd` tag so the /// in-container producers can emit a plain JSON line without linking a /// typed client (matrix/bash build the JSON by hand; a user-configured /// MCP server can do the same with its own `subsystem` marker). #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "cmd", rename_all = "snake_case")] pub enum Request { /// Upsert a todo from an in-container subsystem — matrix / bash / /// forge are the built-in producers, but any subsystem name is /// accepted; nothing restricts `subsystem` to a fixed set, so a /// user-configured MCP server can push its own todos here too. /// `subsystem` is the producer marker; `key` the optional /// subsystem-specific dedup key (a matrix room id, a bash task id). /// A new-or-changed row signals the turn loop; an identical keyed /// re-push is a silent no-op. Keyless todos always insert as one-offs. /// /// `reopen_if_acked` (default `false`) governs what "changed" means /// for an *acked* row with byte-identical `summary`/`source`: by /// default it stays quiet — the right behavior for a reconciler /// producer re-announcing a still-true condition (`disk_watch` polling /// and finding the same problem). A producer whose keyed re-push /// represents a genuinely new occurrence rather than a restatement /// (a recurring scheduled prompt firing again with the same body) sets /// this `true` so an acked row reopens and re-wakes instead of being /// silently swallowed by the previous occurrence's ack. UpsertTodo { subsystem: String, #[serde(default, skip_serializing_if = "Option::is_none")] key: Option, summary: String, #[serde(default, skip_serializing_if = "Option::is_none")] source: Option, #[serde(default)] reopen_if_acked: bool, }, /// Clear producer-resolved todo(s). `key = Some(k)` clears the one /// keyed row; `key = None` clears the subsystem's keyless rows; `all /// = true` wipes the producer's whole set (cancel-and-recreate on /// daemon restart). ClearTodo { subsystem: String, #[serde(default, skip_serializing_if = "Option::is_none")] key: Option, #[serde(default)] all: bool, }, /// List todos, optionally filtered to one `subsystem` (a producer /// reconciling its own set). `None` = all. ListTodos { #[serde(default, skip_serializing_if = "Option::is_none")] subsystem: Option, }, /// The agent marks one of its own todos done, by id. MarkTodoDone { id: i64 }, /// The agent bulk-acks a specific, explicit list of its own todos in /// one shot — for clearing a backlog that's piled up past the point of /// per-id triage, without the range-based `ack_until`-style semantics /// that risk silently acking something never actually looked at (see /// `Todos::mark_done_many`'s doc for why it's ids, not a threshold). MarkTodosDone { ids: Vec }, /// Schedule a reminder that fires into this agent's own turn loop at /// `timing` (harness-local — no broker round-trip). Same semantics as /// the old broker `Remind` request. `file_path`, when set, is where the /// harness persists an over-cap body instead of inlining it. StoreReminder { message: String, timing: hive_sh4re::inbox::ReminderTiming, #[serde(default, skip_serializing_if = "Option::is_none")] file_path: Option, }, /// List this agent's pending reminders (single-agent scope — unlike /// the old broker query, there's no cross-agent `agent` filter here). ListReminders, /// Cancel one of this agent's own pending reminders by id, before it /// fires. CancelReminder { id: i64 }, /// This agent's pending-reminder count — used by the pre-`remind` cap /// check and the harness's own turn-stats sink. CountPendingReminders, /// Scheduled/delivered/pending reminder counts over a trailing /// `since_secs` window (`0` = all time) — the harness's own `/api/stats` /// reminder-activity chart data (was `hive-core-agent-sock`'s /// `ReminderRollup` against the broker; now served from the local store). ReminderRollup { since_secs: u64 }, /// Agent self-service request to compact the current session, mirroring /// the operator's `/compact` dashboard button. Gated server-side: only /// honoured when the last completed turn's context usage is above 66% /// of the effective context window — below that, `Response::Err` /// explains why and takes no action. On a pass, queues the same /// deferred `compact_pending` flag the operator's button sets (consumed /// at the next turn boundary), so it never races a live claude process. /// `wake_prompt`, when set, is driven as a synthetic follow-up turn once /// the compaction actually finishes — the dashboard button's own /// requests go through `Bus::request_compact` directly with `None`, not /// through this variant, since a human watching the dashboard isn't /// waiting on a wake. Compact { wake_prompt: Option }, /// Mirror an outstanding question this agent asked (`ask()` succeeded). /// `target` is who it's waiting on (`"operator"` when asked with /// `to: None`). Part of the questions-mirror increment — see /// `hive-agent::questions`. RecordAskedQuestion { id: i64, target: String, question: String, }, /// Mirror an outstanding question this agent was asked (a /// `question_asked` system event arrived in the inbox). `asker` is who's /// waiting on this agent for a reply. RecordAnsweringQuestion { id: i64, asker: String, question: String, }, /// Drop the mirror row for `id` (either role) — the question resolved /// from this agent's side (answered, or the `question_answered` event /// for a question this agent asked arrived). ClearQuestion { id: i64 }, /// List this agent's mirrored questions (both roles) — single-agent /// scope, same shape as `ListReminders`. ListQuestions, } /// A response on the in-agent socket. Serialised with a `kind` tag, /// mirroring the core↔agent protocol's response shape. #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "kind", rename_all = "snake_case")] pub enum Response { /// Op succeeded, no payload. Ok, /// Op succeeded and touched `count` rows (clear / mark-done). Acked { count: u64 }, /// `ListTodos` / `ListReminders` / `ListQuestions` result (each wraps /// its rows as the matching [`LooseEnd`] variant). LooseEnds { loose_ends: Vec }, /// `CountPendingReminders` result. PendingRemindersCount { count: u64 }, /// `ReminderRollup` result. ReminderRollup { stats: hive_sh4re::approvals::ReminderStats, }, /// Op failed; `message` is operator-facing. Err { message: String }, }