Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2ce8bb5b77 | ||
|
|
50879cea5a |
2 changed files with 34 additions and 42 deletions
|
|
@ -125,6 +125,23 @@ kind-specific payload carrier.
|
||||||
inbox messages to each target at the scheduled time, recurring
|
inbox messages to each target at the scheduled time, recurring
|
||||||
when `interval_seconds` is set.
|
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:<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>` 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.
|
||||||
|
|
||||||
|
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
|
### Destroy semantics
|
||||||
|
|
||||||
`HostRequest::Destroy { name, purge }` is the lifecycle tear-down,
|
`HostRequest::Destroy { name, purge }` is the lifecycle tear-down,
|
||||||
|
|
|
||||||
|
|
@ -1,41 +1,16 @@
|
||||||
//! Scheduled prompts (closes #444). Persistent sqlite queue of
|
//! Scheduled prompts: persistent sqlite queue of `(fire_at, targets,
|
||||||
//! `(fire_at, targets, body)` rows that the worker fans out as
|
//! body)` rows that the worker fans out as broker `Message`s to each
|
||||||
//! broker `Message`s to each target's inbox at fire time. Recurring
|
//! target's inbox at fire time. Recurring schedules carry
|
||||||
//! schedules carry `interval_seconds` and re-arm `next_fire_at` on
|
//! `interval_seconds` and re-arm `next_fire_at` on delivery;
|
||||||
//! delivery; one-shots are reaped.
|
//! one-shots are reaped.
|
||||||
//!
|
//!
|
||||||
//! ## Three submit paths
|
//! Schema + retention: `docs/persistence.md::/var/lib/hyperhive/broker.sqlite`
|
||||||
//!
|
//! (the `scheduled_prompts` / `scheduled_prompt_targets` table bullets).
|
||||||
//! - **Operator-direct** (`source = Operator`): the operator adds
|
//! Submit paths (operator-direct vs `ApprovalKind::SchedulePrompt`,
|
||||||
//! a schedule through the dashboard form. Lands in the table
|
//! plus why even agent-self schedules go through approval):
|
||||||
//! immediately, no approval gate.
|
//! `docs/approvals.md::Scheduled prompts (submit paths)`.
|
||||||
//! - **Agent-requested** (`source = Approval { id }`): a sub-agent
|
//! Catch-up clamp on resume + per-target tombstoning:
|
||||||
//! (or the manager) submits a `RequestSchedulePrompt` through the
|
//! `docs/approvals.md::Scheduled prompt worker (catch-up clamp)`.
|
||||||
//! 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.
|
|
||||||
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
|
@ -145,7 +120,7 @@ pub struct NewSchedule {
|
||||||
pub source: ScheduleSource,
|
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.
|
/// Every field is `Option<_>`; `None` keeps the existing value.
|
||||||
/// The doubly-wrapped `Option<Option<u64>>` on `interval_seconds`
|
/// The doubly-wrapped `Option<Option<u64>>` on `interval_seconds`
|
||||||
/// is intentional: outer `None` = "don't touch", outer
|
/// is intentional: outer `None` = "don't touch", outer
|
||||||
|
|
@ -154,7 +129,7 @@ pub struct NewSchedule {
|
||||||
/// "missing key" vs "explicit null" — the dashboard surface
|
/// "missing key" vs "explicit null" — the dashboard surface
|
||||||
/// preserves the distinction.
|
/// 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
|
/// run inside the same transaction as the scalar field updates so
|
||||||
/// "save my changes" is atomic. Remove delegates to the same
|
/// "save my changes" is atomic. Remove delegates to the same
|
||||||
/// cancel-targets path used by `cancel_targets` (tombstoning, preserves
|
/// cancel-targets path used by `cancel_targets` (tombstoning, preserves
|
||||||
|
|
@ -365,7 +340,7 @@ impl ScheduledPrompts {
|
||||||
Ok(skipped)
|
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
|
/// Every scalar field is `Option<_>`; `None` means "leave the
|
||||||
/// existing value alone", `Some(_)` means "set it to this."
|
/// existing value alone", `Some(_)` means "set it to this."
|
||||||
/// Refuses cancelled rows (no point editing a tombstone —
|
/// Refuses cancelled rows (no point editing a tombstone —
|
||||||
|
|
@ -373,8 +348,8 @@ impl ScheduledPrompts {
|
||||||
/// `interval_seconds = Some(0)` — same rule as submit-time
|
/// `interval_seconds = Some(0)` — same rule as submit-time
|
||||||
/// validation.
|
/// validation.
|
||||||
///
|
///
|
||||||
/// Targets are mutable via `targets_remove` + `targets_add`
|
/// Targets are mutable via `targets_remove` + `targets_add`.
|
||||||
/// (#478 fast-follow). Both are processed in the same
|
/// Both are processed in the same
|
||||||
/// transaction, with **removes before adds** so a single PATCH
|
/// transaction, with **removes before adds** so a single PATCH
|
||||||
/// can swap a target without ever leaving the schedule
|
/// can swap a target without ever leaving the schedule
|
||||||
/// target-less mid-tx. Remove writes a tombstone via
|
/// target-less mid-tx. Remove writes a tombstone via
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue