# `checks.module-eval-agent-memory` — see ./lib.nix for the shared # rationale (why this suite exists, naming convention, "evaluates # not executes"). { pkgs, lib, self, nixosSystem, }: let inherit (import ./lib.nix { inherit pkgs lib self nixosSystem ; }) agent runGroup agentHarness ; agentDefault = agent { }; agentBashDaemon = machine: machine.systemd.services.hive-bash-daemon; agentSubagentDaemon = machine: machine.systemd.services.hive-subagent-daemon; cases = [ { # 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: # a HIGHER OOMScoreAdjust is a MORE likely victim. So the subagent # daemon must be strictly above zero and the harness strictly below # it — swap the two and the kernel takes the agent's own turn first, # which is worse than setting nothing at all. Both nested `claude` # processes inherit their unit's value, so ordering the units orders # the sessions. Held as an inequality rather than two constants: what # must not drift is the order, not the magnitudes. name = "the OOM killer prefers a subagent over the agent's own session"; ok = let sub = (agentSubagentDaemon agentDefault).serviceConfig.OOMScoreAdjust; own = (agentHarness agentDefault).serviceConfig.OOMScoreAdjust; in sub > 0 && own < 0 && sub > own; } { # The pair the case above only makes sense with: preferring this unit # as the victim is an improvement only if losing one subagent isn't # losing all of them. systemd's default `stop` would take the daemon # and every sibling session down with whichever process the kernel # 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 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 agentDefault).serviceConfig.OOMPolicy or null == "continue"; } ]; in runGroup "agent-memory" cases