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

@ -1,10 +1,9 @@
//! Approval queue. Requests are submitted by an agent
//! (`RequestSchedulePrompt`); the user approves/denies via the host admin
//! CLI; on approval the host runs the corresponding action.
//! Approval queue. The user approves/denies via the host admin CLI; on
//! approval the host runs the corresponding action.
//!
//! `UpdateMetaInputs` rows are legacy: the MCP tool that queued them was
//! removed and nothing produces the kind any more. The variant and
//! its (de)serialization stay so pre-existing rows still read back.
//! Nothing submits approvals any more. `UpdateMetaInputs`, the only kind
//! left, is legacy: the MCP tool that queued it was removed. The variant
//! and its (de)serialization stay so pre-existing rows still read back.
use std::path::Path;
use std::sync::Mutex;
@ -64,6 +63,13 @@ const MIGRATIONS: &[Migration] = &[
sql: "DELETE FROM approvals WHERE kind = 'merge_config_pr'",
adds_column: None,
},
// v6: drop `schedule_prompt` rows. Scheduled prompts are swarm-level, so
// no approval of that kind can be acted on, and `row_to_approval` rejects
// the kind.
Migration {
sql: "DELETE FROM approvals WHERE kind = 'schedule_prompt'",
adds_column: None,
},
];
pub struct Approvals {
@ -81,7 +87,8 @@ impl Approvals {
})
}
/// Insert a new pending approval row.
/// Test fixture: insert a new pending approval row.
#[cfg(test)]
pub fn submit_kind(
&self,
agent: &str,
@ -354,7 +361,6 @@ fn row_to_approval(row: &rusqlite::Row<'_>) -> rusqlite::Result<Approval> {
let kind: String = row.get(2)?;
let kind = match kind.as_str() {
"update_meta_inputs" => ApprovalKind::UpdateMetaInputs,
"schedule_prompt" => ApprovalKind::SchedulePrompt,
other => {
return Err(rusqlite::Error::FromSqlConversionFailure(
2,
@ -404,7 +410,6 @@ fn row_to_approval(row: &rusqlite::Row<'_>) -> rusqlite::Result<Approval> {
fn kind_from_str(s: &str) -> Result<ApprovalKind> {
Ok(match s {
"update_meta_inputs" => ApprovalKind::UpdateMetaInputs,
"schedule_prompt" => ApprovalKind::SchedulePrompt,
other => bail!("unknown approval kind '{other}'"),
})
}
@ -421,17 +426,6 @@ mod tests {
(dir, path, db)
}
#[test]
fn mixed_kinds_all_listed() {
let (_dir, _path, db) = open_temp();
db.submit_kind("b", ApprovalKind::SchedulePrompt, "", None, "b")
.unwrap();
db.submit_kind("c", ApprovalKind::UpdateMetaInputs, "[]", None, "c")
.unwrap();
let pending = db.pending().expect("pending");
assert_eq!(pending.len(), 2, "both kinds must be visible");
}
#[test]
fn mark_cancelled_transitions_pending_row() {
// Manager withdraws a pending approval. Row leaves pending(),
@ -441,8 +435,8 @@ mod tests {
let id = db
.submit_kind(
"bitburner",
ApprovalKind::SchedulePrompt,
"{}",
ApprovalKind::UpdateMetaInputs,
"[]",
Some("test"),
"bitburner",
)
@ -464,7 +458,7 @@ mod tests {
// final — re-cancelling errors instead of silently overwriting.
let (_dir, _path, db) = open_temp();
let id = db
.submit_kind("a", ApprovalKind::SchedulePrompt, "{}", None, "a")
.submit_kind("a", ApprovalKind::UpdateMetaInputs, "[]", None, "a")
.unwrap();
db.mark_cancelled(id, "manager").expect("first cancel");
let err = db
@ -479,7 +473,7 @@ mod tests {
// whole list — collect_lenient skips it instead of failing.
let (_dir, path, db) = open_temp();
let good = db
.submit_kind("good", ApprovalKind::SchedulePrompt, "{}", None, "good")
.submit_kind("good", ApprovalKind::UpdateMetaInputs, "[]", None, "good")
.unwrap();
let raw = Connection::open(&path).unwrap();
raw.execute(
@ -502,14 +496,20 @@ mod tests {
// fall back to the root agent.
let (_dir, path, db) = open_temp();
let id = db
.submit_kind("child", ApprovalKind::SchedulePrompt, "{}", None, "parent")
.submit_kind(
"child",
ApprovalKind::UpdateMetaInputs,
"[]",
None,
"parent",
)
.unwrap();
assert_eq!(db.submitter_of(id).unwrap().as_deref(), Some("parent"));
let raw = Connection::open(&path).unwrap();
raw.execute(
"INSERT INTO approvals (agent, kind, commit_ref, requested_at, status)
VALUES ('old', 'schedule_prompt', '', 0, 'pending')",
VALUES ('old', 'update_meta_inputs', '', 0, 'pending')",
[],
)
.unwrap();
@ -524,7 +524,7 @@ mod tests {
fn a_pre_v5_merge_config_pr_row_is_dropped_on_open() {
let (_dir, path, db) = open_temp();
let kept = db
.submit_kind("iris", ApprovalKind::SchedulePrompt, "{}", None, "iris")
.submit_kind("iris", ApprovalKind::UpdateMetaInputs, "[]", None, "iris")
.unwrap();
drop(db);
let raw = Connection::open(&path).unwrap();
@ -546,4 +546,30 @@ mod tests {
assert!(db.recent_resolved(10).unwrap().is_empty());
assert!(db.get(old).unwrap().is_none());
}
/// A database written before v6 still holds `schedule_prompt` rows.
/// Opening it drops them and keeps every other row.
#[test]
fn a_pre_v6_schedule_prompt_row_is_dropped_on_open() {
let (_dir, path, db) = open_temp();
let kept = db
.submit_kind("iris", ApprovalKind::UpdateMetaInputs, "[]", None, "iris")
.unwrap();
drop(db);
let raw = Connection::open(&path).unwrap();
raw.execute_batch(
"INSERT INTO approvals (agent, kind, commit_ref, requested_at, status)
VALUES ('iris', 'schedule_prompt', '{}', 0, 'pending');
UPDATE schema_versions SET version = 5 WHERE store = 'approvals';",
)
.unwrap();
let old: i64 = raw.last_insert_rowid();
drop(raw);
let db = Approvals::open(&path).expect("a pre-v6 db opens");
let pending = db.pending().expect("pending");
assert_eq!(pending.len(), 1);
assert_eq!(pending[0].id, kept);
assert!(db.get(old).unwrap().is_none());
}
}

View file

@ -1,5 +1,5 @@
//! Sqlite-backed host-side stores (broker, approval / schedule queues,
//! build logs, power intent) plus the shared connection open/migration
//! Sqlite-backed host-side stores (broker, approval queue, read-only
//! scheduled prompts, build logs, power intent) plus the shared connection open/migration
//! helper (`db`). Each submodule is re-exported at the crate root, so
//! `crate::broker::…` etc. keep working unchanged.

File diff suppressed because it is too large Load diff