Watch
0
0
Fork
You've already forked hyperhive
0

docs: scheduled prompts are swarm-level; the hive page is a read-only list

Drops docs/tools/scheduling.md and every description of hive-level
schedule creation, the worker, the removed MCP tools, the scheduling tool
group and the removed endpoints/events, pointing at docs/swarm/schedules.md.
This commit is contained in:
atlas 2026-10-04 21:23:18 +02:00
commit b4a2aeb5ba
18 changed files with 90 additions and 346 deletions

View file

@ -1,7 +1,8 @@
# Approvals + helper events
The approval queue is where an agent asks the operator for something it
can't do itself: a scheduled prompt, or a meta-input bump. Config
can't do itself. Nothing submits a new approval today: the one kind
left, a meta-input bump, only reads back from existing rows. Config
changes don't go through this queue: they're pull requests
on the agent's config repo, which an operator merges on the forge, and
that merge deploys them. The submitting agent — any agent with the
@ -16,13 +17,6 @@ it stays informed about what happens after a decision lands.
config (or its own, via a sub-manager) as a pull request on
`agent-configs/<agent>`. Review it on the forge like any other PR, and
merge it there to deploy it. See [Config changes](#config-changes).
- **Scheduled prompt** (`SchedulePrompt`) — an agent asked to schedule
a message to one or more inboxes at a future time. It lands on your
dashboard's Y3R C4LL tab (or `hivectl approvals pending` /
`approve <id>` from the CLI). You can also add schedules yourself
directly from the SCH3DUL3S tab, which skips this approval step
entirely — the gate here is specifically for an *agent* asking to
schedule something, not for you doing it.
- **Meta/flake update** (`UpdateMetaInputs`) — an existing row reads
back and you can approve it. Approving runs the update and commits
the lock change; it doesn't rebuild anything by itself.
@ -113,9 +107,8 @@ merged by an operator on the forge (see [Config changes](#config-changes)).
### Approval kinds (wire shapes)
`ApprovalKind` carries two variants; each maps to a different
`commit_ref` encoding because `ApprovalKind` overloads that field as
the kind-specific payload carrier.
`ApprovalKind` carries one variant, and it overloads `commit_ref` as
its payload carrier.
<!-- vale write-good.Passive = NO -->
- `UpdateMetaInputs` — `commit_ref` stores the JSON-encoded inputs
@ -123,64 +116,8 @@ the kind-specific payload carrier.
etc.). hive-c0re sets the `agent` field to the requesting root agent.
On approve hive-c0re runs `nix flake update [inputs...]` on the
meta flake and commits the resulting lock changes.
- `SchedulePrompt` — `commit_ref` stores the JSON-encoded
`SchedulePromptPayload` (target list, body, schedule) so the
approval row carries the full submission verbatim. On approve
hive-c0re inserts a row into `scheduled_prompts` with
`source = approval:<id>`; the worker fans the body out as
inbox messages to each target at the scheduled time, recurring
when `interval_seconds` is set.
<!-- vale write-good.Passive = YES -->
### 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>"`): an agent submits a `RequestSchedulePrompt` through its MCP socket (the `request_schedule_prompt` tool, `scheduling` group). hive-c0re queues an `ApprovalKind::SchedulePrompt` row; on approve, it 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 it skipped in `last_result` (per-target). Operators see "fired late, caught up from 17 skipped" instead of 17 wake-up storms.
The worker fires one-shot rows once (if past due, on the next worker pass) and deletes them; 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.
### Scheduled prompt delivery: todo, not a broker message
An agent target's delivery is `push_todo` (`Coordinator::push_todo`,
`docs/scheduler/coordinator.md` covers the mechanism generally), not a broker
`Message` — a scheduled prompt wakes its target with a todo instead of
driving an immediate turn, by design. `key = "schedule:<id>"` per
target drives `push_todo`'s own upsert-by-key dedup: a re-fire of the
*same schedule* against a target that hasn't reviewed the last one
collapses into that one todo instead of stacking up.
### Missing-target failure
When a target name doesn't resolve to a known agent (container
destroyed, typo, etc.) the worker:
1. Records `last_result = "no such agent: <name>"` on the
per-target row.
2. Sends a single advisory `Message` from `system` to `operator`
naming the schedule, target, and reason. This one stays a `Message`
regardless of target type — it's a to-operator advisory about a
broken schedule, not the schedule's own delivery.
3. Continues fanning out to the other live targets.
Transient broker errors (sqlite lock contention, etc.) get the same
`last_result` annotation plus a `tracing::warn`, and then:
- **Recurring rows** re-arm to the next interval slot — the retry
self-heals on the next worker pass.
- **One-shot rows**: the worker deletes them unconditionally after their single
fan-out pass; a broker error on a one-shot isn't retried (the
operator advisory and `last_result` are the only audit trail).
### Reminder delivery: file-path semantics
A reminder may carry a `file_path` (the agent-visible path inside its
@ -324,7 +261,6 @@ by the approval handler rather than run inline:
| `ApprovalKind` | DAG submitted | source |
|---|---|---|
| `UpdateMetaInputs` | `meta_update` (`MetaLock` + rebuild fan-out) | `approval` |
| `SchedulePrompt` | — runs inline (single sqlite insert) | — |
The DAG carries the originating `approval_id`. **Every** queued kind
resolves through `actions::resolve_approval_dag` when its DAG settles
@ -508,10 +444,8 @@ the `submitter` column on the `approvals` table). The harness delivers it
as a regular `system` inbox message so it drives a normal claude turn.
`finish_approval` fires an `ApprovalResolved` HelperEvent this way for
**every** approval kind's terminal state. A
"FYI, check when convenient" event doesn't need a message — those go
through `Coordinator::push_todo` instead, a direct live dial of the target
agent's in-container todo socket (same `UpsertTodo` request in-container
producers use). Legacy
"FYI, check when convenient" event doesn't need a message — hive-c0re publishes those as swarm
lifecycle notices instead (`swarm_notices::notify`). Legacy
approval rows that predate the submitter column fall back to the
root agent. Variants (`hive_sh4re::manager::HelperEvent`):
@ -533,9 +467,8 @@ root agent. Variants (`hive_sh4re::manager::HelperEvent`):
The remaining lower-urgency lifecycle notices — `Rebuilt`, `Killed`,
`Destroyed`, `NeedsLogin`, `LoggedIn` — are "FYI, check
when convenient" events with no reason to drive an immediate turn, so
they deliver via `push_todo` (see above) instead
of `HelperEvent`: an `agent_todo_socket` push instead of a broker
message, `subsystem = "core"`, `key = "<event>:<agent>"` for dedup,
they go out via `swarm_notices::notify` (see above) instead
of a `HelperEvent`: `subsystem = "core"`, `key = "<event>:<agent>"`,
and a single free-text `summary` (`rebuilt_todo_summary` renders
`Rebuilt`'s `ok`/`note`/`sha`/`tag` fields into that string).
@ -543,7 +476,7 @@ To add a new lifecycle notice: if it needs to drive an immediate turn
(something genuinely urgent, like `ContainerCrash`), add a
`HelperEvent` variant + call sites + update `prompts/system.md`'s
message-event list. If it's "FYI, check when convenient," call
`push_todo` directly instead — no new wire type
`swarm_notices::notify` instead — no new wire type
needed.
## Autoupdate on startup