10 agents, 30 GB of RAM and one queue: how they share without clashing A developer released Sluice 0.12.2, a tool that funnels the tests, builds and heavy commands of up to 10 concurrent AI agents on a Linux machine into a single queue with a shared CPU and memory budget, running each check once per git tree. After 11 global out-of-memory kills and 7 systemd-oomd kills over 37 hours on a 30 GiB machine, version 0.10.0 moved enforcement into the kernel by writing each job's cgroup memory.max every second, and the next 387 jobs ran with no OOM and swap never above 36 percent. A later incident on the developer's desktop saw GNOME Shell hang and require a SysRq reboot, which the developer has not yet explained. For the two weeks after I published Sluice 0.8.0, I ran it every day on my two machines, both with 30 GiB of RAM, with up to 10 AI agents working at once on one of them. Sluice https://github.com/josegarridodigio/sluice puts the tests, builds and heavy commands of all the AI agents on a Linux machine into one queue, with a shared CPU and memory budget, and runs each check once per git tree; I told how it started in the first article https://josegarrido.dev/en/sluice-shared-queue-so-your-agents-stop-repeating-tests/ . Even with that queue, my agents' test suites could still run a machine out of memory. How they share it now, in short: the queue decides who runs, the kernel's ceilings keep each job within its share, and the desktop guard keeps the desktop's share free. This is what I learned, and each lesson became a version, from 0.9.0 to 0.12.2: NODE OPTIONS go into the cache key. A week after publishing 0.8.0 I went through what had happened on my second machine: 12 threads, 30 GiB of RAM, 8 GiB of swap, and between 4 and 10 agents working at once. In 37 hours there were 11 global out-of-memory kills, 7 kills by systemd-oomd , 16 test jobs killed for memory and one agent session killed along with them. Swap was full every time. There were four causes: --filter or --test-concurrency=1 . That was a new command for Sluice, with no history, so it guessed 1.3–1.7 GiB for a suite that needs 11–14 GiB. systemd-oomd window was 5 s, and it ended an agent 6 s after a test job was killed. Version 0.9.0 changed the memory admission: sluice status now says where the reserve comes from, and flags a job that started alone beyond its estimate. No config change needed. With 0.9.0 the same machine hit the next wall. Between 30 October and 1 November one test suite ran 61 times, every time alone on the machine and beyond its estimate: 46 passed, 9 failed their tests and 6 were killed for memory. Each of those six kills was a global OOM, and before each one the system journal fell silent for 22, 30, 37, 67, 72 and about 150 seconds. The whole machine was thrashing, and systemd-oomd and Sluice's own guard were stalled with everything else: anything that runs in userspace arrives too late. Version 0.10.0 hands that job to the kernel. Every second the daemon writes the memory.max of each job's cgroup, so that all jobs together can never take the machine below a floor. The kernel enforces it even while the daemon is frozen. It is on by default with full containment: ~/.config/sluice/config.toml admission memory reserve = "auto" a tenth of the RAM: 3 GiB on 30 GiB memory ceiling = true memory floor = "auto" half the reserve: 1.5 GiB on 30 GiB sluice status shows each running job's ceiling. From 2 November, the next 387 jobs on that machine ran with no OOM and no memory kill, and swap never went above 36 %. The trade-off is deliberate: a run that used to pass by pushing the rest of the machine into swap is now killed inside its own job. The machine stays usable, and the message tells the agent to retry once memory is free, or to tell you that the gate doesn't fit. On 5 November it was my development machine's turn: 20 threads, 30 GiB of RAM, 8 GiB of swap, GNOME on Wayland, Sluice 0.10.0. Between 18:40 and 19:20 a frontend test suite of about 14.5–15 GiB started at three times its estimate, Playwright jobs took 7.7–9.4 GiB each, and a production build ran outside Sluice at the same time. The machine reached 21 GiB used 66 % , swap went from 3.6 to 5.2 GiB, swap-out peaked at 1,759 pages per second, load hit 32, and GNOME Shell's input lag reached 61–80 ms. There was no OOM and nothing was killed: the 0.10.0 ceiling never engaged. The shell ended up not responding, and at 20:37 I rebooted with SysRq. I still don't know what hung the shell: it stayed hung after the memory recovered. But looking into it I found something else. That day systemd had created 385 Chromium scopes on the machine, and that is where the browsers started by the test jobs had gone: outside their jobs. This is what happens. At startup, every Chromium browser process, headless included, prepares the connection to the XDG desktop portal. The portal needs an app id, and Chromium gets one by asking the systemd user manager, over the session bus, for a new scope with its own pid: StartTransientUnit . The process ends up in app.slice/app-org.chromium.Chromium-