hive-c0re/hive-sh4re: remove the ask/answer wire protocol + core routing
This commit is contained in:
parent
46183795dd
commit
2850270829
23 changed files with 177 additions and 851 deletions
|
|
@ -94,7 +94,7 @@ umount-old / mount-new / restart-cascade step.
|
|||
| `request_init_config` (spawn a new child) | any agent, child added under self |
|
||||
| config change via forge PR (any descendant's config) | any ancestor |
|
||||
| `get_logs` (any descendant) | any ancestor |
|
||||
| moderate questions / reminders (cancel any open thread of a descendant) | any ancestor |
|
||||
| moderate reminders (cancel any open thread of a descendant) | any ancestor |
|
||||
| `send` / `recv` routing | parent ↔ same-parent siblings ↔ self ↔ descendants; explicit allow-list for anyone else |
|
||||
| `request_update_meta_inputs` (bump meta lock) | root agents only (today: just `manager`) |
|
||||
|
||||
|
|
@ -149,16 +149,17 @@ other agents don't:
|
|||
- **Scattered ownership checks** — a handful of independent
|
||||
manager-only overrides exist across `hive-c0re` today: loose-ends
|
||||
visibility (manager sees hive-wide, sub-agents only their own),
|
||||
"manager can cancel any question/reminder" overrides on the owner
|
||||
check, `destroy` refusing to act on the manager, and crash-watch
|
||||
skipping the manager (it auto-restarts via systemd instead of going
|
||||
through the crash-watch loop). Each is planned to become an
|
||||
`destroy` refusing to act on the manager, and crash-watch skipping
|
||||
the manager (it auto-restarts via systemd instead of going through
|
||||
the crash-watch loop). Each is planned to become an
|
||||
ancestor/descendant check instead of a manager-name check — see the
|
||||
module docs for `loose_ends.rs`, `stores/operator_questions.rs`,
|
||||
`stores/broker.rs`, `actions.rs`, and `workers/crash_watch.rs` for
|
||||
the current owner-check logic in each. (Reminder cancellation has
|
||||
since moved fully in-agent — see the note on
|
||||
`CancelLooseEndKind::Reminder` in `hive-c0re/src/questions.rs`.)
|
||||
module docs for `loose_ends.rs`, `stores/broker.rs`, `actions.rs`,
|
||||
and `workers/crash_watch.rs` for the current owner-check logic in
|
||||
each. (Question/answer routing and its own manager-override cancel
|
||||
path — formerly `hive-c0re/src/questions.rs` and
|
||||
`stores/operator_questions.rs` — has been removed entirely; reminder
|
||||
cancellation is now handled fully in-agent, see the note on
|
||||
`CancelLooseEndKind::Reminder` in `hive-c0re/src/socket_server/mod.rs`.)
|
||||
|
||||
None of the above is a stable interface — treat the module doc
|
||||
comments as the source of truth for exactly which checks exist today.
|
||||
|
|
|
|||
|
|
@ -598,12 +598,6 @@ 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.
|
||||
|
||||
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
|
||||
|
||||
`Coordinator::notify_submitter(approval_id, &HelperEvent)` routes the
|
||||
|
|
@ -635,18 +629,6 @@ root agent. Variants (`hive_sh4re::manager::HelperEvent`):
|
|||
- `NeedsUpdate { agent }` — sub-agent's recorded flake rev is
|
||||
stale. The root agent calls `update(name)` to rebuild — idempotent,
|
||||
no approval required.
|
||||
- `QuestionAnswered { id, question, answer, answerer }` — peer
|
||||
`Answer` request (answerer = agent name) or ttl watchdog expiry
|
||||
(answerer = `"ttl-watchdog"`, answer = `"[expired]"`). The dashboard's
|
||||
own `/answer-question/{id}` trigger (answerer = `"operator"`) is
|
||||
gone — removed along with the rest of the dashboard's ask/answer
|
||||
surface, so an operator-targeted question now has no path to an
|
||||
answer at all.
|
||||
- `QuestionAsked { id, asker, question, options, multi }` —
|
||||
fired when an agent calls `Ask { to: Some(<this-agent>), ... }`.
|
||||
The recipient responds via `Answer { id, answer }` and the
|
||||
asker sees the matching `QuestionAnswered`.
|
||||
|
||||
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
|
||||
|
|
@ -668,11 +650,11 @@ or the dashboard `↻ R3BU1LD` button when the lock didn't move). When set,
|
|||
bootstrap container yields the exact tree that was referenced.
|
||||
|
||||
To add a new lifecycle notice: if it needs to drive an immediate turn
|
||||
(a live conversation like `Ask`/`Answer`, or something genuinely
|
||||
urgent), add a `HelperEvent` variant + call sites + update
|
||||
`prompts/system.md`'s message-event list. If it's "FYI, check when
|
||||
convenient," call `push_todo`/`push_todo_submitter` directly instead —
|
||||
no new wire type needed.
|
||||
(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`/`push_todo_submitter` directly instead — no new wire type
|
||||
needed.
|
||||
|
||||
## Auto-update on startup
|
||||
|
||||
|
|
|
|||
|
|
@ -171,46 +171,6 @@ cutoff so a later `AckTurn` doesn't double-update and a stale
|
|||
redelivery tag can't outlive its row. The operator-side sibling is
|
||||
the dashboard's "mark all read" (unbounded, per-agent).
|
||||
|
||||
### Question routing (Ask / Answer)
|
||||
|
||||
`AgentRequest::Ask` (and the manager-flavour mirror) surfaces a
|
||||
structured question that either lands in the operator-question queue
|
||||
or in a peer agent's inbox. The recipient is the `to` field:
|
||||
|
||||
- `to = None` or `to = Some("operator")` — routes to the
|
||||
operator-question queue. ⚠️ The dashboard's whole ask/answer
|
||||
surface (the UI that used to render this queue and let the operator
|
||||
reply) has been removed — an operator-targeted question currently
|
||||
has no reader anywhere; this routing case is being removed too as
|
||||
the same effort continues. The legacy `AskOperator` variant
|
||||
collapses into this case.
|
||||
- `to = Some(<agent>)` — peer Q&A. The target agent receives a
|
||||
`HelperEvent::QuestionAsked { id, asker, question, options, multi }`
|
||||
in their inbox. They reply via `AgentRequest::Answer` (or
|
||||
`ManagerRequest::Answer` if they're the manager); the answer
|
||||
threads back to the asker as a `HelperEvent::QuestionAnswered`
|
||||
event.
|
||||
|
||||
Shape fields are uniform across both targets:
|
||||
|
||||
- `options` is advisory — a list is never enforced; peer-agent
|
||||
recipients see it in their `QuestionAsked` event and can return any
|
||||
string regardless.
|
||||
- `multi = true` is a hint that the answerer should treat `options` as
|
||||
a multi-select rather than pick-one. The answer comes back as a
|
||||
single string with selections joined by `", "`.
|
||||
- `ttl_seconds` auto-cancels with answer `[expired]` (and `answerer:
|
||||
"ttl-watchdog"`) when the wait becomes moot. `None` = wait
|
||||
indefinitely or until manual cancel.
|
||||
|
||||
Response shape is always `QuestionQueued { id }` — the asker stores
|
||||
the id and correlates the asynchronous answer event when it lands.
|
||||
Authorisation on `Answer`: only the question's `target` agent, or the
|
||||
operator, is permitted to reply; an answer attempt from anyone else
|
||||
fails the wire-side check. (The operator's only path to invoke this
|
||||
was the now-removed dashboard — see the note above; the
|
||||
authorisation rule itself is unchanged.)
|
||||
|
||||
### Loose-ends wire shape
|
||||
|
||||
`LooseEnd` is the per-row response shape for `GetLooseEnds` (both
|
||||
|
|
@ -220,15 +180,12 @@ privileged bot, etc.) can land later without breaking existing
|
|||
handlers. Each row carries enough context that the caller renders
|
||||
it directly as a bulleted list, no follow-up fetch needed.
|
||||
|
||||
Per-flavour scoping is uniform across the three variants:
|
||||
Per-flavour scoping is uniform across the two cancellable variants:
|
||||
|
||||
- **agent-flavour** `GetLooseEnds` only surfaces rows the calling
|
||||
agent has standing in. `Approval` rows only appear when the
|
||||
calling agent is the manager (sub-agents don't submit
|
||||
approvals). `Question` rows surface where the agent is `asker`
|
||||
OR `target` (the routing semantics from the Ask/Answer
|
||||
subsection above). `Reminder` rows are scoped to `owner ==
|
||||
self`.
|
||||
approvals). `Reminder` rows are scoped to `owner == self`.
|
||||
- **manager-flavour** `GetLooseEnds` lists every pending row in
|
||||
the swarm — full audit view.
|
||||
|
||||
|
|
@ -240,9 +197,6 @@ Per-variant fields:
|
|||
blurb shown on the dashboard card. `commit_ref` is the
|
||||
kind-specific payload (see `docs/approvals.md::Approval kinds
|
||||
(wire shapes)`).
|
||||
- `Question { id, asker, target?, question, age_seconds }` —
|
||||
`target = None` = operator-routed (dashboard); `Some(agent)` =
|
||||
peer-to-peer thread.
|
||||
- `Reminder { id, owner, message, due_at, age_seconds }` —
|
||||
`due_at` is the absolute time the scheduler is targeting (RFC
|
||||
3339 on the wire, see *Timestamps on the wire* below); clients
|
||||
|
|
@ -265,10 +219,9 @@ unsynchronised wall clock, etc.) so the bulleted list never
|
|||
shows nonsense ages.
|
||||
|
||||
`CancelLooseEnd { kind, id }` is the matching write surface. The
|
||||
`kind` enum (`Question` / `Reminder` / `Approval`) selects which
|
||||
underlying store the dispatcher reaches into. `Question` and
|
||||
`Reminder` cancel from either surface subject to ownership
|
||||
checks (asker for the question, scheduler for the reminder).
|
||||
`kind` enum (`Reminder` / `Approval`) selects which underlying store
|
||||
the dispatcher reaches into. `Reminder` cancels from either surface
|
||||
subject to an ownership check (the scheduling agent).
|
||||
`Approval` is manager-only — sub-agents don't submit approvals
|
||||
so they have nothing of their own to withdraw; their wire
|
||||
surface returns a clear error if they try. Cancelling an approval
|
||||
|
|
|
|||
|
|
@ -20,8 +20,8 @@ keeps its state, purging it doesn't.**
|
|||
|
||||
Beyond that:
|
||||
|
||||
- **Approvals and questions are kept forever** — they're audit trails,
|
||||
not caches. Nothing about them ever ages out.
|
||||
- **Approvals are kept forever** — they're an audit trail, not a
|
||||
cache. Nothing about them ever ages out.
|
||||
- **Broker messages**: acked ones vacuum after 30 days; anything
|
||||
undelivered or delivered-but-not-yet-acked is always kept, however
|
||||
old.
|
||||
|
|
@ -55,20 +55,11 @@ power-intent registry:
|
|||
⚠️ The `mcp__hyperhive__remind` queue is **not** here any more: it
|
||||
moved to a harness-local, per-agent store as part of the
|
||||
loose-ends-v2 migration — see [`/harness/` contents
|
||||
below](#state-dirs-per-agent) for where reminders (and todos, and
|
||||
the questions mirror) actually live now.
|
||||
below](#state-dirs-per-agent) for where reminders (and todos)
|
||||
actually live now.
|
||||
- `approvals` — the queue. `agent / kind (merge_config_pr | spawn |
|
||||
init_config | update_meta_inputs | schedule_prompt) /
|
||||
commit_ref / requested_at / status / resolved_at / note`.
|
||||
- `operator_questions` — `ask` / `answer` queue (despite the
|
||||
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
|
||||
NULL` = operator path (dashboard); `target = '<agent>'` = peer
|
||||
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.
|
||||
`owner / body / interval_seconds (NULL = one-shot) /
|
||||
next_fire_at_unix / created_at_unix / source ("operator" or
|
||||
|
|
@ -98,9 +89,8 @@ Retention:
|
|||
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.
|
||||
- Approvals are kept indefinitely — an audit trail. `actions::destroy`
|
||||
rows stay visible to anything that queries by id.
|
||||
- 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
|
||||
|
|
@ -301,9 +291,9 @@ Under `/var/lib/hyperhive/agents/<name>/`:
|
|||
hours; non-terminal (still-running) tasks are never deleted. This
|
||||
used to be a host-side `hive-c0re` vacuum, moved in-container for
|
||||
the same privsep-ownership reason as the events vacuum above.
|
||||
- `hyperhive-state.sqlite` — consolidated loose-ends-v2 store: todos,
|
||||
reminders, and a questions mirror, one small table each in a single
|
||||
file (in-container daemons — `hive-bash-daemon`, `hive-matrix-daemon`,
|
||||
- `hyperhive-state.sqlite` — consolidated loose-ends-v2 store: todos
|
||||
and reminders, one small table each in a single file (in-container
|
||||
daemons — `hive-bash-daemon`, `hive-matrix-daemon`,
|
||||
`hive-forge-notify` — upsert keyed todos here over the harness's
|
||||
in-agent socket, `HIVE_AGENT_SOCKET`; the harness merges them into
|
||||
`get_loose_ends` output and clears a row on `mark_todo_done`).
|
||||
|
|
|
|||
|
|
@ -180,8 +180,6 @@ tools (Read, Write, etc.) keep their name as-is.
|
|||
| **Core hyperhive** | |
|
||||
| `send*` | rich renderer: `send* → to · NL` (default-open body) |
|
||||
| `recv*` | `recv*()` · `recv* wait Ns` · `recv* max N` |
|
||||
| `ask*` | rich renderer: `ask* → to` (no inline answer form — see [Inline ask-operator answer](#inline-ask-operator-answer)) |
|
||||
| `answer*` | rich renderer: `answer* #id` |
|
||||
| `remind*` | `remind* +Xm "preview"` or `remind* at HH:MMZ "preview"` |
|
||||
| `set_status*` | `set_status* "text"` |
|
||||
| `get_loose_ends*` | `get_loose_ends*()` or `get_loose_ends* [agent]` |
|
||||
|
|
@ -242,15 +240,6 @@ This keeps less-frequent tools that don't have a specific
|
|||
`fmt_tool_use` case from dumping raw JSON. Common matrix and
|
||||
hyperhive tools have their own cases and skip this path.
|
||||
|
||||
## Inline ask-operator answer
|
||||
|
||||
An `mcp__hyperhive__ask(to: "operator", ...)` row has no inline
|
||||
answer form in this terminal — it renders like any other tool call.
|
||||
The dashboard's own question-surfacing UI that used to let the
|
||||
operator answer from the Y3R C4LL tab is gone (hyperhive#3721); there
|
||||
is currently no UI path to answer an operator-targeted `ask()` at
|
||||
all.
|
||||
|
||||
## Dashboard side (not covered here)
|
||||
|
||||
The main dashboard's message-flow pane is a different
|
||||
|
|
|
|||
|
|
@ -170,14 +170,11 @@ from the harness-local store (same effect as `cancel_loose_end(kind:
|
|||
"todo")`, just from the web UI instead of the agent's own tool calls).
|
||||
|
||||
The todos flyout is the only per-agent flyout — there is no separate
|
||||
"loose-ends" or "tasks" list. There is also no inline answer form for
|
||||
`ask` tool calls in this terminal: an `ask` renders like any other
|
||||
tool call (see
|
||||
[`terminal-rendering.md`](../terminal-rendering.md#inline-ask-operator-answer)).
|
||||
The dashboard's own Y3R C4LL questions pane that used to let the
|
||||
operator answer from there is gone (hyperhive#3721) — an
|
||||
operator-targeted `ask()` currently has no UI path to an answer at
|
||||
all until the removal finishes or a replacement lands.
|
||||
"loose-ends" or "tasks" list. There used to be a note here about the
|
||||
`ask`/`answer` MCP tools having no inline answer form in this
|
||||
terminal — that whole mechanism (the tools, the dashboard's questions
|
||||
pane, the wire protocol) has since been removed entirely, so there's
|
||||
nothing left to render a form for.
|
||||
|
||||
## Live view
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue