mcp: remove the restart/kill/start/update/get_logs agent verbs

Container lifecycle from inside an agent goes away: an agent no longer
starts, stops, restarts or rebuilds a container in its subtree, and no
longer reads another container's journal. Those are operator actions —
the dashboard and hivectl keep their own paths to the same job-queue
and hive-priv plumbing, which is why none of that machinery is removed
here, only the five MCP verbs and what they alone reached.

What went with them: the `Request` variants and `Response::Logs` on the
agent socket, the five tool definitions and their arg structs, the four
lifecycle handlers plus `handle_get_logs`, and `require_descendant` —
the topology guard those five were the only remaining callers of.
`ToolGroup::Diagnostics` goes too: `get_logs` was its only tool, so it
would otherwise be a grantable group that grants nothing. `lifecycle`
stays, now carrying `list_containers` alone.

An agent that gets a `needs_update` or `container_crash` helper event
has no remedy of its own left, so the system prompt and the docs now
send it to the operator instead of to a tool that no longer exists.

Refs #4480
This commit is contained in:
atlas 2026-09-19 03:47:18 +02:00 • committed by mara
commit 87970a8c93
17 changed files with 48 additions and 450 deletions

View file

@ -94,15 +94,9 @@ pub struct CompactArgs {
}
// -----------------------------------------------------------------------------
// Privileged tool arg types (lifecycle, approvals, scheduling, diagnostics)
// Privileged tool arg types (lifecycle, approvals, scheduling)
// -----------------------------------------------------------------------------
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
pub struct KillArgs {
/// Sub-agent name (without the `h-` container prefix).
pub name: String,
}
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
pub struct SetStatusArgs {
/// Status text to display on the dashboard card. Pass an empty string to clear.
@ -125,24 +119,6 @@ pub struct GetAgentMetaArgs {
pub name: Option<String>,
}
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
pub struct StartArgs {
/// Sub-agent name (without the `h-` container prefix).
pub name: String,
}
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
pub struct RestartArgs {
/// Sub-agent name (without the `h-` container prefix).
pub name: String,
}
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
pub struct UpdateArgs {
/// Sub-agent name (without the `h-` container prefix).
pub name: String,
}
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
pub struct CancelLooseEndArgs {
/// Which kind of thread to cancel — `"reminder"` for a scheduled
@ -265,17 +241,6 @@ pub struct EditScheduleArgs {
pub targets_remove: Option<Vec<String>>,
}
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
pub struct GetLogsArgs {
/// Logical agent name to fetch logs for (e.g. `gui`, `iris`).
/// hive-c0re maps it to the underlying machine name (`h-gui`)
/// itself — pass the plain agent name, not the `h-` form.
pub agent: String,
/// How many journal lines to return (default: 50, max: 500).
#[serde(default)]
pub lines: Option<u32>,
}
/// Arguments for `get_host_journal` (capability-gated: `read_host_journal`).
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
pub struct GetHostJournalArgs {

View file

@ -26,8 +26,8 @@ mod render;
pub use args::{
AckUntilArgs, AgentGetLooseEndsArgs, CancelLooseEndArgs, CancelScheduleArgs, CompactArgs,
CreateRepoArgs, EditScheduleArgs, FireScheduleNowArgs, GetAgentMetaArgs, GetHostJournalArgs,
GetLogsArgs, KillArgs, MarkTodosDoneArgs, RecvArgs, RemindArgs, RequestSchedulePromptArgs,
RestartArgs, SendArgs, SetStatusArgs, StartArgs, UpdateArgs, UpdateMetaInputsArgs,
MarkTodosDoneArgs, RecvArgs, RemindArgs, RequestSchedulePromptArgs, SendArgs, SetStatusArgs,
UpdateMetaInputsArgs,
};
pub use render::{annotate_retries, format_ack, format_agent_meta, format_recv};
@ -557,78 +557,6 @@ impl AgentServer {
.await
}
// IMPORTANT: this tool is only available when the `lifecycle` tool group
// is granted to this agent. hive-c0re enforces the topology check
// server-side: the call is rejected unless `name` is in the caller's
// subtree.
#[tool(
description = "Restart a sub-agent container (stop + start). Only succeeds if `name` \
is somewhere in this agent's subtree — a child, a child's child, and so on down \
the topology tree — which the server enforces. No approval required."
)]
async fn restart(&self, Parameters(args): Parameters<RestartArgs>) -> String {
let log = format!("{args:?}");
let name = args.name.clone();
run_tool_envelope("restart", log, async move {
let (resp, retries) = self
.dispatch(hive_core_agent_sock::Request::Restart { name: args.name })
.await;
annotate_retries(
format_ack(resp, "restart", format!("restarted {name}")),
retries,
)
})
.await
}
// IMPORTANT: this tool is only available when the `lifecycle` tool group
// is granted to this agent. hive-c0re enforces the topology check
// server-side: the call is rejected unless `name` is in the caller's
// subtree.
#[tool(
description = "Stop a sub-agent container (graceful). Only succeeds if `name` \
is somewhere in this agent's subtree — a child, a child's child, and so on down \
the topology tree — which the server enforces. No approval required. \
State dir is kept; recreating the agent reuses prior config + credentials."
)]
async fn kill(&self, Parameters(args): Parameters<KillArgs>) -> String {
let log = format!("{args:?}");
let name = args.name.clone();
run_tool_envelope("kill", log, async move {
let (resp, retries) = self
.dispatch(hive_core_agent_sock::Request::Kill { name: args.name })
.await;
annotate_retries(format_ack(resp, "kill", format!("killed {name}")), retries)
})
.await
}
// IMPORTANT: this tool is only available when the `lifecycle` tool group
// is granted to this agent. hive-c0re enforces the topology check
// server-side: the call is rejected unless `name` is in the caller's
// subtree.
#[tool(
description = "Rebuild a sub-agent: re-applies the current hyperhive flake + agent.nix \
and restarts the container. Only succeeds if `name` is somewhere in this agent's \
subtree — a child, a child's child, and so on down the topology tree — which the \
server enforces. No approval required. Idempotent — use when an agent needs its \
config reapplied."
)]
async fn update(&self, Parameters(args): Parameters<UpdateArgs>) -> String {
let log = format!("{args:?}");
let name = args.name.clone();
run_tool_envelope("update", log, async move {
let (resp, retries) = self
.dispatch(hive_core_agent_sock::Request::Update { name: args.name })
.await;
annotate_retries(
format_ack(resp, "update", format!("updated {name}")),
retries,
)
})
.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.
@ -709,70 +637,13 @@ impl AgentServer {
.await
}
// IMPORTANT: this tool is only available when the `lifecycle` tool group
// is granted to this agent. hive-c0re enforces the topology check
// server-side: the call is rejected unless `name` is in the caller's
// subtree.
#[tool(
description = "Start a stopped sub-agent container. Only succeeds if `name` \
is somewhere in this agent's subtree — a child, a child's child, and so on down \
the topology tree — which the server enforces. No approval required."
)]
async fn start(&self, Parameters(args): Parameters<StartArgs>) -> String {
let log = format!("{args:?}");
let name = args.name.clone();
run_tool_envelope("start", log, async move {
let (resp, retries) = self
.dispatch(hive_core_agent_sock::Request::Start { name: args.name })
.await;
annotate_retries(
format_ack(resp, "start", format!("started {name}")),
retries,
)
})
.await
}
#[tool(
description = "Fetch recent journal log lines for a sub-agent container. Useful \
for diagnosing MCP server registration failures, startup crashes, plugin install \
errors, or any harness issue you can't see from inside the container. Pass the \
plain logical agent name (e.g. `gui`) — hive-c0re resolves the machine name. \
`lines` defaults to 50 (max capped at 500 on the host side)."
)]
async fn get_logs(&self, Parameters(args): Parameters<GetLogsArgs>) -> String {
let log = format!("{args:?}");
let agent = args.agent.clone();
run_tool_envelope("get_logs", log, async move {
let lines = args.lines.map(|n| n.min(500));
let (resp, retries) = self
.dispatch(hive_core_agent_sock::Request::GetLogs {
agent: agent.clone(),
lines,
})
.await;
let s = match resp {
Ok(hive_core_agent_sock::Response::Logs { content }) => {
if content.is_empty() {
format!("(no journal output for {agent})")
} else {
content
}
}
other => reply_err(other, "get_logs"),
};
annotate_retries(s, retries)
})
.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 — call `update` on each affected agent \
separately after the approval resolves."
Does NOT trigger container rebuilds — the operator rebuilds affected agents \
after the approval resolves."
)]
async fn request_update_meta_inputs(
&self,