From 50879cea5a222984c329e2ca927620b195fd56e3 Mon Sep 17 00:00:00 2001 From: damocles Date: Sun, 31 May 2026 17:07:57 +0200 Subject: [PATCH 1/2] hive-c0re + docs: extract scheduled_prompts prose (#715 batch 2) --- docs/approvals.md | 8 +++++ hive-c0re/src/scheduled_prompts.rs | 57 ++++++++---------------------- 2 files changed, 23 insertions(+), 42 deletions(-) diff --git a/docs/approvals.md b/docs/approvals.md index ff4243ba..c0abe68a 100644 --- a/docs/approvals.md +++ b/docs/approvals.md @@ -125,6 +125,14 @@ kind-specific payload carrier. inbox messages to each target at the scheduled time, recurring when `interval_seconds` is set. +### Scheduled prompt worker (catch-up clamp) + +When hive-c0re comes back from being down, the worker sees rows whose `next_fire_at_unix` is well in the past. For recurring rows that would mean firing N delayed pulses in a row — spammy and useless. Instead the worker fires **once** per row and bumps `next_fire_at_unix` to the next interval slot ≥ `now`, recording how many cycles were skipped in `last_result` (per-target). Operators see "fired late, caught up from 17 skipped" instead of 17 wake-up storms. + +One-shot rows fire once (if past due, on the next worker pass) and are deleted by the worker; recurring rows survive until cancelled. + +`targets` is its own table (`scheduled_prompt_targets`) so partial cancellation flips a single row and the dashboard can show last-fired / last-result per recipient. Cancelling every target reaps the parent row on the next worker pass. + ### Destroy semantics `HostRequest::Destroy { name, purge }` is the lifecycle tear-down, diff --git a/hive-c0re/src/scheduled_prompts.rs b/hive-c0re/src/scheduled_prompts.rs index 739f7751..a3d75325 100644 --- a/hive-c0re/src/scheduled_prompts.rs +++ b/hive-c0re/src/scheduled_prompts.rs @@ -1,41 +1,14 @@ -//! Scheduled prompts (closes #444). Persistent sqlite queue of -//! `(fire_at, targets, body)` rows that the worker fans out as -//! broker `Message`s to each target's inbox at fire time. Recurring -//! schedules carry `interval_seconds` and re-arm `next_fire_at` on -//! delivery; one-shots are reaped. +//! Scheduled prompts: persistent sqlite queue of `(fire_at, targets, +//! body)` rows that the worker fans out as broker `Message`s to each +//! target's inbox at fire time. Recurring schedules carry +//! `interval_seconds` and re-arm `next_fire_at` on delivery; +//! one-shots are reaped. //! -//! ## Three submit paths -//! -//! - **Operator-direct** (`source = Operator`): the operator adds -//! a schedule through the dashboard form. Lands in the table -//! immediately, no approval gate. -//! - **Agent-requested** (`source = Approval { id }`): a sub-agent -//! (or the manager) submits a `RequestSchedulePrompt` through the -//! manager socket. An `ApprovalKind::SchedulePrompt` row is -//! queued; on approve, hive-c0re inserts the schedule row with -//! `source = Approval { id: approval_id }` so the audit trail -//! points back at the operator decision. -//! - **No self-target shortcut**: even agent-self schedules need -//! approval. The existing `remind` MCP tool stays the quick -//! self-wake path; this module is the bigger, multi-recipient, -//! operator-visible thing. -//! -//! ## Catch-up clamp (missed-while-down) -//! -//! When hive-c0re comes back from being down, the worker sees rows -//! whose `next_fire_at` is well in the past. For recurring rows -//! that would mean firing N delayed pulses in a row — spammy and -//! useless. Instead the worker fires ONCE per row and bumps -//! `next_fire_at` to the next interval slot ≥ `now`, recording how -//! many cycles were skipped in `last_result`. Operators see "fired -//! late, caught up from 17 skipped" instead of 17 wake-up storms. -//! -//! ## Per-target state -//! -//! `targets` is its own table so partial cancellation flips a -//! single row + so the dashboard can show last-fired / last-result -//! per recipient. Cancelling every target reaps the parent row on -//! the next worker pass. +//! Schema + retention: `docs/persistence.md::scheduled_prompts / +//! scheduled_prompt_targets`. Submit paths (operator vs +//! `ApprovalKind::SchedulePrompt`), catch-up clamp on resume, and +//! per-target tombstoning semantics: `docs/approvals.md::Scheduled +//! prompt worker (catch-up clamp)`. use std::path::Path; use std::sync::Mutex; @@ -145,7 +118,7 @@ pub struct NewSchedule { pub source: ScheduleSource, } -/// Partial-update payload for `ScheduledPrompts::update` (#474). +/// Partial-update payload for `ScheduledPrompts::update`. /// Every field is `Option<_>`; `None` keeps the existing value. /// The doubly-wrapped `Option>` on `interval_seconds` /// is intentional: outer `None` = "don't touch", outer @@ -154,7 +127,7 @@ pub struct NewSchedule { /// "missing key" vs "explicit null" — the dashboard surface /// preserves the distinction. /// -/// Target add/remove (#474 fast-follow): `targets_add` / `targets_remove` +/// Target add/remove: `targets_add` / `targets_remove` /// run inside the same transaction as the scalar field updates so /// "save my changes" is atomic. Remove delegates to the same /// cancel-targets path used by `cancel_targets` (tombstoning, preserves @@ -365,7 +338,7 @@ impl ScheduledPrompts { Ok(skipped) } - /// Partial-update an existing schedule's mutable fields (#474). + /// Partial-update an existing schedule's mutable fields. /// Every scalar field is `Option<_>`; `None` means "leave the /// existing value alone", `Some(_)` means "set it to this." /// Refuses cancelled rows (no point editing a tombstone — @@ -373,8 +346,8 @@ impl ScheduledPrompts { /// `interval_seconds = Some(0)` — same rule as submit-time /// validation. /// - /// Targets are mutable via `targets_remove` + `targets_add` - /// (#478 fast-follow). Both are processed in the same + /// Targets are mutable via `targets_remove` + `targets_add`. + /// Both are processed in the same /// transaction, with **removes before adds** so a single PATCH /// can swap a target without ever leaving the schedule /// target-less mid-tx. Remove writes a tombstone via From 2ce8bb5b77a129c2e794136c780b41c84bb1cede Mon Sep 17 00:00:00 2001 From: damocles Date: Sun, 31 May 2026 17:14:27 +0200 Subject: [PATCH 2/2] =?UTF-8?q?scheduled=5Fprompts:=20address=20argus=20?= =?UTF-8?q?=F0=9F=9F=A1=20on=20#849=20=E2=80=94=20submit=20paths=20section?= =?UTF-8?q?=20+=20persistence.md=20anchor=20fix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/approvals.md | 9 +++++++++ hive-c0re/src/scheduled_prompts.rs | 12 +++++++----- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/docs/approvals.md b/docs/approvals.md index c0abe68a..75ad859b 100644 --- a/docs/approvals.md +++ b/docs/approvals.md @@ -125,6 +125,15 @@ kind-specific payload carrier. inbox messages to each target at the scheduled time, recurring when `interval_seconds` is set. +### Scheduled prompts (submit paths) + +Two ways a row lands in `scheduled_prompts`: + +- **Operator-direct** (`source = "operator"`): the operator adds a schedule through the dashboard form. Lands in the table immediately, no approval gate — operator action is already the trust boundary. +- **Agent-requested** (`source = "approval:"`): a sub-agent (or the manager) submits a `RequestSchedulePrompt` through the manager socket. An `ApprovalKind::SchedulePrompt` row is queued; on approve, hive-c0re inserts the schedule row with `source = approval:` so the audit trail points back at the operator decision (above). + +No self-target shortcut: even agent-self schedules need approval. The existing `remind` MCP tool stays the quick self-wake path (no approval, lands directly in the agent's own inbox); this module is the bigger, multi-recipient, operator-visible thing. + ### Scheduled prompt worker (catch-up clamp) When hive-c0re comes back from being down, the worker sees rows whose `next_fire_at_unix` is well in the past. For recurring rows that would mean firing N delayed pulses in a row — spammy and useless. Instead the worker fires **once** per row and bumps `next_fire_at_unix` to the next interval slot ≥ `now`, recording how many cycles were skipped in `last_result` (per-target). Operators see "fired late, caught up from 17 skipped" instead of 17 wake-up storms. diff --git a/hive-c0re/src/scheduled_prompts.rs b/hive-c0re/src/scheduled_prompts.rs index a3d75325..d4d83571 100644 --- a/hive-c0re/src/scheduled_prompts.rs +++ b/hive-c0re/src/scheduled_prompts.rs @@ -4,11 +4,13 @@ //! `interval_seconds` and re-arm `next_fire_at` on delivery; //! one-shots are reaped. //! -//! Schema + retention: `docs/persistence.md::scheduled_prompts / -//! scheduled_prompt_targets`. Submit paths (operator vs -//! `ApprovalKind::SchedulePrompt`), catch-up clamp on resume, and -//! per-target tombstoning semantics: `docs/approvals.md::Scheduled -//! prompt worker (catch-up clamp)`. +//! Schema + retention: `docs/persistence.md::/var/lib/hyperhive/broker.sqlite` +//! (the `scheduled_prompts` / `scheduled_prompt_targets` table bullets). +//! Submit paths (operator-direct vs `ApprovalKind::SchedulePrompt`, +//! plus why even agent-self schedules go through approval): +//! `docs/approvals.md::Scheduled prompts (submit paths)`. +//! Catch-up clamp on resume + per-target tombstoning: +//! `docs/approvals.md::Scheduled prompt worker (catch-up clamp)`. use std::path::Path; use std::sync::Mutex;