manager: start/restart at will, no approval; refuse self

new manager tools mcp__hyperhive__{start,restart} that delegate to the
existing lifecycle::start / lifecycle::restart on the host. kill was
already at the manager's discretion; rounding out start + restart for
parity so day-to-day container care doesn't have to round-trip through
the operator.

guard: refuse self-targeting on kill/start/restart — the manager would
just be cutting its own legs. spawn (request_spawn) and config changes
(request_apply_commit) still go through the approval queue, since those
are the actual gate. prompt + claude.md updated to make the boundary
explicit. kill now also emits HelperEvent::Killed (it didn't before).
This commit is contained in:
müde 2026-05-15 18:57:25 +02:00
parent d943bddd9e
commit ac1b5fde8e
6 changed files with 104 additions and 3 deletions

View file

@ -131,6 +131,11 @@ async fn dispatch(req: &ManagerRequest, coord: &Coordinator) -> ManagerResponse
}
ManagerRequest::Kill { name } => {
tracing::info!(%name, "manager: kill");
if name == crate::lifecycle::MANAGER_NAME {
return ManagerResponse::Err {
message: "refusing to kill the manager".into(),
};
}
let result: Result<()> = async {
lifecycle::kill(name).await?;
coord.unregister_agent(name);
@ -138,6 +143,39 @@ async fn dispatch(req: &ManagerRequest, coord: &Coordinator) -> ManagerResponse
}
.await;
match result {
Ok(()) => {
coord.notify_manager(&hive_sh4re::HelperEvent::Killed {
agent: name.clone(),
});
ManagerResponse::Ok
}
Err(e) => ManagerResponse::Err {
message: format!("{e:#}"),
},
}
}
ManagerRequest::Start { name } => {
tracing::info!(%name, "manager: start");
if name == crate::lifecycle::MANAGER_NAME {
return ManagerResponse::Err {
message: "refusing to start the manager from itself".into(),
};
}
match lifecycle::start(name).await {
Ok(()) => ManagerResponse::Ok,
Err(e) => ManagerResponse::Err {
message: format!("{e:#}"),
},
}
}
ManagerRequest::Restart { name } => {
tracing::info!(%name, "manager: restart");
if name == crate::lifecycle::MANAGER_NAME {
return ManagerResponse::Err {
message: "refusing to restart the manager from itself".into(),
};
}
match lifecycle::restart(name).await {
Ok(()) => ManagerResponse::Ok,
Err(e) => ManagerResponse::Err {
message: format!("{e:#}"),