hive-c0re: the per-agent resource limit override is gone
The hive no longer sets an agent's CPU or memory cap: caps come from the swarm, else the hive-wide defaults. The container load tab was the only hive UI that set them, and its figures are already exported as OTEL metrics, so with the tab gone nothing wrote meta/resource-limits.json.
Removed: the file's reader and writer, the `hivectl agent set-limits` verb with its host-socket request, the `GET /api/container-resources` and `POST /api/resource-limits/{name}` dashboard endpoints, and the `cpu_quota`/`memory_max` fields of the container view that only the tab read. A leftover resource-limits.json is ignored.
This commit is contained in:
parent
a2540be98b
commit
90e33cbd19
19 changed files with 161 additions and 828 deletions
|
|
@ -170,7 +170,7 @@ async fn dispatch(req: &HostRequest, coord: Arc<Coordinator>) -> HostResponse {
|
|||
// question is only answerable on this side of the socket —
|
||||
// see the request's doc comment for why the client asks.
|
||||
HostRequest::AgentExists { name } => HostResponse::agent_exists(agent_exists(name)?),
|
||||
HostRequest::AgentStatus => handle_agent_status(&coord).await,
|
||||
HostRequest::AgentStatus => handle_agent_status().await,
|
||||
// Intercepted in `handle()` before a request ever reaches
|
||||
// `dispatch` — see `stream_agent_status`'s doc comment. This
|
||||
// arm exists only so the match stays exhaustive; reaching it
|
||||
|
|
@ -184,19 +184,6 @@ async fn dispatch(req: &HostRequest, coord: Arc<Coordinator>) -> HostResponse {
|
|||
// federation peer-config block) and open the web surfaces
|
||||
// (`hivectl open`).
|
||||
HostRequest::Urls => HostResponse::urls(hive_urls()),
|
||||
HostRequest::SetResourceLimits {
|
||||
name,
|
||||
cpu_quota,
|
||||
memory_max,
|
||||
} => {
|
||||
handle_set_resource_limits(
|
||||
&coord,
|
||||
name,
|
||||
cpu_quota.as_deref(),
|
||||
memory_max.as_deref(),
|
||||
)
|
||||
.await?
|
||||
}
|
||||
HostRequest::MatrixInvite { user, room } => {
|
||||
handle_matrix_invite(user, room.as_deref()).await?
|
||||
}
|
||||
|
|
@ -293,8 +280,8 @@ async fn handle_set_paused(
|
|||
}
|
||||
|
||||
/// Collect per-agent status rows for `hivectl status` and the dashboard.
|
||||
async fn handle_agent_status(coord: &Arc<Coordinator>) -> HostResponse {
|
||||
match crate::container_view::build_all(&coord.hive_env()).await {
|
||||
async fn handle_agent_status() -> HostResponse {
|
||||
match crate::container_view::build_all().await {
|
||||
Ok(views) => HostResponse::agent_statuses(
|
||||
views
|
||||
.into_iter()
|
||||
|
|
@ -380,66 +367,6 @@ fn agent_exists(name: &hive_types::Ident) -> Result<bool> {
|
|||
.with_context(|| format!("check agent state dir for {name}"))
|
||||
}
|
||||
|
||||
/// Validate + persist an agent's CPU/memory overrides, then re-apply the
|
||||
/// drop-in so the change lands without waiting for a rebuild.
|
||||
///
|
||||
/// Validation is here rather than only in `hivectl` because the values
|
||||
/// are written verbatim into the systemd drop-in: a malformed
|
||||
/// `CPUQuota=` makes systemd reject the unit, and the container stops
|
||||
/// starting. Every client (CLI, dashboard, anything later) goes through
|
||||
/// this path, so the guard belongs on this side of the socket.
|
||||
///
|
||||
/// `None`/`None` removes the agent's entry, returning it to the
|
||||
/// hive-wide defaults.
|
||||
async fn handle_set_resource_limits(
|
||||
coord: &Arc<Coordinator>,
|
||||
name: &hive_types::Ident,
|
||||
cpu_quota: Option<&str>,
|
||||
memory_max: Option<&str>,
|
||||
) -> Result<HostResponse> {
|
||||
if let Some(value) = cpu_quota {
|
||||
crate::resource_limits::validate_cpu_quota(value).map_err(anyhow::Error::msg)?;
|
||||
}
|
||||
// `validate_memory_max` returns the value to store, not just an
|
||||
// ok/err verdict: a friendly spelling like "8GB" is accepted but
|
||||
// normalized to the "8G" systemd's own parser actually takes.
|
||||
let memory_max = memory_max
|
||||
.map(crate::resource_limits::validate_memory_max)
|
||||
.transpose()
|
||||
.map_err(anyhow::Error::msg)?;
|
||||
tracing::info!(%name, ?cpu_quota, ?memory_max, "set_resource_limits");
|
||||
let limits = crate::resource_limits::AgentLimits {
|
||||
cpu_quota: cpu_quota.map(ToOwned::to_owned),
|
||||
memory_max,
|
||||
};
|
||||
// Goes through `meta::commit_resource_limits`, not the bare
|
||||
// `resource_limits::set_limits`: the write has to be staged +
|
||||
// committed under `META_LOCK` or it leaves the meta working tree
|
||||
// dirty for the next `sync_agents` to trip over.
|
||||
crate::meta::commit_resource_limits(name.as_str(), &limits).await?;
|
||||
|
||||
// Re-apply the drop-in straight away — same three lines as the job
|
||||
// queue's `WriteDropin` node. Without this the new values would sit
|
||||
// in the JSON until the agent's next spawn or rebuild.
|
||||
let agent_dir = crate::paths::agent_runtime_dir(name.as_str());
|
||||
let hive = coord.hive_env();
|
||||
let paths = Coordinator::agent_paths(name.as_str(), agent_dir)?;
|
||||
crate::lifecycle::write_dropins(name.as_str(), &hive, &paths).await?;
|
||||
|
||||
let (cpu, mem) = crate::resource_limits::effective_with_swarm(
|
||||
&crate::resource_limits::resource_limits_path(),
|
||||
&crate::resource_limits::swarm_limits_path(),
|
||||
name.as_str(),
|
||||
&hive.agent_cpu_quota,
|
||||
&hive.agent_memory_max,
|
||||
)?;
|
||||
Ok(HostResponse::messages(vec![format!(
|
||||
"{name}: CPUQuota={cpu} MemoryMax={mem} — the cgroup cap itself is live now; the \
|
||||
Claude/JSC heap ceiling and the subagent MemoryHigh= derived from it follow at the \
|
||||
container's next start (restart it if it's running and needs them now)"
|
||||
)]))
|
||||
}
|
||||
|
||||
/// Guard: matrix provisioning needs a homeserver to provision against.
|
||||
///
|
||||
/// Answers "is one configured", not "is one running here": the address is
|
||||
|
|
|
|||
Loading…
Reference in a new issue