docs: migrate scheduled-prompts worker + dashboard-events design prose to docs
This commit is contained in:
parent
1b8a6be8ce
commit
34cc68bdfb
4 changed files with 37 additions and 60 deletions
|
|
@ -142,6 +142,26 @@ One-shot rows fire once (if past due, on the next worker pass) and are deleted b
|
||||||
|
|
||||||
`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.
|
`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.
|
||||||
|
|
||||||
|
### Missing-target failure
|
||||||
|
|
||||||
|
When a target name doesn't resolve to a known agent (container
|
||||||
|
destroyed, operator 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.
|
||||||
|
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** are deleted unconditionally after their single
|
||||||
|
fan-out pass; a broker error on a one-shot is not retried (the
|
||||||
|
operator advisory and `last_result` are the only audit trail).
|
||||||
|
|
||||||
### Destroy semantics
|
### Destroy semantics
|
||||||
|
|
||||||
`HostRequest::Destroy { name, purge }` is the lifecycle tear-down,
|
`HostRequest::Destroy { name, purge }` is the lifecycle tear-down,
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,14 @@
|
||||||
~200 broker messages wrapped in `{ seq, events }`) on the
|
~200 broker messages wrapped in `{ seq, events }`) on the
|
||||||
dashboard and `GET /events/history` (last 2000 `LiveEvent`s
|
dashboard and `GET /events/history` (last 2000 `LiveEvent`s
|
||||||
also wrapped in `{ seq, events }`) on the agent.
|
also wrapped in `{ seq, events }`) on the agent.
|
||||||
|
**One unified channel**: browsers cap concurrent SSE
|
||||||
|
connections per origin (~6 in Chrome). Using one channel per
|
||||||
|
domain would exhaust this budget on a live hive; dispatching
|
||||||
|
by `kind` on the client is a one-liner. Per-domain splits are
|
||||||
|
reserved for high-volume sub-streams most consumers skip (none
|
||||||
|
exist yet). The broker's intra-process channel stays separate
|
||||||
|
from the dashboard channel to avoid coupling `recv_blocking_batch`
|
||||||
|
(hot path inside the harness turn loop) to presentation concerns.
|
||||||
**SSE multiplexing**: the dashboard uses a
|
**SSE multiplexing**: the dashboard uses a
|
||||||
`SharedWorker` (`stream-worker.js`) to hold one upstream
|
`SharedWorker` (`stream-worker.js`) to hold one upstream
|
||||||
`EventSource` per URL. All same-origin tabs share this worker
|
`EventSource` per URL. All same-origin tabs share this worker
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,8 @@
|
||||||
//! Unified dashboard event channel.
|
//! Unified dashboard event channel — all near-real-time browser events
|
||||||
//!
|
//! flow through `Coordinator.dashboard_events`. Each event carries a
|
||||||
//! Anything the browser wants to react to in near-real-time flows through
|
//! monotonic `seq` for client-side dedupe against `/api/state` snapshots.
|
||||||
//! `Coordinator.dashboard_events`. Each event is stamped with a monotonic
|
//! Design rationale (single channel, broker forwarder isolation):
|
||||||
//! per-process `seq` so the client can dedupe its buffered live traffic
|
//! `docs/web-ui/shape.md::One unified channel`.
|
||||||
//! against snapshot/history responses (drop frames with
|
|
||||||
//! `seq <= snapshot.seq`).
|
|
||||||
//!
|
|
||||||
//! Why one channel instead of one-per-domain: browsers cap concurrent
|
|
||||||
//! SSE connections per origin (~6 in chrome) and dispatch-by-kind on the
|
|
||||||
//! client is a one-liner. Splits get reserved for high-volume sub-streams
|
|
||||||
//! that most consumers don't care about (none yet).
|
|
||||||
//!
|
|
||||||
//! Message-broker traffic (`Sent` / `Delivered`) lives on this channel
|
|
||||||
//! too. A background forwarder task in `main.rs` subscribes to the broker
|
|
||||||
//! and re-emits each `MessageEvent` as a `DashboardEvent::Sent` /
|
|
||||||
//! `DashboardEvent::Delivered` with a freshly-stamped seq. Keeping the
|
|
||||||
//! broker's intra-process channel separate avoids coupling the broker
|
|
||||||
//! (used by `recv_blocking_batch` inside the harness loop) to dashboard
|
|
||||||
//! presentation concerns.
|
|
||||||
//!
|
|
||||||
//! New mutation kinds (approval added/resolved, question added/answered,
|
|
||||||
//! transient changed, etc.) land here as additional variants. The client
|
|
||||||
//! dispatches by `kind` and updates the relevant section.
|
|
||||||
|
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,39 +1,7 @@
|
||||||
//! Background loop that drains due `scheduled_prompts` rows
|
//! Background loop that drains due `scheduled_prompts` rows and fans
|
||||||
//! and fans the body out as inbox `Message`s to each active
|
//! the body to each active target. 5s poll cadence, shutdown-aware.
|
||||||
//! target. Mirrors `reminder_scheduler::spawn` shape:
|
//! Catch-up clamp, missing-target handling, and broker-error retry
|
||||||
//! single `spawn(coord)` entry, 5s poll cadence, shutdown-aware.
|
//! semantics: `docs/approvals.md::Scheduled prompt worker`.
|
||||||
//!
|
|
||||||
//! ## Catch-up semantics
|
|
||||||
//!
|
|
||||||
//! When hive-c0re comes back from being down, a recurring row
|
|
||||||
//! whose `next_fire_at` is well in the past would otherwise fire
|
|
||||||
//! N delayed pulses in a row. Instead we fire ONCE and let
|
|
||||||
//! `ScheduledPrompts::rearm` bump `next_fire_at` to the next
|
|
||||||
//! interval slot ≥ `now`, recording the skipped-cycle count in
|
|
||||||
//! the per-target `last_result` so operators see how many
|
|
||||||
//! firings were caught up rather than losing the signal.
|
|
||||||
//!
|
|
||||||
//! ## Missing-target failure
|
|
||||||
//!
|
|
||||||
//! When a target name doesn't resolve to a known agent (the
|
|
||||||
//! container has been destroyed, the operator typo'd a name,
|
|
||||||
//! etc.) the worker:
|
|
||||||
//! 1. records `last_result = "no such agent: <name>"` against
|
|
||||||
//! the per-target row,
|
|
||||||
//! 2. sends a single advisory `Message` from `system` to
|
|
||||||
//! `operator` describing the schedule + target + reason,
|
|
||||||
//! 3. continues fanning out to the other (live) targets.
|
|
||||||
//!
|
|
||||||
//! Transient broker errors (sqlite lock contention, etc.) get
|
|
||||||
//! the per-target `last_result` annotated AND a `tracing::warn`,
|
|
||||||
//! but the post-fire bookkeeping treats the row the same way it
|
|
||||||
//! does on a clean fire:
|
|
||||||
//! - **recurring** rows re-arm — the next interval slot tries
|
|
||||||
//! the broker send again, so transient errors self-heal.
|
|
||||||
//! - **one-shots** delete unconditionally after their single
|
|
||||||
//! fan-out pass; a broker failure on a one-shot is NOT
|
|
||||||
//! retried (the operator advisory + `last_result` are the only
|
|
||||||
//! audit trail).
|
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue