agents: read the memory cap at boot, not from the build
An agent's memory cap was rendered into its flake (`memoryMaxBytes` → `services.hyperhive.agent.claudeMemoryMaxBytes`), so the closure depended on it and a changed cap reached the JSC heap ceiling and the subagent `MemoryHigh=` only after a rebuild. hive-c0re now writes the effective `MemoryMax=` in bytes to the agent's runtime dir (`/run/hive/memory-max-bytes` inside) in `write_dropins`, from the same value the drop-in gets. The file is removed for `infinity` or a RAM percentage, and left alone when the drop-in is kept. Inside the container, `nix/agent-modules/memory-cap.nix` reads it at boot: an environment generator sets `BUN_JSC_forceRAMSize` (75%) for every unit and `machinectl shell`, `/etc/set-environment` exports it in login shells, and a unit generator writes `MemoryHigh=` (2/3) as a drop-in for `hive-subagent-daemon`. The render drops `memoryMaxBytes`, the agent option is removed (both spellings fail eval with a removal message), and `effective_memory_bytes_from` goes with its only caller. Part of #5081 (slice S0-2).
This commit is contained in:
parent
c8df666bf3
commit
0d619e215e
15 changed files with 416 additions and 287 deletions
|
|
@ -22,39 +22,17 @@ let
|
|||
agentHarness
|
||||
;
|
||||
|
||||
# The memory-pressure pair. `claudeMemoryMaxBytes` is the container's own
|
||||
# cap, rendered per agent by meta.rs — the capped arm is the one every
|
||||
# real deploy gets, the uncapped arm is a hive that set `infinity` or a
|
||||
# RAM percentage and so hands the module no byte count to size against.
|
||||
agentCapped = agent { claudeMemoryMaxBytes = 8589934592; };
|
||||
|
||||
agentUncapped = agent { };
|
||||
agentDefault = agent { };
|
||||
|
||||
agentBashDaemon = machine: machine.systemd.services.hive-bash-daemon;
|
||||
agentSubagentDaemon = machine: machine.systemd.services.hive-subagent-daemon;
|
||||
cases = [
|
||||
{
|
||||
# Two thirds of the container's cap, and a SOFT ceiling: the daemon's
|
||||
# cgroup holds every nested claude, so `MemoryHigh=` throttles the
|
||||
# subagent set as a whole before the kernel picks a victim, while the
|
||||
# absent `MemoryMax=` is what still lets one subagent use more than
|
||||
# its share on a container that has the memory free. A hard cap here
|
||||
# would trade the silent kill for a guaranteed wall, which is the
|
||||
# shape this deliberately does not have.
|
||||
name = "the subagent daemon throttles at two thirds of the container's memory";
|
||||
ok =
|
||||
let
|
||||
c = (agentSubagentDaemon agentCapped).serviceConfig;
|
||||
in
|
||||
c.MemoryHigh == "5726623061" && !(c ? MemoryMax);
|
||||
}
|
||||
{
|
||||
# What makes the case above able to fail. With no byte count for the
|
||||
# container there is no fraction to take, and a hardcoded fallback
|
||||
# would be a number about some other hive's machine — so the unit
|
||||
# renders no ceiling at all rather than a fabricated one.
|
||||
name = "an agent with no byte-valued memory cap renders no subagent ceiling";
|
||||
ok = !((agentSubagentDaemon agentUncapped).serviceConfig ? MemoryHigh);
|
||||
# The ceiling is sized at boot from the container's cap
|
||||
# (../agent-modules/memory-cap.nix). One in the unit would be the cap at
|
||||
# the last rebuild, and every agent would carry its own hive's number.
|
||||
name = "the subagent daemon's memory ceiling is not built into its unit";
|
||||
ok = !((agentSubagentDaemon agentDefault).serviceConfig ? MemoryHigh);
|
||||
}
|
||||
{
|
||||
# The sign is the whole property, and it is easy to write backwards:
|
||||
|
|
@ -68,8 +46,8 @@ let
|
|||
name = "the OOM killer prefers a subagent over the agent's own session";
|
||||
ok =
|
||||
let
|
||||
sub = (agentSubagentDaemon agentUncapped).serviceConfig.OOMScoreAdjust;
|
||||
own = (agentHarness agentUncapped).serviceConfig.OOMScoreAdjust;
|
||||
sub = (agentSubagentDaemon agentDefault).serviceConfig.OOMScoreAdjust;
|
||||
own = (agentHarness agentDefault).serviceConfig.OOMScoreAdjust;
|
||||
in
|
||||
sub > 0 && own < 0 && sub > own;
|
||||
}
|
||||
|
|
@ -81,13 +59,13 @@ let
|
|||
# chose, which is the blast radius that made the preference a bad
|
||||
# trade in the first place.
|
||||
name = "one subagent losing the OOM draw does not stop the daemon";
|
||||
ok = (agentSubagentDaemon agentUncapped).serviceConfig.OOMPolicy == "continue";
|
||||
ok = (agentSubagentDaemon agentDefault).serviceConfig.OOMPolicy == "continue";
|
||||
}
|
||||
{
|
||||
# Each bash task is a process in the daemon's cgroup; under `stop` one
|
||||
# OOM-killed task would end every running sibling task.
|
||||
name = "one bash task losing the OOM draw does not stop the daemon";
|
||||
ok = (agentBashDaemon agentUncapped).serviceConfig.OOMPolicy or null == "continue";
|
||||
ok = (agentBashDaemon agentDefault).serviceConfig.OOMPolicy or null == "continue";
|
||||
}
|
||||
];
|
||||
in
|
||||
|
|
|
|||
Loading…
Reference in a new issue