Watch
0
0
Fork
You've already forked hyperhive
0

hive-c0re stops owning scheduled prompts; agents lose the schedule tools

Scheduled prompts are swarm-level now (swarmctl schedule, #4936). The hive
keeps the rows it has only so the operator can copy them over.

- hive-c0re no longer fires anything: the scheduled-prompts worker is gone,
  and with it every create/edit/cancel/pause/resume/fire path (dashboard
  endpoints, agent-socket verbs, the SchedulePrompt approval kind and its
  approve handler). The store keeps open + list; GET /api/schedules stays.
- A v6 approvals migration drops schedule_prompt rows.
- Nothing submits approvals any more, so the submit path goes too:
  Approvals::submit_kind (now a test fixture), the ApprovalAdded dashboard
  event and its emitter.
- Coordinator::push_todo, whose only caller was the worker, goes, and with
  it hive_host_sock::agent_todo_socket and hive-c0re's hive-agent-sock /
  hive-sock-client deps.
- The schedules_changed dashboard event goes; nothing mutates the rows.
- Agents lose request_schedule_prompt, cancel_schedule, fire_schedule_now,
  edit_schedule and list_schedules, the `scheduling` tool group, their
  system-prompt line and their transcript formatters. `remind` is unchanged.
This commit is contained in:
atlas 2026-10-04 21:23:18 +02:00
commit c3a54bbdd0
31 changed files with 273 additions and 3124 deletions

View file

@ -15,8 +15,8 @@ pub struct Approval {
pub id: i64,
pub agent: Ident,
pub kind: ApprovalKind,
/// Kind-specific payload (git sha / inputs array / schedule
/// payload / empty). See the Approval struct doc.
/// Kind-specific payload (git sha / inputs array / empty). See the
/// Approval struct doc.
pub commit_ref: String,
pub requested_at: DateTime<Utc>,
pub status: ApprovalStatus,
@ -40,8 +40,6 @@ pub enum ApprovalKind {
/// Run `nix flake update [inputs...]` on the meta flake and commit
/// the resulting lock changes.
UpdateMetaInputs,
/// Add a scheduled prompt to the broker queue.
SchedulePrompt,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]

View file

@ -1,8 +1,7 @@
//! Manager socket — `/run/hyperhive/manager/mcp.sock` on the host,
//! bind-mounted into the manager container at `/run/hive/mcp.sock`.
//! Reserved recipient names/senders, the out-of-band `HelperEvent`
//! payload hive-c0re pushes into the manager's inbox, and the
//! schedule-prompt submission payload.
//! Reserved recipient names/senders and the out-of-band `HelperEvent`
//! payload hive-c0re pushes into the manager's inbox.
use hive_types::Ident;
use serde::{Deserialize, Serialize};
@ -66,30 +65,6 @@ pub enum HelperEvent {
},
}
/// Submission payload for `RequestSchedulePrompt`. Lives outside the
/// enum so it can also serialize into the approval row's `commit_ref`
/// (the dispatcher re-parses it on approve and inserts the schedule).
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct SchedulePromptPayload {
/// Names of recipient agents. Operator + `root` allowed.
pub targets: Vec<String>,
/// Message body delivered to each target's inbox at fire time.
/// Same size budget as `Send.body` — soft cap at the broker level.
pub body: String,
/// Absolute unix timestamp (seconds) for the FIRST fire. For
/// recurring schedules the worker then re-arms in
/// `interval_seconds` steps.
pub first_fire_at_unix: i64,
/// `None` = one-shot. `Some(n > 0)` = recurring every `n` seconds.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub interval_seconds: Option<u64>,
/// Optional description shown on the dashboard approval card AND
/// stored on the resulting schedule row for the operator's
/// "what is this?" reference later.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
#[cfg(test)]
mod reserved_name_tests {
use super::{MANAGER_AGENT, OPERATOR_RECIPIENT, SYSTEM_SENDER};

View file

@ -162,9 +162,6 @@ pub enum ToolGroup {
/// server-side gate: `cancel_loose_end`'s approval-cancel arm
/// requires it (`socket_server::check_can_cancel_approval`).
Approvals,
/// `request_schedule_prompt`, `fire_schedule_now`, `cancel_schedule`,
/// `edit_schedule`, `list_schedules` - *(privileged)*
Scheduling,
/// Gates no tool today — `create_repo`, its only member, was removed
/// with no replacement. Kept so existing `meta/capabilities.json`
/// grants still parse; `tools()` returns `&[]`.
@ -230,13 +227,6 @@ impl ToolGroup {
Self::Messaging => &["send", "recv", "ack_until"],
Self::Meta => &["get_agent_meta"],
Self::Inbox => &["get_loose_ends", "cancel_loose_end", "remind"],
Self::Scheduling => &[
"request_schedule_prompt",
"fire_schedule_now",
"cancel_schedule",
"edit_schedule",
"list_schedules",
],
// All five empty, for different reasons — see each
// variant's own doc comment above. `Execution` grants the
// out-of-process `bash` MCP server
@ -308,7 +298,6 @@ impl ToolGroup {
Self::Inbox,
Self::Lifecycle,
Self::Approvals,
Self::Scheduling,
Self::Execution,
];
@ -321,7 +310,6 @@ impl ToolGroup {
Self::Inbox,
Self::Lifecycle,
Self::Approvals,
Self::Scheduling,
Self::Forge,
Self::Execution,
Self::WebTools,
@ -340,9 +328,6 @@ impl ToolGroup {
Self::Approvals => {
"no tools — grants cancel_loose_end's approval-cancel arm (privileged)"
}
Self::Scheduling => {
"request_schedule_prompt and related — operator-visible scheduled prompts (privileged)"
}
Self::Forge => "no tools — vestigial since create_repo was removed",
Self::Execution => {
"run, status — run shell commands via mcp__bash__run / mcp__bash__status"

View file

@ -1,15 +1,14 @@
//! Scheduled-prompt row shape on the wire, shared by the dashboard and
//! agent surfaces (mirrors `hive-c0re`'s internal `scheduled_prompts::Schedule`).
//! Scheduled-prompt row shape on the wire, served by the dashboard's
//! read-only schedule list (mirrors `hive-c0re`'s internal `scheduled_prompts::Schedule`).
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
/// Schedule row shape on the wire — mirror of
/// `scheduled_prompts::Schedule` but in the public crate so the
/// dashboard and agent surfaces can deserialize without depending
/// on hive-c0re-internal types. Kept structurally identical to the
/// in-process type; the conversion is field-by-field in
/// `manager_server` / `dashboard`.
/// `scheduled_prompts::Schedule` but in the public crate so a client
/// can deserialize it without depending on hive-c0re-internal types.
/// Kept structurally identical to the in-process type; the conversion
/// is field-by-field in the dashboard.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WireSchedule {
pub id: i64,
@ -22,10 +21,7 @@ pub struct WireSchedule {
pub source: WireScheduleSource,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cancelled_at_unix: Option<DateTime<Utc>>,
/// Set while the schedule is paused. Worker skips paused rows;
/// they keep their `next_fire_at_unix` so resuming at any time
/// fires at the next intended instant (no catch-up clamp needed
/// — a paused schedule simply slips its next fire).
/// Set if the row is paused.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub paused_at_unix: Option<DateTime<Utc>>,
#[serde(default, skip_serializing_if = "Option::is_none")]

View file

@ -645,13 +645,6 @@ fn fmt_hyperhive_tool(name: &str, short: &str, input: &Value) -> String {
format!("{short} ≤{up_to}")
}
"mcp__hyperhive__remind" => fmt_hyperhive_remind(short, input),
"mcp__hyperhive__list_schedules"
| "mcp__hyperhive__cancel_schedule"
| "mcp__hyperhive__fire_schedule_now"
| "mcp__hyperhive__edit_schedule"
| "mcp__hyperhive__request_schedule_prompt" => {
fmt_hyperhive_schedule_tool(name, short, input)
}
"mcp__hyperhive__set_status" => {
format!("{short} \"{}\"", trim_str(&sv(input, "text"), 60))
}
@ -739,91 +732,6 @@ fn fmt_hyperhive_remind(short: &str, input: &Value) -> String {
format!("{short}{when_part}{msg_part}")
}
fn fmt_hyperhive_edit_schedule(short: &str, input: &Value) -> String {
let id = input
.get("id")
.and_then(Value::as_u64)
.map_or_else(|| "?".to_owned(), |n| n.to_string());
let mut parts = vec![format!("#{id}")];
if input.get("body").is_some() {
parts.push("body".to_owned());
}
if input.get("interval_seconds").is_some() {
parts.push("interval".to_owned());
}
if input.get("next_fire_at_unix").is_some() {
parts.push("next".to_owned());
}
if let Some(a) = input.get("targets_add").and_then(Value::as_array)
&& !a.is_empty()
{
parts.push(format!("+{} tgt", a.len()));
}
if let Some(r) = input.get("targets_remove").and_then(Value::as_array)
&& !r.is_empty()
{
parts.push(format!("-{} tgt", r.len()));
}
format!("{short} {}", parts.join(" · "))
}
/// Schedule-management hyperhive tools (list/cancel/fire/edit/request).
fn fmt_hyperhive_schedule_tool(name: &str, short: &str, input: &Value) -> String {
match name {
"mcp__hyperhive__list_schedules" => format!("{short}()"),
"mcp__hyperhive__cancel_schedule" => {
let id = input
.get("id")
.and_then(Value::as_u64)
.map_or_else(|| "?".to_owned(), |n| n.to_string());
let tgts = match input.get("targets").and_then(Value::as_array) {
Some(arr) if !arr.is_empty() => {
let names: Vec<&str> = arr.iter().filter_map(Value::as_str).collect();
format!(" [{}]", names.join(", "))
}
_ => " all".to_owned(),
};
format!("{short} #{id}{tgts}")
}
"mcp__hyperhive__fire_schedule_now" => {
let id = input
.get("id")
.and_then(Value::as_u64)
.map_or_else(|| "?".to_owned(), |n| n.to_string());
format!("{short} #{id}")
}
"mcp__hyperhive__edit_schedule" => fmt_hyperhive_edit_schedule(short, input),
"mcp__hyperhive__request_schedule_prompt" => {
let tgts = match input.get("targets").and_then(Value::as_array) {
Some(arr) => arr
.iter()
.filter_map(Value::as_str)
.collect::<Vec<_>>()
.join(", "),
None => "?".to_owned(),
};
let when = input
.get("first_fire_at_unix")
.and_then(Value::as_u64)
.map_or_else(
|| "?".to_owned(),
|ts| {
let h = (ts % 86_400) / 3_600;
let m = (ts % 3_600) / 60;
format!("{h:02}:{m:02}Z")
},
);
let recur = input
.get("interval_seconds")
.and_then(Value::as_u64)
.map(|s| format!(" +{s}s"))
.unwrap_or_default();
format!("{short} → {tgts} at {when}{recur}")
}
_ => fmt_args_generic(short, input),
}
}
/// `mcp__bash__*` tools.
fn fmt_bash_tool(name: &str, short: &str, input: &Value) -> String {
match name {