diff --git a/docs/persistence.md b/docs/persistence.md index 8acd29f3..aea02c8f 100644 --- a/docs/persistence.md +++ b/docs/persistence.md @@ -2,20 +2,29 @@ Where state lives, what survives what, and how it's bounded. -## Two sqlite databases +## Three sqlite databases ### `/var/lib/hyperhive/broker.sqlite` (host) -Three tables, all in one file: +Six tables, all in one file — four queues plus the schedule +header/targets split: - `messages` — every inter-agent / operator-bound message. `sender / recipient / body / sent_at / delivered_at / acked_at / in_reply_to`. `in_reply_to` links a reply to its parent row id; the dashboard and per-agent inbox render these as threaded rows. -- `approvals` — the queue. `agent / kind (apply_commit | spawn) / +- `reminders` — `mcp__hyperhive__remind` queue. + `agent / message / file_path / due_at / created_at / sent_at / + attempt_count / last_error`. `file_path` set when a body + exceeded the inline soft-cap and got auto-spilled to a file + under the agent's state dir; the worker delivers a short + pointer instead. `attempt_count` / `last_error` accumulate + on delivery-failed retries. +- `approvals` — the queue. `agent / kind (apply_commit | spawn | + init_config | update_meta_inputs | schedule_prompt) / commit_ref / requested_at / status / resolved_at / note`. - `operator_questions` — `ask` / `answer` queue (despite the - file name, stores both operator-targeted + agent-to-agent + table name, stores both operator-targeted + agent-to-agent questions since the `ask` rename). `asker / question / options_json / multi / asked_at / deadline_at (ttl) / answered_at / answer / target`. `target IS @@ -23,15 +32,37 @@ Three tables, all in one file: Q&A (`HelperEvent::QuestionAsked` pushed into target's inbox, answered via `Answer` request). Migrated via `ALTER TABLE ADD COLUMN` against `pragma_table_info`. +- `scheduled_prompts` — recurring + one-shot prompt queue + (closes #444). `owner / body / interval_seconds (NULL = one-shot) / + next_fire_at_unix / created_at_unix / source ("operator" or + "approval:") / cancelled_at_unix / description`. `owner` + drives cancel-permission checks (operator vs the submitting + agent). Cancelled rows are tombstoned and reaped by the worker + on its next pass. +- `scheduled_prompt_targets` — per-target state for each schedule. + `schedule_id / target / cancelled_at_unix / + last_fired_at_unix / last_result`. `ON DELETE CASCADE` from + `scheduled_prompts(id)` — requires `PRAGMA foreign_keys = ON` + per connection (set at open). Retention: - `Broker::vacuum_delivered` runs hourly via a tokio task in - `hive-c0re::main`. Drops delivered rows older than 30 days. - Undelivered rows are always kept (still in flight). + `hive-c0re::main`. Drops acked message rows older than 30 days + (`acked_at IS NOT NULL`). Undelivered + delivered-but-not-acked + rows are always kept — the harness `ack_turn`s only after a + successful turn, so an unacked row can still be requeued via + `requeue_inflight` on a crash. - Approvals and questions are kept indefinitely — both are audit trails. `actions::destroy` and answered questions stay visible to anything that queries by id. +- Reminder rows are kept after `sent_at` is set (audit trail); + no automatic vacuum today. +- Scheduled prompts: one-shot rows are deleted on fire by the + worker; recurring rows live until the operator cancels them + (`cancel_schedule` MCP / dashboard ✗) which tombstones via + `cancelled_at_unix`, then `reap_cancelled` drops the row on + the next worker pass. ### `/state/hyperhive-events.sqlite` (per agent)