remove the list_containers and request_update_meta_inputs MCP tools
Both agent-facing tools go away end to end, with no replacement. This is an intentional capability removal: agents can no longer enumerate their own subtree, and can no longer queue a meta-flake input bump. The system prompt and docs/tools/lifecycle.md land in this same commit on purpose. A tool named in the prompt but absent from the server makes agents confidently call something that doesn't exist, and the failure then surfaces far from its cause. Removed: - MCP registrations and bodies (hive-agent-mcp), plus the now-unused UpdateMetaInputsArgs. - Wire variants Request::ListDescendants, Request::RequestUpdateMetaInputs and Response::Containers, plus ContainerInfo, whose only consumer was that response. - hive-c0re's handle_list_descendants (its whole module) and handle_request_update_meta_inputs, the two dispatch arms, and the require_group(agent, "approvals", ...) gate on the meta-inputs verb. - The stream_enrich emoji entry and argument formatter. - docs/tools/lifecycle.md (both tools it documented are gone), its two referrers, the tool-group tables and the agent-hierarchy prose. Tool groups are kept, deliberately. ToolGroup::Lifecycle listed exactly one tool and now lists none — it is vestigial, but the variant stays so existing meta/capabilities.json grants still parse; retiring it is a separate decision. ToolGroup::Approvals also listed exactly one tool, but the group is NOT dead: check_can_cancel_approval still gates cancel_loose_end's approval-cancel arm on it server-side. ApprovalKind::UpdateMetaInputs stays too. Nothing in production code produces it any more, but pre-existing approval rows may still carry it, and the operator's own path to a meta update is unaffected — the dashboard's POST /api/meta-update inserts the meta_update job directly, bypassing approvals entirely. The two format_ack tests in hive-agent-mcp that named request_update_meta_inputs were only using it as a label string while exercising the generic OkWarn/Ok renderer, so they are retargeted to a surviving tool rather than deleted. Note hive-c0re's priv_client::list_containers is a different thing (the host-side privileged container listing behind hive-priv) and is untouched. Closes #4591
This commit is contained in:
parent
8614cb2613
commit
b88a5b2430
18 changed files with 74 additions and 356 deletions
|
|
@ -96,7 +96,6 @@ umount-old / mount-new / restart-cascade step.
|
|||
| config change via forge PR (any descendant's config) | 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`) |
|
||||
|
||||
"Ancestor" walks `ContainerView.parent` chains; a visited-set guards against
|
||||
cycles at dispatch time (a malformed `topology.json` can't lock
|
||||
|
|
@ -116,14 +115,11 @@ other agents don't:
|
|||
approval step — every other agent goes through a `Spawn` approval.
|
||||
Topology-wise, `ruth` is still just another root agent.
|
||||
- **Wire-protocol** — the privileged `Request` variants
|
||||
(`Kill` / `Start` / `Restart` / `Update`;
|
||||
`GetLogs`; `RequestUpdateMetaInputs`) — marked `*(privileged)*` in
|
||||
`hive-core-agent-sock`'s unified `Request` enum — are reachable only
|
||||
from the manager's socket flavour today. Planned rule for each is in the
|
||||
table above ("any ancestor" for lifecycle/logs);
|
||||
`RequestUpdateMetaInputs` stays
|
||||
a root-only capability even post-milestone, not a topology rule.
|
||||
One exception: `Wake` (inject a `from: <X>` message into the
|
||||
(`Kill` / `Start` / `Restart` / `Update`; `GetLogs`) — marked
|
||||
`*(privileged)*` in `hive-core-agent-sock`'s unified `Request` enum —
|
||||
are reachable only from the manager's socket flavour today. Planned
|
||||
rule for each is in the table above ("any ancestor" for
|
||||
lifecycle/logs). One exception: `Wake` (inject a `from: <X>` message into the
|
||||
caller's own inbox) isn't really privileged — every per-agent daemon
|
||||
(for example `hive-forge-notify`) needs it, and sub-agents already have the
|
||||
equivalent on their own socket.
|
||||
|
|
@ -135,9 +131,10 @@ other agents don't:
|
|||
deploy log). Planned: each agent gets RW to `/agents/<descendant>/`
|
||||
for just its own subtree — the manager's full-forest RW becomes the
|
||||
"root's subtree is everything" case of that same rule. hive-c0re will
|
||||
gate RO `/meta` access on a "meta read" capability; only
|
||||
`request_update_meta_inputs` writes `flake.lock`, gated by its own
|
||||
capability.
|
||||
gate RO `/meta` access on a "meta read" capability; no agent-facing
|
||||
path writes `flake.lock` any more — `request_update_meta_inputs` was
|
||||
removed, leaving the operator dashboard's `POST
|
||||
/api/meta-update` as the only entry point.
|
||||
- **Prompt/tools** — the system prompt uses `<!-- role:agent -->` /
|
||||
`<!-- role:manager -->` marker blocks, and a `Flavor::{Agent,
|
||||
Manager}` switch picks the MCP tool allow-list claude sees. Both are
|
||||
|
|
|
|||
|
|
@ -310,8 +310,8 @@ binary flavor.
|
|||
| `meta` | `get_agent_meta` (`set_status` is always-on, see below) |
|
||||
| `inbox` | `get_loose_ends`, `cancel_loose_end`, `remind` |
|
||||
| `execution` | vestigial — `mcp__bash__run` / `mcp__bash__status` are always available unconditionally via `extraMcpServers`; this group's entries expand to non-existent `mcp__hyperhive__run` / `mcp__hyperhive__status` and have no effect. See `docs/tools/bash.md`. |
|
||||
| `lifecycle` | `list_containers` *(privileged)* |
|
||||
| `approvals` | `request_update_meta_inputs` *(privileged)* |
|
||||
| `lifecycle` | none — `list_containers` no longer exists, with no replacement; the variant survives only so existing grants parse. |
|
||||
| `approvals` | none — `request_update_meta_inputs` no longer exists, with no replacement. Still a live server-side gate: `cancel_loose_end`'s approval-cancel arm requires it. |
|
||||
| `scheduling` | `request_schedule_prompt`, `fire_schedule_now`, `cancel_schedule`, `edit_schedule`, `list_schedules` *(privileged)* |
|
||||
| `forge` | `create_repo` — create git repos through hive-c0re (operator-gated merge) |
|
||||
| `web_tools` | none (gates the Claude built-ins `WebFetch`/`WebSearch`, not an MCP tool) |
|
||||
|
|
@ -391,7 +391,7 @@ bypass the operator approval gate.
|
|||
`hive-sh4re/src/permissions.rs` + an arm to `as_str`. Add it to `Capability::ALL`
|
||||
(the source of truth for the permissions UI columns). Implement the access
|
||||
check in the relevant handler (`hive-c0re/src/socket_server/mod.rs`,
|
||||
`hive-c0re/src/socket_server/lifecycle_handlers.rs`, `coordinator.rs`, or a
|
||||
`hive-c0re/src/socket_server/schedules.rs`, `coordinator.rs`, or a
|
||||
handler under `hive-c0re/src/dashboard/`).
|
||||
|
||||
## Async forms
|
||||
|
|
|
|||
|
|
@ -40,8 +40,6 @@ debug agent behavior.
|
|||
- **[forge-cli](forge-cli.md)** — the exhaustive, autogenerated
|
||||
flag-by-flag reference for `hive-forge`, kept in lockstep with the
|
||||
binary by CI the same way `hivectl-cli.md` is.
|
||||
- **[lifecycle](lifecycle.md)** — listing the agents in a caller's own
|
||||
subtree, plus the approval-gated config-change tools.
|
||||
- **[matrix](matrix.md)** — the matrix MCP tool surface
|
||||
(`mcp__matrix__*`) for agents with a matrix account, multiple
|
||||
accounts per agent, and declaring extra MCP servers generally.
|
||||
|
|
|
|||
|
|
@ -1,58 +0,0 @@
|
|||
# Lifecycle and approvals tools
|
||||
|
||||
Two tool groups govern agent lifecycle management and config changes.
|
||||
The server scopes both to the caller's **own subtree** (topology-enforced
|
||||
per `topology.json`: a child, a child's child, every agent below them —
|
||||
plus the caller itself). No privileged class exists to belong to; the
|
||||
root agent reaches every agent purely because the check is transitive and
|
||||
everything sits under it.
|
||||
|
||||
## `lifecycle` tool group
|
||||
|
||||
No operator approval required. The caller's own subtree.
|
||||
|
||||
### `list_containers()`
|
||||
|
||||
List the caller's whole **subtree** with running status — children,
|
||||
their children, every agent below them. The calling agent is part of its
|
||||
own subtree, so it appears in its own listing; a leaf agent gets a
|
||||
one-row answer naming itself.
|
||||
|
||||
## `approvals` tool group
|
||||
|
||||
Meta-flake input bumps route through the operator approval queue.
|
||||
|
||||
Creating a new agent is **not** in this group — agents have no tool for
|
||||
it. The swarm controller's `InitAgentConfigRepo` job scaffolds a new
|
||||
agent's config repo (`POST /api/agents`, see
|
||||
`swarm-controller/`), and the operator spawns the container from the
|
||||
dashboard (`◆ R3QU3ST SP4WN` / `Spawn` approval, routed via
|
||||
`HostRequest::RequestSpawn`).
|
||||
|
||||
Config changes on an existing agent go through a **forge PR** on the
|
||||
agent's `agent-configs/<name>` repo (queues a `MergeConfigPr` approval
|
||||
on open/update — no MCP tool involved), not a tool call. See
|
||||
`docs/agent-lifecycle/approvals.md`.
|
||||
|
||||
### `request_update_meta_inputs(inputs?, description?)`
|
||||
|
||||
Queue an approval to run `nix flake update [inputs...]` on the meta
|
||||
flake. Pass specific input names (for example `["bitburner-agent"]`) or omit
|
||||
/ pass `[]` for all inputs. Returns immediately; the lock update runs
|
||||
on operator approval.
|
||||
|
||||
**Doesn't** trigger container rebuilds — the operator rebuilds affected
|
||||
agents after the approval resolves.
|
||||
|
||||
## Boundary summary
|
||||
|
||||
| Operation | Requires approval? | Scope |
|
||||
| ---------------------------- | ------------------ | ---------------------------- |
|
||||
| `list_containers` | No | Own subtree, caller included |
|
||||
| `request_update_meta_inputs` | Yes (MetaUpdate) | Meta flake (global) |
|
||||
|
||||
## See also
|
||||
|
||||
- [`docs/agent-lifecycle/approvals.md`](../agent-lifecycle/approvals.md) — full approval flow, kinds,
|
||||
helper events (`approval_resolved`), flake.lock
|
||||
validation.
|
||||
|
|
@ -130,9 +130,13 @@ hive_name?, swarm_name?, matrix_accounts? }`. `matrix_accounts` is a
|
|||
- **Subagent spawning** — headless claude sub-instances as background
|
||||
tasks, shipped default-on like bash execution (no tool group gates it
|
||||
yet). See [`docs/tools/subagent.md`](../tools/subagent.md).
|
||||
- **Lifecycle + config** (`lifecycle`, `approvals`) — list the child
|
||||
agents in your own subtree, apply config commits. See
|
||||
[`docs/tools/lifecycle.md`](../tools/lifecycle.md).
|
||||
- **Lifecycle + config** (`lifecycle`, `approvals`) — neither group
|
||||
carries an MCP tool any more: `list_containers` and
|
||||
`request_update_meta_inputs` no longer exist, with no
|
||||
replacement. `approvals` survives as a server-side gate on
|
||||
`cancel_loose_end`'s approval-cancel arm; `lifecycle` gates nothing.
|
||||
Config changes go through a forge PR on `agent-configs/<name>` — see
|
||||
[`docs/agent-lifecycle/approvals.md`](../agent-lifecycle/approvals.md).
|
||||
- **Scheduling** (`scheduling`) — scheduled prompts. See
|
||||
[`docs/tools/scheduling.md`](../tools/scheduling.md).
|
||||
- **Forge repos** (`forge`) — `create_repo` — the only agent path to
|
||||
|
|
|
|||
|
|
@ -131,17 +131,6 @@ pub struct CancelLooseEndArgs {
|
|||
pub id: i64,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
|
||||
pub struct UpdateMetaInputsArgs {
|
||||
/// Flake input names to update (e.g. `["bitburner-agent", "nixpkgs"]`).
|
||||
/// Pass an empty list to update ALL inputs.
|
||||
#[serde(default)]
|
||||
pub inputs: Vec<String>,
|
||||
/// Optional description shown on the dashboard approval card.
|
||||
#[serde(default)]
|
||||
pub description: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
|
||||
pub struct RequestSchedulePromptArgs {
|
||||
/// Recipient agents — one schedule fires to many inboxes at the
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ mod render;
|
|||
pub use args::{
|
||||
AckUntilArgs, CancelLooseEndArgs, CancelScheduleArgs, CompactArgs, CreateRepoArgs,
|
||||
EditScheduleArgs, FireScheduleNowArgs, GetAgentMetaArgs, GetHostJournalArgs, MarkTodosDoneArgs,
|
||||
RecvArgs, RemindArgs, RequestSchedulePromptArgs, SendArgs, SetStatusArgs, UpdateMetaInputsArgs,
|
||||
RecvArgs, RemindArgs, RequestSchedulePromptArgs, SendArgs, SetStatusArgs,
|
||||
};
|
||||
pub use render::{annotate_retries, format_ack, format_agent_meta, format_recv};
|
||||
|
||||
|
|
@ -547,44 +547,6 @@ impl AgentServer {
|
|||
.await
|
||||
}
|
||||
|
||||
// IMPORTANT: this tool is only available when the `lifecycle` tool group
|
||||
// is granted to this agent. Returns the calling agent's whole subtree,
|
||||
// itself included, with running status.
|
||||
#[tool(
|
||||
description = "List this agent's whole subtree — children, their children, and so on \
|
||||
down — with running status. The calling agent is part of its own subtree, so it \
|
||||
appears in the listing too. Requires the `lifecycle` tool group. \
|
||||
Returns every known descendant regardless of running state — check the `running` \
|
||||
field to distinguish live from stopped containers. Ordered by topology depth \
|
||||
(parents before children), then alphabetically within each tier."
|
||||
)]
|
||||
async fn list_containers(&self) -> String {
|
||||
run_tool_envelope("list_containers", String::new(), async move {
|
||||
let (resp, retries) = self
|
||||
.dispatch(hive_core_agent_sock::Request::ListDescendants)
|
||||
.await;
|
||||
let body = match resp {
|
||||
Ok(hive_core_agent_sock::Response::Containers { containers }) => {
|
||||
if containers.is_empty() {
|
||||
"no descendant containers".to_owned()
|
||||
} else {
|
||||
containers
|
||||
.iter()
|
||||
.map(|c| {
|
||||
let status = if c.running { "running" } else { "stopped" };
|
||||
format!("{} ({})", c.name, status)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
}
|
||||
}
|
||||
other => reply_err(other, "list_containers"),
|
||||
};
|
||||
annotate_retries(body, retries)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
// IMPORTANT: this tool is capability-gated (`read_host_journal`).
|
||||
// It is added to `--allowedTools` by `allowed_capability_tools` only
|
||||
// when `HIVE_CAPABILITIES` contains `read_host_journal`. hive-c0re
|
||||
|
|
@ -627,43 +589,6 @@ impl AgentServer {
|
|||
.await
|
||||
}
|
||||
|
||||
#[tool(
|
||||
description = "Queue an approval for the operator to run `nix flake update` on the \
|
||||
meta flake and commit the resulting lock changes. Pass specific input names to update \
|
||||
only those inputs (e.g. `[\"bitburner-agent\"]`), or pass an empty list to update ALL \
|
||||
inputs. Returns immediately — the lock update runs when the operator approves. \
|
||||
Does NOT trigger container rebuilds — the operator rebuilds affected agents \
|
||||
after the approval resolves."
|
||||
)]
|
||||
async fn request_update_meta_inputs(
|
||||
&self,
|
||||
Parameters(args): Parameters<UpdateMetaInputsArgs>,
|
||||
) -> String {
|
||||
let log = format!("{args:?}");
|
||||
run_tool_envelope("request_update_meta_inputs", log, async move {
|
||||
let label = if args.inputs.is_empty() {
|
||||
"all inputs".to_string()
|
||||
} else {
|
||||
args.inputs.join(", ")
|
||||
};
|
||||
let (resp, retries) = self
|
||||
.dispatch(hive_core_agent_sock::Request::RequestUpdateMetaInputs {
|
||||
inputs: args.inputs,
|
||||
description: args.description,
|
||||
})
|
||||
.await;
|
||||
annotate_retries(
|
||||
format_ack(
|
||||
resp,
|
||||
"request_update_meta_inputs",
|
||||
format!("approval queued: {label}"),
|
||||
),
|
||||
retries,
|
||||
)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
#[tool(
|
||||
description = "Queue an approval to add a scheduled prompt — one body delivered to \
|
||||
N agent inboxes at a target time, optionally recurring every `interval_seconds`. \
|
||||
|
|
|
|||
|
|
@ -643,12 +643,12 @@ mod tests {
|
|||
Ok(hive_core_agent_sock::Response::OkWarn {
|
||||
warnings: vec!["name is reserved".to_owned(), "second thing".to_owned()],
|
||||
}),
|
||||
"request_update_meta_inputs",
|
||||
"update_meta_inputs approval queued".to_owned(),
|
||||
"request_schedule_prompt",
|
||||
"schedule approval queued".to_owned(),
|
||||
);
|
||||
// The operation HAPPENED — dropping the success line would read as a
|
||||
// failure and invite a retry that queues a second approval.
|
||||
assert!(out.starts_with("update_meta_inputs approval queued"));
|
||||
assert!(out.starts_with("schedule approval queued"));
|
||||
assert!(out.contains("⚠️ name is reserved"));
|
||||
assert!(out.contains("⚠️ second thing"));
|
||||
}
|
||||
|
|
@ -659,7 +659,7 @@ mod tests {
|
|||
// warning marker would pass the test above.
|
||||
let out = format_ack(
|
||||
Ok(hive_core_agent_sock::Response::Ok),
|
||||
"request_update_meta_inputs",
|
||||
"request_schedule_prompt",
|
||||
"queued".to_owned(),
|
||||
);
|
||||
assert_eq!(out, "queued");
|
||||
|
|
|
|||
|
|
@ -4,8 +4,6 @@ Tools (hyperhive surface). Full signature + behavior for each comes from the too
|
|||
|
||||
- **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_, your own subtree — children, their children, and so on down, no approval needed): `list_containers`.
|
||||
- **Approvals** (_requires `approvals` tool group_, queues an operator approval): `request_update_meta_inputs`.
|
||||
- **Scheduling** (_requires `scheduling` tool group_): `request_schedule_prompt` (queues an approval), `cancel_schedule`, `fire_schedule_now`, `edit_schedule`, `list_schedules` (these four don't need approval — you can manage schedules you own or that a sub-agent in your subtree owns).
|
||||
- **Diagnostics**: `get_host_journal` (_requires `read_host_journal` capability_).
|
||||
|
||||
|
|
@ -19,7 +17,7 @@ Messages from sender `system` are hyperhive helper events (JSON body, `event` fi
|
|||
|
||||
- `needs_update` — agent's flake rev is stale. Ask the operator to rebuild it.
|
||||
- `container_crash` — ask the operator to start it again; if it keeps crashing, say so with what you saw.
|
||||
- `approval_resolved` — one of your own submitted approvals (`request_update_meta_inputs`, a scheduled prompt, a config PR, …) was approved, denied, or failed; the body carries the resolution.
|
||||
- `approval_resolved` — one of your own submitted approvals (a scheduled prompt, a config PR, …) was approved, denied, or failed; the body carries the resolution.
|
||||
|
||||
Lifecycle notices that don't need an immediate turn — a new agent spawned, a container rebuilt/killed/destroyed, or its login state changing — surface as todos instead of messages now. Call `get_loose_ends` to see them.
|
||||
|
||||
|
|
|
|||
|
|
@ -498,8 +498,7 @@ fn tool_icon(name: &str) -> &'static str {
|
|||
"mcp__hyperhive__cancel_loose_end" => "✂️",
|
||||
"mcp__hyperhive__ack_until" => "✅",
|
||||
"mcp__hyperhive__get_agent_meta" => "ℹ️",
|
||||
"mcp__hyperhive__list_containers"
|
||||
| "mcp__matrix__list_rooms"
|
||||
"mcp__matrix__list_rooms"
|
||||
| "mcp__matrix__list_room_members"
|
||||
| "mcp__matrix__list_invites" => "📋",
|
||||
"mcp__hyperhive__get_host_journal" => "📜",
|
||||
|
|
@ -666,8 +665,7 @@ fn fmt_hyperhive_tool(name: &str, short: &str, input: &Value) -> String {
|
|||
}
|
||||
}
|
||||
"mcp__hyperhive__remind" => fmt_hyperhive_remind(short, input),
|
||||
"mcp__hyperhive__request_update_meta_inputs"
|
||||
| "mcp__hyperhive__list_schedules"
|
||||
"mcp__hyperhive__list_schedules"
|
||||
| "mcp__hyperhive__cancel_schedule"
|
||||
| "mcp__hyperhive__fire_schedule_now"
|
||||
| "mcp__hyperhive__edit_schedule"
|
||||
|
|
@ -792,17 +790,6 @@ fn fmt_hyperhive_edit_schedule(short: &str, input: &Value) -> String {
|
|||
/// Schedule-management hyperhive tools (list/cancel/fire/edit/request).
|
||||
fn fmt_hyperhive_schedule_tool(name: &str, short: &str, input: &Value) -> String {
|
||||
match name {
|
||||
"mcp__hyperhive__request_update_meta_inputs" => {
|
||||
let ins = match input.get("inputs").and_then(Value::as_array) {
|
||||
Some(arr) if !arr.is_empty() => {
|
||||
let names: Vec<&str> = arr.iter().filter_map(Value::as_str).take(4).collect();
|
||||
let tail = if arr.len() > 4 { ", …" } else { "" };
|
||||
format!("[{}{}]", names.join(", "), tail)
|
||||
}
|
||||
_ => "all".to_owned(),
|
||||
};
|
||||
format!("{short} {ins}")
|
||||
}
|
||||
"mcp__hyperhive__list_schedules" => format!("{short}()"),
|
||||
"mcp__hyperhive__cancel_schedule" => {
|
||||
let id = input
|
||||
|
|
|
|||
|
|
@ -892,8 +892,8 @@ async fn run_deploy_tail(
|
|||
/// otherwise just the agents named by `agent-<name>` inputs.
|
||||
/// Topology-sorted so parents rebuild before their children.
|
||||
///
|
||||
/// `inputs` is the caller-supplied flake-input-name list
|
||||
/// (`RequestUpdateMetaInputs`'s `inputs` field, operator-approved but not
|
||||
/// `inputs` is the caller-supplied flake-input-name list (the dashboard's
|
||||
/// `POST /api/meta-update` form field, operator-supplied but not
|
||||
/// otherwise validated) — the `agent-<name>` branch parses agent names
|
||||
/// straight out of it, so each is validated through [`hive_types::Ident`]
|
||||
/// before it ever reaches a filesystem path or a forge URL built from a
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
//! Config-approval request handlers: `RequestUpdateMetaInputs`, plus the
|
||||
//! shared submit helper `submit_merge_config_pr`.
|
||||
//! Config-approval submit helper `submit_merge_config_pr`.
|
||||
//!
|
||||
//! `submit_merge_config_pr` is called from the dashboard webhook handler
|
||||
//! (`dashboard::webhook`) — agents no longer need an MCP tool for config
|
||||
|
|
@ -8,57 +7,8 @@
|
|||
|
||||
use std::sync::Arc;
|
||||
|
||||
use hive_core_agent_sock::Response;
|
||||
|
||||
use crate::coordinator::Coordinator;
|
||||
|
||||
/// `RequestUpdateMetaInputs` — queue an `UpdateMetaInputs` approval
|
||||
/// carrying the JSON-encoded input list in `commit_ref` (no git commit
|
||||
/// is involved; the field is the payload the approval handler decodes).
|
||||
pub(super) fn handle_request_update_meta_inputs(
|
||||
coord: &Arc<Coordinator>,
|
||||
requester: &str,
|
||||
inputs: &[String],
|
||||
description: Option<&str>,
|
||||
) -> Response {
|
||||
let label = if inputs.is_empty() {
|
||||
"all inputs".to_string()
|
||||
} else {
|
||||
inputs.join(", ")
|
||||
};
|
||||
tracing::info!(%requester, %label, "request_update_meta_inputs");
|
||||
let commit_ref = serde_json::to_string(inputs).unwrap_or_default();
|
||||
let id = match coord
|
||||
.approvals
|
||||
.submit_kind(
|
||||
requester,
|
||||
hive_sh4re::approvals::ApprovalKind::UpdateMetaInputs,
|
||||
&commit_ref,
|
||||
description,
|
||||
requester,
|
||||
None,
|
||||
)
|
||||
.map_err(|e| anyhow::anyhow!("{e:#}"))
|
||||
{
|
||||
Ok(id) => id,
|
||||
Err(e) => {
|
||||
return Response::Err {
|
||||
message: format!("queue update_meta_inputs approval: {e:#}"),
|
||||
};
|
||||
}
|
||||
};
|
||||
tracing::info!(%id, %label, "update_meta_inputs approval queued");
|
||||
coord.emit_approval_added(crate::coordinator::ApprovalAdded {
|
||||
id,
|
||||
agent: requester,
|
||||
approval_kind: "update_meta_inputs",
|
||||
sha_short: None,
|
||||
description: description.map(str::to_owned),
|
||||
pr_number: None,
|
||||
});
|
||||
Response::Ok
|
||||
}
|
||||
|
||||
/// Submit-time half of the PR-merge flow: fetch the PR head sha from the
|
||||
/// forge, queue the approval row, and emit the `approval_added` event so the
|
||||
/// dashboard shows the pending card immediately.
|
||||
|
|
|
|||
|
|
@ -1,48 +0,0 @@
|
|||
//! `ListDescendants` request handler — the `lifecycle` tool group's
|
||||
//! remaining verb, a read of the caller's own subtree.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use hive_core_agent_sock::Response;
|
||||
|
||||
use crate::coordinator::Coordinator;
|
||||
|
||||
/// `ListDescendants` — every topological descendant of `agent` with
|
||||
/// its running/stopped state, parents before children.
|
||||
pub(super) async fn handle_list_descendants(coord: &Arc<Coordinator>, agent: &str) -> Response {
|
||||
tracing::debug!(%agent, "agent: list descendants");
|
||||
// Walk the full topology and collect every descendant.
|
||||
let topo = crate::topology::read();
|
||||
let mut names: Vec<String> = topo
|
||||
.keys()
|
||||
.filter(|name| crate::topology::is_descendant_of(name, agent))
|
||||
.cloned()
|
||||
.collect();
|
||||
// Parents before children, then alpha within each tier.
|
||||
crate::auto_update::topology_sort(&mut names, &topo);
|
||||
// Read from the coordinator's cached container snapshot instead of
|
||||
// live-querying each container's systemd unit state — the same
|
||||
// `containers_snapshot()` the dashboard's `/api/state` cold-load path
|
||||
// already uses, kept fresh by `rescan_containers_and_emit()` on every
|
||||
// mutation plus the crash-watcher's periodic poll. Avoids N
|
||||
// `systemctl is-active` subprocess spawns per `list_containers` call;
|
||||
// per mara, daemons should do the expensive work themselves and serve
|
||||
// clients a cheap cached read.
|
||||
let snapshot = coord.containers_snapshot().await;
|
||||
let running_by_name: std::collections::HashMap<&str, bool> = snapshot
|
||||
.iter()
|
||||
.map(|v| (v.name.as_str(), v.running))
|
||||
.collect();
|
||||
let containers = names
|
||||
.into_iter()
|
||||
.map(|name| {
|
||||
// A descendant absent from the snapshot (not yet scanned since
|
||||
// its own registration, e.g. mid-spawn) reads as not running
|
||||
// rather than erroring — matches the old membership-check's
|
||||
// default-false behavior for an unknown name.
|
||||
let running = running_by_name.get(name.as_str()).copied().unwrap_or(false);
|
||||
hive_sh4re::container::ContainerInfo { name, running }
|
||||
})
|
||||
.collect();
|
||||
Response::Containers { containers }
|
||||
}
|
||||
|
|
@ -23,15 +23,12 @@ use tokio::task::JoinHandle;
|
|||
use crate::coordinator::Coordinator;
|
||||
|
||||
mod config_approvals;
|
||||
mod lifecycle_handlers;
|
||||
mod schedules;
|
||||
|
||||
pub(crate) use config_approvals::submit_merge_config_pr;
|
||||
pub(crate) use schedules::filter_ghost_schedule_targets;
|
||||
pub use schedules::schedule_to_wire_public;
|
||||
|
||||
use config_approvals::handle_request_update_meta_inputs;
|
||||
use lifecycle_handlers::handle_list_descendants;
|
||||
use schedules::{
|
||||
EditSchedulePatch, handle_cancel_schedule, handle_edit_schedule, handle_fire_schedule_now,
|
||||
handle_list_schedules, handle_request_schedule_prompt,
|
||||
|
|
@ -181,9 +178,9 @@ pub(crate) fn recv_timeout(wait_seconds: Option<u64>) -> std::time::Duration {
|
|||
/// Handle the subset of `Request` variants that are identical on both
|
||||
/// the agent socket and the manager socket. Returns `Some(response)` for
|
||||
/// every variant it handles; returns `None` for the remaining variants —
|
||||
/// `ListDescendants` and the orchestration verbs (schedules / meta-inputs),
|
||||
/// which need per-verb tool-group gating — or for host-admin / unknown
|
||||
/// requests invalid on either socket.
|
||||
/// `GetLooseEnds` and the orchestration verbs (schedules), which need
|
||||
/// per-verb tool-group gating — or for host-admin / unknown requests
|
||||
/// invalid on either socket.
|
||||
///
|
||||
/// The unified `dispatch` calls this first; the remaining arms (which gate
|
||||
/// on topology / capabilities / tool-groups) are handled there.
|
||||
|
|
@ -542,14 +539,13 @@ fn handle_requeue_inflight(
|
|||
/// Unified dispatch for every socket connection — per-agent sockets and the
|
||||
/// (now pure-transport) manager socket alike. There is no privilege bit;
|
||||
/// authority derives uniformly from the caller's identity: hive-wide
|
||||
/// orchestration verbs (schedules / meta-inputs) require the matching
|
||||
/// tool-group (the grantable capability).
|
||||
/// orchestration verbs (schedules) require the matching tool-group (the
|
||||
/// grantable capability).
|
||||
async fn dispatch(req: &Request, agent: &str, coord: &Arc<Coordinator>) -> Response {
|
||||
if let Some(resp) = dispatch_shared(req, agent, coord).await {
|
||||
return resp;
|
||||
}
|
||||
match req {
|
||||
Request::ListDescendants => handle_list_descendants(coord, agent).await,
|
||||
Request::GetLooseEnds => handle_get_loose_ends(coord, agent),
|
||||
// Orchestration verbs — gated per-verb on tool-group membership
|
||||
// (see `dispatch_orchestration`).
|
||||
|
|
@ -557,21 +553,12 @@ async fn dispatch(req: &Request, agent: &str, coord: &Arc<Coordinator>) -> Respo
|
|||
}
|
||||
}
|
||||
|
||||
/// Handle the hive-wide orchestration verbs (scheduling, meta-input updates).
|
||||
/// No blanket socket gate: each verb gates on the grantable capability that
|
||||
/// authorises it — the matching tool-group (`scheduling` / `approvals`). Any
|
||||
/// other variant is a host-admin / unknown request invalid on either socket.
|
||||
/// Handle the hive-wide orchestration verbs (scheduling). No blanket socket
|
||||
/// gate: each verb gates on the grantable capability that authorises it —
|
||||
/// the matching tool-group (`scheduling`). Any other variant is a
|
||||
/// host-admin / unknown request invalid on either socket.
|
||||
async fn dispatch_orchestration(req: &Request, agent: &str, coord: &Arc<Coordinator>) -> Response {
|
||||
match req {
|
||||
Request::RequestUpdateMetaInputs {
|
||||
inputs,
|
||||
description,
|
||||
} => {
|
||||
if let Some(err) = require_group(agent, "approvals", "request update_meta_inputs") {
|
||||
return err;
|
||||
}
|
||||
handle_request_update_meta_inputs(coord, agent, inputs, description.as_deref())
|
||||
}
|
||||
Request::RequestSchedulePrompt(payload) => {
|
||||
if let Some(err) = require_group(agent, "scheduling", "schedule a prompt") {
|
||||
return err;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,11 @@
|
|||
//! Approval queue. Requests are submitted by the manager
|
||||
//! (`RequestUpdateMetaInputs`), the config-PR webhook (`MergeConfigPr`), or
|
||||
//! Approval queue. Requests are submitted by an agent
|
||||
//! (`RequestSchedulePrompt`), the config-PR webhook (`MergeConfigPr`), or
|
||||
//! the operator (`Spawn`); the user approves/denies via the host admin CLI;
|
||||
//! on approval the host runs the corresponding action.
|
||||
//!
|
||||
//! `UpdateMetaInputs` rows are legacy: the MCP tool that queued them was
|
||||
//! removed and nothing produces the kind any more. The variant and
|
||||
//! its (de)serialization stay so pre-existing rows still read back.
|
||||
|
||||
use std::path::Path;
|
||||
use std::sync::Mutex;
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
//! shared payload types it references (`Message`, `LooseEnd`, `Approval`, …)
|
||||
//! stay in `hive-sh4re`, which this crate depends on.
|
||||
|
||||
use hive_sh4re::container::{ContainerInfo, MatrixIdentity};
|
||||
use hive_sh4re::container::MatrixIdentity;
|
||||
use hive_sh4re::inbox::{CancelLooseEndKind, DeliveredMessage, InboxRow, LooseEnd};
|
||||
use hive_sh4re::journal::JournalPriority;
|
||||
use hive_sh4re::manager::SchedulePromptPayload;
|
||||
|
|
@ -149,13 +149,6 @@ pub enum Request {
|
|||
},
|
||||
|
||||
// ---- privileged (manager socket only for now) ---------------------------
|
||||
/// *(privileged)* Queue an approval to run `nix flake update [inputs...]`.
|
||||
RequestUpdateMetaInputs {
|
||||
#[serde(default)]
|
||||
inputs: Vec<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
description: Option<String>,
|
||||
},
|
||||
/// *(privileged)* Queue an approval to add a scheduled prompt.
|
||||
RequestSchedulePrompt(SchedulePromptPayload),
|
||||
/// *(privileged)* Cancel a scheduled prompt.
|
||||
|
|
@ -168,12 +161,6 @@ pub enum Request {
|
|||
/// plus any owned by an agent in its subtree (everything, for the
|
||||
/// operator).
|
||||
ListSchedules,
|
||||
/// List the calling agent's subtree — children, their children, and so
|
||||
/// on down, plus the caller itself, which is part of its own subtree.
|
||||
/// Gated by the `lifecycle` tool group. The result includes every known
|
||||
/// member regardless of whether the container is currently
|
||||
/// running — use `running` to distinguish.
|
||||
ListDescendants,
|
||||
/// *(privileged)* Fire a scheduled prompt out of band immediately.
|
||||
FireScheduleNow { id: i64 },
|
||||
/// *(privileged)* Edit an existing schedule's mutable fields.
|
||||
|
|
@ -276,10 +263,6 @@ pub enum Response {
|
|||
full_name: String,
|
||||
clone_url: String,
|
||||
},
|
||||
/// `ListDescendants` result: all descendant containers, with running
|
||||
/// status. Ordered by topology depth (parents before children), then
|
||||
/// alphabetically within each depth tier.
|
||||
Containers { containers: Vec<ContainerInfo> },
|
||||
/// `Recv` result when a graceful stop is pending for this agent
|
||||
/// (set by hive-c0re's `GracefulStop` orchestration). Returned in
|
||||
/// place of `Messages` — it doubles as the inbound fence: the harness
|
||||
|
|
|
|||
|
|
@ -1,19 +1,10 @@
|
|||
//! Container/agent-roster wire shapes: what `ListDescendants` and
|
||||
//! `HostRequest::AgentStatus` return, plus the per-account matrix
|
||||
//! identity shape surfaced by `GetAgentMeta`.
|
||||
//! Container/agent-roster wire shapes: what `HostRequest::AgentStatus`
|
||||
//! returns, plus the per-account matrix identity shape surfaced by
|
||||
//! `GetAgentMeta`.
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// One entry in a `ListDescendants` result.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ContainerInfo {
|
||||
/// Logical agent name (no `h-` prefix).
|
||||
pub name: String,
|
||||
/// Whether the container is currently running.
|
||||
pub running: bool,
|
||||
}
|
||||
|
||||
/// One row in a `HostRequest::AgentStatus` result — the operator-CLI
|
||||
/// projection of the dashboard's per-agent `ContainerView`. Carries the
|
||||
/// agent's running/health flags plus the technical state an operator
|
||||
|
|
|
|||
|
|
@ -152,9 +152,15 @@ pub enum ToolGroup {
|
|||
Meta,
|
||||
/// `get_loose_ends`, `cancel_loose_end`, `remind`
|
||||
Inbox,
|
||||
/// `list_containers` - *(privileged)*
|
||||
/// Gates no tool today — `list_containers`, its only member, was
|
||||
/// removed with no replacement. Kept so existing
|
||||
/// `meta/capabilities.json` grants still parse; `tools()` returns
|
||||
/// `&[]`.
|
||||
Lifecycle,
|
||||
/// `request_update_meta_inputs` - *(privileged)*
|
||||
/// Grants no MCP tool today — `request_update_meta_inputs`, its only
|
||||
/// member, was removed with no replacement. Still a live
|
||||
/// server-side gate: `cancel_loose_end`'s approval-cancel arm
|
||||
/// requires it (`socket_server::check_can_cancel_approval`).
|
||||
Approvals,
|
||||
/// `request_schedule_prompt`, `fire_schedule_now`, `cancel_schedule`,
|
||||
/// `edit_schedule`, `list_schedules` - *(privileged)*
|
||||
|
|
@ -215,16 +221,15 @@ impl ToolGroup {
|
|||
}
|
||||
|
||||
/// The MCP tool names (without the `mcp__hyperhive__` prefix) in this group.
|
||||
/// Returns `&[]` for `WebTools` — it enables Claude built-in tools,
|
||||
/// not MCP tools; see `builtin_tools()`.
|
||||
/// Returns `&[]` for `WebTools` (it enables Claude built-in tools, not MCP
|
||||
/// tools — see `builtin_tools()`) and for `Lifecycle` / `Approvals`
|
||||
/// (their tools were removed); see each variant's doc comment.
|
||||
#[must_use]
|
||||
pub fn tools(self) -> &'static [&'static str] {
|
||||
match self {
|
||||
Self::Messaging => &["send", "recv", "ack_until"],
|
||||
Self::Meta => &["get_agent_meta"],
|
||||
Self::Inbox => &["get_loose_ends", "cancel_loose_end", "remind"],
|
||||
Self::Lifecycle => &["list_containers"],
|
||||
Self::Approvals => &["request_update_meta_inputs"],
|
||||
Self::Scheduling => &[
|
||||
"request_schedule_prompt",
|
||||
"fire_schedule_now",
|
||||
|
|
@ -233,16 +238,22 @@ impl ToolGroup {
|
|||
"list_schedules",
|
||||
],
|
||||
Self::Forge => &["create_repo"],
|
||||
// Both empty, for different reasons — see each variant's own
|
||||
// doc comment above. `Execution` grants the out-of-process
|
||||
// `bash` MCP server (`mcp__bash__run`/`status`/`kill`), gated
|
||||
// at config-render time by `extra_server_required_group` in
|
||||
// All four empty, for four different reasons — see each
|
||||
// variant's own doc comment above. `Execution` grants the
|
||||
// out-of-process `bash` MCP server
|
||||
// (`mcp__bash__run`/`status`/`kill`), gated at config-render
|
||||
// time by `extra_server_required_group` in
|
||||
// `hive-agent/src/mcp_config.rs`, not by this list — an
|
||||
// out-of-process server has no later enforcement point, so
|
||||
// that gate is the actual security boundary. `WebTools`
|
||||
// grants Claude built-in tools, not MCP ones; see
|
||||
// `builtin_tools()`.
|
||||
Self::Execution | Self::WebTools => &[],
|
||||
// `builtin_tools()`. `Lifecycle` and `Approvals` each listed
|
||||
// exactly one tool — `list_containers` and
|
||||
// `request_update_meta_inputs` respectively — and both tools
|
||||
// were removed outright; the variants stay so existing
|
||||
// grants parse, and `Approvals` still gates
|
||||
// `cancel_loose_end`'s approval-cancel arm server-side.
|
||||
Self::Lifecycle | Self::Approvals | Self::Execution | Self::WebTools => &[],
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -324,9 +335,9 @@ impl ToolGroup {
|
|||
"get_agent_meta — identity introspection (set_status is always available)"
|
||||
}
|
||||
Self::Inbox => "get_loose_ends, cancel_loose_end, remind — self-scheduling",
|
||||
Self::Lifecycle => "list_containers — own-subtree container listing (privileged)",
|
||||
Self::Lifecycle => "no tools — vestigial since list_containers was removed",
|
||||
Self::Approvals => {
|
||||
"request_update_meta_inputs — operator-approved meta-flake input bumps (privileged)"
|
||||
"no tools — grants cancel_loose_end's approval-cancel arm (privileged)"
|
||||
}
|
||||
Self::Scheduling => {
|
||||
"request_schedule_prompt and related — operator-visible scheduled prompts (privileged)"
|
||||
|
|
|
|||
Loading…
Reference in a new issue