172 lines
8.4 KiB
Rust
172 lines
8.4 KiB
Rust
//! 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<String>,
|
|
summary: String,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
source: Option<String>,
|
|
#[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<String>,
|
|
#[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<String>,
|
|
},
|
|
/// 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<i64> },
|
|
/// 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<String>,
|
|
},
|
|
/// 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<String> },
|
|
/// 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<LooseEnd> },
|
|
/// `CountPendingReminders` result.
|
|
PendingRemindersCount { count: u64 },
|
|
/// `ReminderRollup` result.
|
|
ReminderRollup {
|
|
stats: hive_sh4re::approvals::ReminderStats,
|
|
},
|
|
/// Op failed; `message` is operator-facing.
|
|
Err { message: String },
|
|
}
|