Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
16ac84ca63 | ||
|
|
2850270829 |
31 changed files with 213 additions and 899 deletions
|
|
@ -32,7 +32,7 @@ hand-maintained per-file tree drifts out of sync with the code.
|
|||
user). `src/main.rs` is the `hive-c0re` binary — **daemon-only**
|
||||
(`serve` + the periodic vacuum/sweep loops); the operator CLI lives in
|
||||
the separate `hivectl` crate, which talks to the daemon over the host
|
||||
admin socket. Owns the sqlite broker, approval + question + reminder +
|
||||
admin socket. Owns the sqlite broker, approval + reminder +
|
||||
schedule queues, the meta flake, lifecycle (`nixos-container`
|
||||
shellouts), gateway / forge / matrix provisioning, per-container stats,
|
||||
and the axum operator dashboard (`dashboard.rs`). Largest crate.
|
||||
|
|
@ -120,7 +120,7 @@ hand-maintained per-file tree drifts out of sync with the code.
|
|||
`forge_notify` are the built-in ones, but any user-configured MCP server
|
||||
can push todos here too, nothing restricts the `subsystem` set. Carries
|
||||
the loose-ends-v2 **todo** ops plus
|
||||
harness-local reminders + questions. ⚠️ Distinct from
|
||||
harness-local reminders. ⚠️ Distinct from
|
||||
`hive-core-agent-sock` above: **this socket never leaves the container**
|
||||
and `hive-c0re` is not in the path at all — no broker round-trip, no
|
||||
long-poll, no marker files.
|
||||
|
|
|
|||
|
|
@ -11,8 +11,7 @@ durable identity, and stop them from eating production. hyperhive is
|
|||
the substrate.
|
||||
|
||||
- identity = unix socket
|
||||
- communication = sqlite-backed broker (`send` / `recv` / `ask` /
|
||||
`answer` / `remind`)
|
||||
- communication = sqlite-backed broker (`send` / `recv` / `remind`)
|
||||
- config = git (manager proposes, operator approves, deploys land as
|
||||
tagged commits)
|
||||
- blast radius = container
|
||||
|
|
|
|||
|
|
@ -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,17 +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
|
||||
|
|
@ -668,11 +651,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
|
||||
|
|
|
|||
|
|
@ -23,8 +23,7 @@ preset (`AGENT_DEFAULT`) includes `messaging`, `meta`, `inbox`, and
|
|||
## Core tools (always available)
|
||||
|
||||
**Messaging** (`messaging` group): `send(to, body, in_reply_to?)`,
|
||||
`recv(max?)`, `ask(question, options?, multi?,
|
||||
ttl_seconds?, to?)`, `answer(id, answer)`, `ack_until(up_to)`.
|
||||
`recv(max?)`, `ack_until(up_to)`.
|
||||
|
||||
- `send` — message a peer (logical name) or the operator
|
||||
(`to: "operator"`). Use `to: "<parent>"` to address the topology
|
||||
|
|
@ -48,13 +47,6 @@ ttl_seconds?, to?)`, `answer(id, answer)`, `ack_until(up_to)`.
|
|||
other turn: if the context crossed the watermark the harness runs a
|
||||
notes-checkpoint turn and then `/compact`. Compacting before shutdown
|
||||
keeps a later cold start cheap instead of re-uploading a huge transcript.
|
||||
- `ask` — surface a structured question to the operator (default) or
|
||||
a peer agent (`to: "<agent>"`). Non-blocking — returns a question
|
||||
id; the answer arrives as a `question_answered` system event in the
|
||||
asker's inbox. `options` is advisory; `multi=true` renders as
|
||||
checkboxes; `ttl_seconds` auto-cancels with answer `[expired]`.
|
||||
- `answer` — respond to a `question_asked` event routed to this
|
||||
agent. Strict authorisation: only the declared target can answer.
|
||||
- `ack_until(up_to)` — bulk-mark inbox rows handled: every row with
|
||||
broker id `<= up_to` is stamped as acked in a single UPDATE.
|
||||
Recipient-scoped (agents can only ack their own rows). Use when a
|
||||
|
|
@ -64,14 +56,12 @@ ttl_seconds?, to?)`, `answer(id, answer)`, `ack_until(up_to)`.
|
|||
Transient pings (sentinel id 0) have nothing to ack and show no marker.
|
||||
|
||||
**System messages** (from sender `system`): the higher-urgency
|
||||
lifecycle + Q&A events (`hive_sh4re::manager::HelperEvent`) are
|
||||
lifecycle events (`hive_sh4re::manager::HelperEvent`) are
|
||||
delivered as regular inbox messages (same `recv` path; body is a JSON
|
||||
object with an `event` discriminant field). The **submitting agent**
|
||||
(the root agent for top-level containers; an agent with the `approvals`
|
||||
tool group for its own subtree) receives `container_crash`,
|
||||
`needs_update`, and `approval_resolved` this way. Any agent receives
|
||||
Q&A events when it is the declared target (`question_asked`) or the
|
||||
asker (`question_answered`). The remaining, lower-urgency lifecycle
|
||||
`needs_update`, and `approval_resolved` this way. The remaining, lower-urgency lifecycle
|
||||
notices — `spawned`, `rebuilt`, `killed`, `destroyed`, `needs_login`,
|
||||
`logged_in`, `config_ready` — skip the inbox entirely: they land as
|
||||
todos on the submitting agent's in-container todo socket instead
|
||||
|
|
@ -86,18 +76,19 @@ payload shapes and routing logic in
|
|||
`cancel_loose_end(kind, id)`, `remind(message, delay_seconds? |
|
||||
at_unix_timestamp?)`.
|
||||
|
||||
- `get_loose_ends(agent?)` — list pending questions (asked/owed),
|
||||
scheduled reminders, and active local tasks published by external MCP
|
||||
daemons (e.g. running bash tasks from `hive-bash-daemon`). Each row
|
||||
carries an id + kind for `cancel_loose_end`. Omit `agent` to list
|
||||
your own threads. Pass `agent: "<name>"` to inspect a direct child
|
||||
agent (always accessible per topology enforcement); non-children
|
||||
require the `query_agent_state` capability. The `"*"` hive-wide
|
||||
query is not available on the agent socket.
|
||||
- `cancel_loose_end` — withdraw a `question` (posts `[cancelled by
|
||||
<self>]`), hard-delete a `reminder`, or cancel a pending `approval`
|
||||
row. Agents may only cancel rows they own; the `approval` kind is
|
||||
further restricted to the root agent (`ruth`) server-side.
|
||||
- `get_loose_ends(agent?)` — list scheduled reminders, pending
|
||||
approvals you submitted, and active local tasks published by
|
||||
external MCP daemons (e.g. running bash tasks from
|
||||
`hive-bash-daemon`). Each row carries an id + kind for
|
||||
`cancel_loose_end`. Omit `agent` to list your own threads. Pass
|
||||
`agent: "<name>"` to inspect a direct child agent (always accessible
|
||||
per topology enforcement); non-children require the
|
||||
`query_agent_state` capability. The `"*"` hive-wide query is not
|
||||
available on the agent socket.
|
||||
- `cancel_loose_end` — hard-delete a `reminder`, cancel a pending
|
||||
`approval` row, or clear a `todo` row (loose-ends-v2). Agents may
|
||||
only cancel rows they own; the `approval` kind is further restricted
|
||||
to the root agent (`ruth`) server-side.
|
||||
- `remind` — schedule a reminder in this agent's own inbox. Large
|
||||
payloads spill to `/agents/<self>/state/reminders/`. Pending count
|
||||
capped at 50 per agent (`HIVE_REMIND_MAX_PENDING_PER_AGENT`).
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ would hit. HTTP is the sole transport; there is no stdio mode here.
|
|||
## When to use it
|
||||
|
||||
This is where the core hyperhive tool surface lives: `send`, `recv`,
|
||||
`ask`/`answer`, `remind`, `get_loose_ends`, `set_status`,
|
||||
`remind`, `get_loose_ends`, `set_status`,
|
||||
`get_agent_meta`, lifecycle (`kill`/`start`/`restart`/`update` on
|
||||
direct children), scheduling, and the approval-request tools. Reach
|
||||
for this crate when you're adding or changing a built-in tool rather
|
||||
|
|
@ -25,7 +25,7 @@ or their own daemon instead of living here.
|
|||
in-agent socket the `hive-agent` harness serves.
|
||||
- **`client.rs`** — socket client to the hyperhive broker.
|
||||
- **`send_allow.rs`** — enforces the per-agent
|
||||
`hyperhive.allowedRecipients` allow-list on `send`/`ask`.
|
||||
`hyperhive.allowedRecipients` allow-list on `send`.
|
||||
- **`paths.rs`** — socket + state path resolution shared with the
|
||||
harness's own `paths.rs` conventions.
|
||||
|
||||
|
|
|
|||
|
|
@ -159,14 +159,13 @@ pub struct UpdateArgs {
|
|||
|
||||
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
|
||||
pub struct CancelLooseEndArgs {
|
||||
/// Which kind of thread to cancel — `"question"` for an open
|
||||
/// `ask` that's still waiting on an answer, `"reminder"` for a
|
||||
/// scheduled `remind` that hasn't fired yet, or `"todo"` for a
|
||||
/// loose-ends-v2 todo (bash/matrix/forge). Use the `kind`
|
||||
/// field straight off the `get_loose_ends` row.
|
||||
/// Which kind of thread to cancel — `"reminder"` for a scheduled
|
||||
/// `remind` that hasn't fired yet, `"approval"` for a pending
|
||||
/// approval you submitted, or `"todo"` for a loose-ends-v2 todo
|
||||
/// (bash/matrix/forge). Use the `kind` field straight off the
|
||||
/// `get_loose_ends` row.
|
||||
pub kind: String,
|
||||
/// Row id from the matching `get_loose_ends` entry (or the
|
||||
/// `question_queued` reply when you submitted it).
|
||||
/// Row id from the matching `get_loose_ends` entry.
|
||||
pub id: i64,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -152,7 +152,7 @@ fn msg_id_tag(id: i64) -> String {
|
|||
}
|
||||
|
||||
/// Hard cap on how many individual `Todo` lines `render_loose_ends` will
|
||||
/// emit. Approvals/questions/reminders stay naturally bounded (they're
|
||||
/// emit. Approvals/reminders stay naturally bounded (they're
|
||||
/// triaged interactively and don't self-multiply), but todos are pushed by
|
||||
/// unattended producers — matrix/bash/forge are the built-in ones, but any
|
||||
/// user-configured MCP server can push its own too — an agent that goes a long
|
||||
|
|
@ -194,19 +194,6 @@ fn render_one_loose_end(out: &mut String, t: &hive_sh4re::inbox::LooseEnd) {
|
|||
"- approval #{id} ({agent} @ {commit_ref}, {age_seconds}s old){desc}"
|
||||
);
|
||||
}
|
||||
hive_sh4re::inbox::LooseEnd::Question {
|
||||
id,
|
||||
asker,
|
||||
target,
|
||||
question,
|
||||
age_seconds,
|
||||
} => {
|
||||
let to = target.as_deref().unwrap_or("operator");
|
||||
let _ = writeln!(
|
||||
out,
|
||||
"- question #{id} ({asker} → {to}, {age_seconds}s old): {question}"
|
||||
);
|
||||
}
|
||||
hive_sh4re::inbox::LooseEnd::Reminder {
|
||||
id,
|
||||
owner,
|
||||
|
|
@ -459,23 +446,20 @@ pub(super) fn parse_loose_end_kind(
|
|||
raw: &str,
|
||||
) -> Result<hive_sh4re::inbox::CancelLooseEndKind, String> {
|
||||
match raw.trim().to_ascii_lowercase().as_str() {
|
||||
"question" | "q" => Ok(hive_sh4re::inbox::CancelLooseEndKind::Question),
|
||||
"reminder" | "r" => Ok(hive_sh4re::inbox::CancelLooseEndKind::Reminder),
|
||||
"approval" | "a" => Ok(hive_sh4re::inbox::CancelLooseEndKind::Approval),
|
||||
other => Err(format!(
|
||||
"cancel_loose_end: unknown kind '{other}' \
|
||||
(expected \"question\", \"reminder\", \"approval\", or \"todo\")"
|
||||
(expected \"reminder\", \"approval\", or \"todo\")"
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Canonical user-facing label for a `CancelLooseEndKind` — used in
|
||||
/// the success ack so the caller always sees `"question"` /
|
||||
/// `"reminder"` instead of whatever alias they passed in (`"q"` /
|
||||
/// `"r"`).
|
||||
/// the success ack so the caller always sees `"reminder"` instead of
|
||||
/// whatever alias they passed in (`"r"`).
|
||||
pub(super) fn loose_end_kind_label(kind: hive_sh4re::inbox::CancelLooseEndKind) -> &'static str {
|
||||
match kind {
|
||||
hive_sh4re::inbox::CancelLooseEndKind::Question => "question",
|
||||
hive_sh4re::inbox::CancelLooseEndKind::Reminder => "reminder",
|
||||
hive_sh4re::inbox::CancelLooseEndKind::Approval => "approval",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ You are hyperhive agent `{label}` (qualified: `{qualified_label}`){hive_identity
|
|||
|
||||
Tools (hyperhive surface). Full signature + behavior for each comes from the tool's own MCP description (you already received it via the MCP tool schema) — this is just the map of what exists and which ones are gated, so you know where to look:
|
||||
|
||||
- **Inbox / messaging** (always available): `mcp__hyperhive__recv`, `mcp__hyperhive__ack_until`, `mcp__hyperhive__send`, `mcp__hyperhive__ask`, `mcp__hyperhive__answer`, `mcp__hyperhive__get_loose_ends`, `mcp__hyperhive__cancel_loose_end`, `mcp__hyperhive__mark_todos_done`, `mcp__hyperhive__remind`, `mcp__hyperhive__set_status`, `mcp__hyperhive__get_agent_meta`. Two habits worth internalizing beyond the tool descriptions themselves: prefer ending the turn over repeatedly polling `recv` when idle (only turn-boundaries observe in-container todo wakes — bash-task completions, matrix unread, forge activity — and ending the turn is also your checkpoint); and `ask`/`answer` are async — `ask` returns immediately with a question id, the reply lands later as a `question_answered` system event, never block a turn waiting on it inline. For a large todo backlog (`get_loose_ends` caps at 40 rows), clear reviewed ids in bulk with `mark_todos_done` rather than cancelling one at a time — there's no blind range-clear, only ids you've actually looked at.
|
||||
- **Inbox / messaging** (always available): `mcp__hyperhive__recv`, `mcp__hyperhive__ack_until`, `mcp__hyperhive__send`, `mcp__hyperhive__get_loose_ends`, `mcp__hyperhive__cancel_loose_end`, `mcp__hyperhive__mark_todos_done`, `mcp__hyperhive__remind`, `mcp__hyperhive__set_status`, `mcp__hyperhive__get_agent_meta`. One habit worth internalizing beyond the tool descriptions themselves: prefer ending the turn over repeatedly polling `recv` when idle (only turn-boundaries observe in-container todo wakes — bash-task completions, matrix unread, forge activity — and ending the turn is also your checkpoint). For a large todo backlog (`get_loose_ends` caps at 40 rows), clear reviewed ids in bulk with `mark_todos_done` rather than cancelling one at a time — there's no blind range-clear, only ids you've actually looked at.
|
||||
- **Extra MCP tools** (some agents only): `mcp__<server>__<tool>` — agent-specific (matrix client, scraper, db connector, etc.) declared in your `agent.nix` under `hyperhive.extraMcpServers`. First-class tools, already operator-approved at deploy time.
|
||||
- **Lifecycle** (_requires `lifecycle` tool group_, direct children only, no approval needed): `restart`, `kill`, `start`, `update`, `list_containers`.
|
||||
- **Approvals** (_requires `approvals` tool group_, queues an operator approval): `request_init_config`, `request_apply_commit`, `request_update_meta_inputs`.
|
||||
|
|
@ -15,11 +15,11 @@ Your config repo is mounted **read-only** at `/agents/{label}/config/` — `agen
|
|||
|
||||
Approval boundary: lifecycle ops on _existing_ direct children (`kill`, `start`, `restart`) are at your discretion — no operator approval needed (requires `lifecycle` tool group). _Creating_ a new agent (two-step: `request_init_config` + `request_apply_commit`) and _changing_ any agent's config (`request_apply_commit`) both go through the approval queue (requires `approvals` tool group). The operator only signs off on changes; you run the day-to-day.
|
||||
|
||||
Messages from sender `system` are hyperhive helper events (JSON body, `event` field discriminates): `approval_resolved`, `container_crash`, `needs_update`, `question_asked`, `question_answered`. Use these to react to lifecycle changes:
|
||||
Messages from sender `system` are hyperhive helper events (JSON body, `event` field discriminates): `approval_resolved`, `container_crash`, `needs_update`. Use these to react to lifecycle changes:
|
||||
|
||||
- `needs_update` — agent's flake rev is stale. Call `update(name)` to rebuild — it's idempotent and doesn't need approval.
|
||||
- `container_crash` — restart with `start(name)`. If it crashes again, ask the operator.
|
||||
- otherwise pick up answers to questions you asked.
|
||||
- `approval_resolved` — one of your own submitted approvals (`request_init_config`, `request_apply_commit`, `request_update_meta_inputs`, a scheduled prompt, …) was approved, denied, or failed; the body carries the resolution.
|
||||
|
||||
Lifecycle notices that don't need an immediate turn — a new agent spawned, its config repo seeded, a container rebuilt/killed/destroyed, or its login state changing — surface as todos instead of messages now. Call `get_loose_ends` to see them.
|
||||
|
||||
|
|
|
|||
|
|
@ -104,9 +104,9 @@ async fn main() -> Result<()> {
|
|||
// ---------- shared turn helpers ----------
|
||||
|
||||
/// Surface a `SYSTEM_SENDER` message in the live event bus + tracing
|
||||
/// log. Both agents and the manager receive `QuestionAnswered`,
|
||||
/// `ContainerCrash`, reparent notifications, and friends; the parse
|
||||
/// and log path is identical. Quiet no-op when `from` isn't
|
||||
/// log. Both agents and the manager receive `ContainerCrash`,
|
||||
/// reparent notifications, and friends; the parse and log path is
|
||||
/// identical. Quiet no-op when `from` isn't
|
||||
/// `SYSTEM_SENDER`.
|
||||
fn log_system_event(bus: &Bus, from: &str, body: &str) {
|
||||
if from != SYSTEM_SENDER {
|
||||
|
|
|
|||
|
|
@ -255,7 +255,7 @@ shared closer
|
|||
assert!(!rendered.contains("<!-- /role:"));
|
||||
// Shared tools appear.
|
||||
assert!(rendered.contains("mcp__hyperhive__recv"));
|
||||
assert!(rendered.contains("mcp__hyperhive__ask"));
|
||||
assert!(rendered.contains("mcp__hyperhive__send"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ const REMINDER_BATCH_LIMIT: u64 = 100;
|
|||
/// Poll interval — matches the old c0re `POLL_INTERVAL`.
|
||||
const POLL_INTERVAL: Duration = Duration::from_secs(5);
|
||||
|
||||
/// Same cap the broker used to enforce on `send`/`ask`/`remind` bodies
|
||||
/// Same cap the broker enforces on `send`/`remind` bodies
|
||||
/// (`hive-c0re`'s `agent_config::limits::MESSAGE_MAX_BYTES`, not
|
||||
/// reachable from here — hive-agent doesn't depend on hive-c0re).
|
||||
/// Duplicated rather than shared: this is the last remaining reminder
|
||||
|
|
|
|||
|
|
@ -254,14 +254,7 @@ fn enrich_tool_use_entry(entry: &mut Value) {
|
|||
/// `_category: "rich"` so the client can distinguish without re-implementing
|
||||
/// the tool name list.
|
||||
fn is_rich_tool(name: &str) -> bool {
|
||||
matches!(
|
||||
name,
|
||||
"Edit"
|
||||
| "mcp__bash__run"
|
||||
| "mcp__hyperhive__send"
|
||||
| "mcp__hyperhive__ask"
|
||||
| "mcp__hyperhive__answer"
|
||||
)
|
||||
matches!(name, "Edit" | "mcp__bash__run" | "mcp__hyperhive__send")
|
||||
}
|
||||
|
||||
/// Pre-compute the expandable body for rich tool entries.
|
||||
|
|
@ -271,7 +264,7 @@ fn is_rich_tool(name: &str) -> bool {
|
|||
/// - `"diff"` → `api.detailsDiff` (colour-coded `+`/`-` lines)
|
||||
/// - `"plain"` → `api.details` (plain `<pre>` block)
|
||||
/// - `"markdown"` → `api.detailsOpenMd` (markdown rendered via marked + `DOMPurify`,
|
||||
/// default-open; used for message-bearing tools: send, ask, answer)
|
||||
/// default-open; used for message-bearing tools: `send`)
|
||||
///
|
||||
/// Returns `None` for tools that have no body at all.
|
||||
///
|
||||
|
|
@ -318,9 +311,7 @@ fn rich_tool_body(name: &str, input: &Value) -> Option<(String, &'static str)> {
|
|||
Some((format!("$ {cmd}"), "plain"))
|
||||
}
|
||||
}
|
||||
// Message-bearing tools: body is markdown text rendered by the client.
|
||||
// The ask form (operator reply slot) is still mounted client-side;
|
||||
// only the raw body text moves to the backend here.
|
||||
// Message-bearing tool: body is markdown text rendered by the client.
|
||||
"mcp__hyperhive__send" => {
|
||||
let body = input.get("body").and_then(Value::as_str).unwrap_or("");
|
||||
if body.is_empty() {
|
||||
|
|
@ -329,22 +320,6 @@ fn rich_tool_body(name: &str, input: &Value) -> Option<(String, &'static str)> {
|
|||
Some((body.to_owned(), "markdown"))
|
||||
}
|
||||
}
|
||||
"mcp__hyperhive__ask" => {
|
||||
let q = input.get("question").and_then(Value::as_str).unwrap_or("");
|
||||
if q.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some((q.to_owned(), "markdown"))
|
||||
}
|
||||
}
|
||||
"mcp__hyperhive__answer" => {
|
||||
let a = input.get("answer").and_then(Value::as_str).unwrap_or("");
|
||||
if a.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some((a.to_owned(), "markdown"))
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
|
@ -354,8 +329,6 @@ fn tool_icon(name: &str) -> &'static str {
|
|||
match name {
|
||||
"mcp__hyperhive__send" => "📤",
|
||||
"mcp__hyperhive__recv" => "📥",
|
||||
"mcp__hyperhive__ask" => "❓",
|
||||
"mcp__hyperhive__answer" => "✍️",
|
||||
"mcp__hyperhive__remind" => "⏰",
|
||||
"mcp__hyperhive__set_status" => "🏷️",
|
||||
"mcp__hyperhive__get_loose_ends" => "🪢",
|
||||
|
|
@ -470,10 +443,10 @@ fn fmt_builtin_tool(name: &str, short: &str, input: &Value) -> String {
|
|||
}
|
||||
}
|
||||
|
||||
/// Summary for message-bearing hyperhive tools (send / ask / answer).
|
||||
/// Summary for `send`, the one message-bearing hyperhive tool.
|
||||
///
|
||||
/// Format: `"{short} → {recipient}"` or `"{short} #{id}"` with ` · NL`
|
||||
/// appended when the body spans multiple lines.
|
||||
/// Format: `"{short} → {recipient}"` with ` · NL` appended when the body
|
||||
/// spans multiple lines.
|
||||
fn fmt_hyperhive_message_tool(name: &str, short: &str, input: &Value) -> String {
|
||||
match name {
|
||||
"mcp__hyperhive__send" => {
|
||||
|
|
@ -485,30 +458,6 @@ fn fmt_hyperhive_message_tool(name: &str, short: &str, input: &Value) -> String
|
|||
format!("{short} → {to}")
|
||||
}
|
||||
}
|
||||
"mcp__hyperhive__ask" => {
|
||||
let to = input
|
||||
.get("to")
|
||||
.and_then(Value::as_str)
|
||||
.unwrap_or("operator");
|
||||
let lines = sv(input, "question").lines().count();
|
||||
if lines > 1 {
|
||||
format!("{short} → {to} · {lines}L")
|
||||
} else {
|
||||
format!("{short} → {to}")
|
||||
}
|
||||
}
|
||||
"mcp__hyperhive__answer" => {
|
||||
let id = input
|
||||
.get("id")
|
||||
.and_then(Value::as_u64)
|
||||
.map_or_else(|| "?".to_owned(), |n| n.to_string());
|
||||
let lines = sv(input, "answer").lines().count();
|
||||
if lines > 1 {
|
||||
format!("{short} #{id} · {lines}L")
|
||||
} else {
|
||||
format!("{short} #{id}")
|
||||
}
|
||||
}
|
||||
_ => fmt_args_generic(short, input),
|
||||
}
|
||||
}
|
||||
|
|
@ -516,9 +465,7 @@ fn fmt_hyperhive_message_tool(name: &str, short: &str, input: &Value) -> String
|
|||
/// `mcp__hyperhive__*` tools.
|
||||
fn fmt_hyperhive_tool(name: &str, short: &str, input: &Value) -> String {
|
||||
match name {
|
||||
"mcp__hyperhive__send" | "mcp__hyperhive__ask" | "mcp__hyperhive__answer" => {
|
||||
fmt_hyperhive_message_tool(name, short, input)
|
||||
}
|
||||
"mcp__hyperhive__send" => fmt_hyperhive_message_tool(name, short, input),
|
||||
"mcp__hyperhive__recv" => {
|
||||
let mut parts = Vec::new();
|
||||
if let Some(w) = input.get("wait_seconds").and_then(Value::as_u64) {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# hive-c0re
|
||||
|
||||
The unprivileged host daemon (runs as `hive-core`). Owns the sqlite
|
||||
broker, the approval/question/schedule queues, the generic job-DAG
|
||||
broker, the approval/reminder/schedule queues, the generic job-DAG
|
||||
queue, container lifecycle, gateway/forge/matrix provisioning,
|
||||
per-container stats, and the axum operator dashboard. Largest crate in
|
||||
the workspace — bin-only, no separate lib.
|
||||
|
|
@ -24,7 +24,7 @@ module's own `//!` doc-comment for real detail, don't expect this file
|
|||
to track it:
|
||||
|
||||
- **`dashboard/`** — the operator dashboard (containers, approvals,
|
||||
schedules, questions, logs, topology).
|
||||
schedules, logs, topology).
|
||||
- **`job_queue/`** — the job-DAG queue + desired-state reconciliation
|
||||
(`docs/coordinator.md`).
|
||||
- **`lifecycle/`** — `nixos-container` lifecycle + per-agent config
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@
|
|||
//! about it — oversized reminder bodies get persisted to disk
|
||||
//! transparently and the inbox sees a pointer.
|
||||
|
||||
/// Per-message body cap. Applies to `send`, `ask` question text,
|
||||
/// `answer` body, and the stored inline form of a reminder. 4 KiB
|
||||
/// Per-message body cap. Applies to `send` bodies and the stored
|
||||
/// inline form of a reminder. 4 KiB
|
||||
/// catches the bulk of conversational overflow (status reports,
|
||||
/// bullet-list summaries, short proposals) while staying small
|
||||
/// enough that a backed-up inbox of ~10 unread messages only adds
|
||||
|
|
@ -112,8 +112,8 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn label_threads_through() {
|
||||
let err = check_size("question", &"x".repeat(MESSAGE_MAX_BYTES + 1)).unwrap_err();
|
||||
assert!(err.starts_with("question body too long"));
|
||||
let err = check_size("broadcast", &"x".repeat(MESSAGE_MAX_BYTES + 1)).unwrap_err();
|
||||
assert!(err.starts_with("broadcast body too long"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ use crate::broker::Broker;
|
|||
use crate::container_view::{self, ContainerView};
|
||||
use crate::dashboard_events::DashboardEvent;
|
||||
use crate::job_queue::RunningTransient;
|
||||
use crate::operator_questions::OperatorQuestions;
|
||||
use crate::socket_server::{self, AgentSocket};
|
||||
|
||||
/// Capacity of the dashboard event channel. Slow browser subscribers
|
||||
|
|
@ -32,7 +31,6 @@ const LAST_STOPPED_RUNNING_KEY: &str = "last_stopped_running";
|
|||
pub struct Coordinator {
|
||||
pub broker: Arc<Broker>,
|
||||
pub approvals: Arc<Approvals>,
|
||||
pub questions: Arc<OperatorQuestions>,
|
||||
/// Scheduled-prompts queue. One sqlite connection,
|
||||
/// internal mutex; the worker drains due rows and the manager
|
||||
/// handlers insert / cancel through the same handle.
|
||||
|
|
@ -467,7 +465,6 @@ impl Coordinator {
|
|||
} = env;
|
||||
let broker = Broker::open(db_path).context("open broker")?;
|
||||
let approvals = Approvals::open(db_path).context("open approvals")?;
|
||||
let questions = OperatorQuestions::open(db_path).context("open operator_questions")?;
|
||||
let scheduled_prompts = crate::scheduled_prompts::ScheduledPrompts::open(db_path)
|
||||
.context("open scheduled_prompts")?;
|
||||
// BuildLogs wants a directory (it picks its own `build_logs.sqlite`
|
||||
|
|
@ -496,7 +493,6 @@ impl Coordinator {
|
|||
Ok(Self {
|
||||
broker: Arc::new(broker),
|
||||
approvals: Arc::new(approvals),
|
||||
questions: Arc::new(questions),
|
||||
scheduled_prompts: Arc::new(scheduled_prompts),
|
||||
build_logs,
|
||||
audit_log,
|
||||
|
|
@ -971,9 +967,8 @@ impl Coordinator {
|
|||
std::fs::create_dir_all(&agent_dir)
|
||||
.with_context(|| format!("create agent dir {}", agent_dir.display()))?;
|
||||
let socket_path = Self::socket_path(name);
|
||||
// Hand the full Coordinator to the per-agent socket — it
|
||||
// needs broker + operator_questions to handle the agent-side
|
||||
// `ask` / `answer` tools, not just the broker.
|
||||
// Hand the full Coordinator to the per-agent socket — it needs
|
||||
// more than just the broker (approvals, scheduled_prompts, ...).
|
||||
let socket = socket_server::start(name, &socket_path, self.clone())?;
|
||||
self.agents.lock().unwrap().insert(name.to_owned(), socket);
|
||||
Ok(agent_dir)
|
||||
|
|
@ -1336,17 +1331,15 @@ impl Coordinator {
|
|||
|
||||
/// Push a `HelperEvent` into an arbitrary agent's inbox. Encoded
|
||||
/// the same way as `notify_manager` (sender = `SYSTEM_SENDER`,
|
||||
/// body = JSON-encoded event). Used to route `QuestionAnswered`
|
||||
/// events back to the agent that called `ask`, `QuestionAsked`
|
||||
/// events to the target of a peer question, etc.
|
||||
/// body = JSON-encoded event) — e.g. `ContainerCrash`, `NeedsUpdate`.
|
||||
pub fn notify_agent(&self, agent: &str, event: &hive_sh4re::manager::HelperEvent) {
|
||||
self.notify_agent_from(hive_sh4re::manager::SYSTEM_SENDER, agent, event);
|
||||
}
|
||||
|
||||
/// Same as `notify_agent` but with an explicit sender. Use this
|
||||
/// when the event originates from a known agent or the operator
|
||||
/// (e.g. `QuestionAnswered` — the answerer should be the `from`,
|
||||
/// not `system`) so the recipient's terminal shows the right name.
|
||||
/// rather than the system itself, so the recipient's terminal
|
||||
/// shows the right name instead of `system`.
|
||||
pub fn notify_agent_from(
|
||||
&self,
|
||||
from: &str,
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue