docs(approvals): trim historical framing, dedupe ask/answer with conventions.md

This commit is contained in:
iris 2026-08-15 11:46:46 +02:00 committed by mara
commit f29df9cadb

View file

@ -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:<id>"` 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-<n>.url =
"git+http://<forge>/agent-configs/<n>.git"`) and one
`nixosConfigurations.<n>` output per agent. Each output wraps
`inputs.agent-<n>.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#<n>`.
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/<n>/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/<n>/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#<n>`, guarded by a marker file so it
ran once per hive. It is gone: containers have been rendered
onto `meta#<n>` 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 = "<event>:<agent>"` 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 = "<event>:<agent>"` 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