From f29df9cadb534788cab0accd46cb1096597d4713 Mon Sep 17 00:00:00 2001 From: iris Date: Sat, 15 Aug 2026 11:46:46 +0200 Subject: [PATCH] docs(approvals): trim historical framing, dedupe ask/answer with conventions.md --- docs/approvals.md | 113 ++++++++++++++++------------------------------ 1 file changed, 38 insertions(+), 75 deletions(-) diff --git a/docs/approvals.md b/docs/approvals.md index 05ca03dd..d3885c49 100644 --- a/docs/approvals.md +++ b/docs/approvals.md @@ -212,10 +212,9 @@ An agent target's delivery is `push_todo` (`Coordinator::push_todo`, `docs/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:"` per -target gives `push_todo`'s own upsert-by-key dedup the job a -now-removed `has_pending_with_body` broker check used to do: 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. +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. **`operator` is the one exception** — it's a valid schedule target but has no in-container todo inbox, so it keeps the original broker @@ -290,8 +289,7 @@ declares one flake input per agent (`agent-.url = "git+http:///agent-configs/.git"`) and one `nixosConfigurations.` output per agent. Each output wraps `inputs.agent-.nixosModules.default` with the identity + -`HIVE_PORT` / `HIVE_LABEL` / `HIVE_DASHBOARD_PORT` injection -module that `setup_applied` used to generate inline. +`HIVE_PORT` / `HIVE_LABEL` / `HIVE_DASHBOARD_PORT` injection module. Containers run against `--flake /var/lib/hyperhive/meta#`. The declared input url is the agent's **forge config repo** (the @@ -419,9 +417,9 @@ submitter pushes again (or closes it) to retry. ### Dispatch via the job queue Long-running approval work — `MergeConfigPr`, `UpdateMetaInputs`, -`Spawn` — no longer runs inline inside `actions::approve`. Instead -the approval handler submits a DAG to the global job queue -(`docs/coordinator.md::Job queue`): +`Spawn` — runs as a DAG on the global job queue +(`docs/coordinator.md::Job queue`), submitted by the approval handler +rather than run inline: | `ApprovalKind` | DAG submitted | source | |---|---|---| @@ -502,9 +500,9 @@ forge into its own state dir, commit on a branch, open a PR**, and let the operator review and approve it. There is deliberately no second, mount-shaped path that reaches the same file without the review. -Agents holding the `can_manage_top_level_agents` topology role -(defined as `ROLE_CAN_MANAGE_TOP_LEVEL_AGENTS` in `hive-c0re/src/agent_config/topology.rs`) -get additional host-side bind mounts via `set_nspawn_flags`: +Agents holding the `can_manage_top_level_agents` topology role (see +`hive-c0re/src/agent_config/topology.rs`) get additional host-side +bind mounts via `set_nspawn_flags`: - `/var/lib/hyperhive/agents/` → `/agents/` (RW) — all top-level agents' proposed repos (not just direct children). @@ -536,34 +534,24 @@ cat /meta/flake.lock | jq '.nodes | with_entries(select(.key | startswith("agent The RO binds block push at the kernel level — git plumbing inside the container cannot corrupt either authoritative repo. -## Migration from the pre-tag / pre-meta schemes +## Startup migrations (older hosts) -Both overhauls (tag-driven flow + meta flake) ship in-place -migrations that run on every hive-c0re startup. Idempotent; -each phase is a no-op once already applied. Behaviour: +hive-c0re runs a couple of idempotent migrations on every startup so a +host set up before the tag-driven-deploy + meta-flake scheme (both +described above) converges to it automatically. Each phase is a no-op +once already applied: -- Tag-driven phase: assumes the operator ran the one-shot - `git tag deployed/0 main` script (see commit history / - earlier docs revisions) once per agent. Tagging is - non-destructive: it doesn't touch live containers, state - dirs, or claude creds. -- Meta-flake phase: rewrites each `applied//flake.nix` to - the module-only boilerplate, wires the `applied` remote in - each proposed repo, and bootstraps the meta repo from the - current agent list. Set `HIVE_SKIP_META_MIGRATION=1` on the - service to defer. +- **Tags**: agents from before the tag-driven scheme are tagged + `deployed/0` on `main` once. Non-destructive — it doesn't touch live + containers, state dirs, or claude creds. +- **Meta flake**: rewrites each `applied//flake.nix` to the + module-only boilerplate, wires the `applied` remote in each proposed + repo, and bootstraps the meta repo from the current agent list. Set + `HIVE_SKIP_META_MIGRATION=1` on the service to defer this phase. - A further step used to `nixos-container update` every - container onto `meta#`, guarded by a marker file so it - ran once per hive. It is gone: containers have been rendered - onto `meta#` at creation for long enough that no live hive - needs the repoint, and a one-shot nobody can still trigger is - dead weight. Same for the `root` → `h-root` container rename. - -No state loss in either migration. claude creds, /state/ -notes, the events DB, proposed history, and applied history -all survive. The root agent keeps its session; sub-agents stay -logged in. +No state loss in either migration: claude creds, `/state/` notes, the +events DB, and both proposed + applied history all survive. The root +agent keeps its session; sub-agents stay logged in. ## The root/bootstrap container is hive-c0re-managed @@ -606,35 +594,11 @@ renders only for the root agent. The root agent's approval-gating behaviour comes from its CLAUDE.md / agent-specific instructions, not the system prompt template. -`ask(question, options?, multi?, ttl_seconds?, to?)` is available to -**any agent** — it queues a question and returns the id immediately. -When `to` is omitted (or `"operator"`) the question shows up on the -dashboard; when `to` is another agent's name, the recipient receives a -`HelperEvent::QuestionAsked` and answers via their own `answer` -tool. Either way the answer arrives back as -`HelperEvent::QuestionAnswered { id, question, answer, answerer }` -in the asker's inbox. Storage is `hive-c0re::operator_questions` -(sqlite) — same table, with a nullable `target` column -(NULL = operator). Dispatch goes through -`hive-c0re/src/questions.rs::{handle_ask, handle_answer}`. The answer flow is: - -``` -POST /answer-question/{id} agent: Answer { id, answer } - → OperatorQuestions::answer(_, _, "operator") → questions::handle_answer - → notify_agent(asker, QuestionAnswered { → OperatorQuestions::answer(_, _, agent) - answerer: "operator", ... }) → notify_agent(asker, QuestionAnswered { - answerer: agent, ... }) -``` - -Two more paths resolve a pending question with a sentinel answer: - -- `POST /cancel-question/{id}` (✗ CANC3L button on the dashboard) - resolves with `[cancelled]`. The asking agent sees a terminal state - and can fall back. -- `ttl_seconds` deadline: a tokio watchdog spawned at submit time - fires `answer(id, "[expired]")` once the ttl runs out. Already- - resolved races no-op. The dashboard surfaces a `⏳ MM:SS` chip - on each pending question with a deadline. +Any agent (root or not) can also ask a structured question of the +operator or a peer agent via the `ask`/`answer` MCP tools, independent +of the approval flow above — see +`docs/conventions.md#question-routing-ask--answer` for the routing +rules, `ttl_seconds` expiry, and cancellation. ## Helper events to the submitting agent @@ -673,15 +637,14 @@ root agent. Variants (`hive_sh4re::manager::HelperEvent`): The recipient responds via `Answer { id, answer }` and the asker sees the matching `QuestionAnswered`. -The rest of the original lifecycle notices — `Rebuilt`, `Killed`, -`Destroyed`, `NeedsLogin`, `LoggedIn`, `ConfigReady` — were pure "FYI, -check when convenient" events with no reason to drive an immediate -turn, so they've been migrated off `HelperEvent` onto -`push_todo`/`push_todo_submitter` (see above): `agent_todo_socket` -push instead of a broker message, `subsystem = "core"`, -`key = ":"` for dedup, one free-text `summary` in place -of the old structured fields (`rebuilt_todo_summary` renders -`Rebuilt`'s former `ok`/`note`/`sha`/`tag` into that string). +The remaining lower-urgency lifecycle notices — `Rebuilt`, `Killed`, +`Destroyed`, `NeedsLogin`, `LoggedIn`, `ConfigReady` — are "FYI, check +when convenient" events with no reason to drive an immediate turn, so +they deliver via `push_todo`/`push_todo_submitter` (see above) instead +of `HelperEvent`: an `agent_todo_socket` push instead of a broker +message, `subsystem = "core"`, `key = ":"` for dedup, +and a single free-text `summary` (`rebuilt_todo_summary` renders +`Rebuilt`'s `ok`/`note`/`sha`/`tag` fields into that string). Optional `sha` field on `ApprovalResolved` carries the canonical hive-c0re-vouched commit sha. Optional `tag` carries the deploy