groundy
infrastructure & runtime

Why cgroups, not permission prompts, bound AI agent CPU and memory

AgentCgroup shows tool calls drive 15.4x memory spikes. Framework prompts gate actions but do not limit CPU or memory. Wrap agent process trees in cgroups to prevent OOM.

11 min···3 sources ↓

Claude Code, Cursor, and custom coding agents execute their tool calls directly on your host, and nothing in a framework permission prompt limits how much CPU or memory those calls consume. The mechanism that does limit them has been in the Linux kernel since 2008: cgroups. The operational fix is to wrap every agent process tree in explicit cpu, memory, and io limits before one runaway tool call OOMs the workloads next to it.

Why doesn’t a permission prompt bound CPU or memory?

Permission prompts gate which actions an agent may take, while resource consumption is governed by the OS scheduler, and the two mechanisms never intersect. When an agent framework spawns a shell, every tool call becomes a process on the host with the same scheduling rights as your editor, your database, and whatever else shares the machine. Approving a command in a prompt confirms intent. It says nothing about what that command will cost once it runs.

The distinction matters more now because agentic tools have moved onto shared infrastructure. Coding agents such as Claude Code and Cursor run their tool calls on the host from the terminal, and sandboxed variants scope what the model can touch by restricting file and folder access. That scoping does not extend to what the agent can eat. A make -j against a monorepo, a package install that expands to a few gigabytes on disk, or a test suite that forks per file will happily consume every page the kernel will give it, sandbox or no sandbox.

The failure mode is not subtle. On a shared dev box or CI runner, memory exhaustion triggers the OOM killer, which selects victims by heuristic rather than by blame. The agent that caused the pressure is one candidate; so is the build job in the adjacent lane. This is the gap that AgentCgroup (arXiv:2602.09345) formalizes: the moment a framework spawns a shell, governance has to move from the framework’s prompt layer to the kernel, because the kernel is the only layer that can actually enforce a bound.

What do cgroups actually enforce?

Cgroups are a Linux kernel feature that limits, accounts for, and isolates the CPU, memory, and disk I/O usage of a collection of processes, with enforcement done by the kernel itself rather than by any userspace policy. That definition, per Wikipedia’s cgroups entry, contains the two properties that make cgroups the right tool for agents. First, the unit of control is a collection of processes, so an agent’s entire tree, including the shell it spawns, the compiler the shell invokes, and the test runner the compiler’s output feeds, is accounted together. Second, enforcement happens in the scheduler and memory manager, which means it applies regardless of whether the process cooperates.

None of this is new capability. Google engineers began the work in 2006 under the name “process containers,” per the same cgroups history, and container runtimes have applied cgroup limits routinely for years. The gap AgentCgroup identifies is not a missing kernel feature. It is an adoption gap: agent frameworks do not wrap their process trees, so a capability the industry already trusts for containers sits unused for the fastest-growing workload category on dev infrastructure.

The concrete levers are the resource controllers. The vendor documentation for control groups, Red Hat’s RHEL 7 Resource Management Guide, names the main ones: cpu, memory, and blkio, the last being the cgroups v1 block-I/O controller. Two operational details from that documentation carry most of the practical weight. First, controllers organize processes into hierarchies, so limits compose down a tree rather than applying per-process. Second, modern distributions bind cgroup hierarchies to the systemd unit tree, which moves resource settings from individual processes to the application level. An agent launched as a systemd unit inherits its limits automatically, children included.

What did AgentCgroup actually measure?

Tool calls dominate the agent’s resource profile: OS-level execution, meaning tool calls plus container and agent initialization, accounts for 55 to 60 percent of end-to-end task latency, and memory spikes are tool-call-driven with a peak-to-average ratio of up to 15.4x. Both figures come from the AgentCgroup paper, which built its characterization on 144 software-engineering tasks from the SWE-rebench benchmark1 across two LLM models.

The spike number deserves a slow read, because it breaks the intuitive way teams set limits. A 15.4x peak-to-average ratio means a single tool call can briefly demand more than fifteen times the agent’s average memory footprint. An agent that idles at a few hundred megabytes while the model thinks can balloon to several gigabytes for the thirty seconds it runs a build. If you size a limit to the average, the kernel kills the agent mid-task. If you size it to the peak, you reserve capacity that sits idle for most of the session, which on a shared box is capacity your other tenants cannot use. Static limits force a choice between fragility and waste; the spikier the workload, the worse both options get.

The 55 to 60 percent latency share reframes where enforcement cost lives. If OS-level execution is already the majority of end-to-end time, then the resource-control layer is not a tax on an otherwise cheap operation. It is instrumentation and governance of the dominant cost center. It also means enforcement is unlikely to be free. The paper measures where time is spent, not what a control plane adds on top, but any controller that does kernel work per tool call is operating on the part of the pipeline that already determines how the agent feels to use.

AgentCgroup’s response is an intent-driven, eBPF-based resource controller that aligns hierarchical cgroup structures with tool-call boundaries, enforces limits in-kernel via sched_ext and memcg_bpf_ops, and applies runtime-adaptive policies, per the paper’s abstract. The design follows directly from the measurement: if spikes are tool-call-driven, the control points should be tool calls, not processes or time slices. Whether you adopt their controller or not, the characterization is the durable contribution. It tells you that agent memory behavior is bursty, that the bursts are attributable to specific actions, and that the OS is where attribution and enforcement have to happen.

How do the enforcement options compare?

The options differ on what they bound, where enforcement lives, and what the kernel requires; only the cgroup-based rows below can stop a runaway tool call from consuming the host.

Enforcement layerWhat it boundsCPU / memory / IO enforcementKernel requirementSurvives a runaway tool call
Framework permission promptsWhich actions the agent may takeNoneNoneNo
Sandboxed shellFile and folder access, shell actionsNoneNoneNo
cgroups v1cpu, memory, blkio controllersYes, per-controller hierarchiesLong-standing kernels (RHEL 7 era)Yes
cgroups v2 via systemd unitscpu, memory, and I/O controllers inherited by the unit’s process treeYes, unified hierarchyModern distribution defaultsYes
AgentCgroup controllerLimits aligned to tool-call boundaries, runtime-adaptiveYes, in-kernel via eBPFsched_ext, memcg_bpf_opsYes, with per-call granularity

The table is assembled from the brief’s sources: the controller list and systemd binding from the Red Hat resource management guide, and the sandbox framing and eBPF controller from the AgentCgroup paper. The pattern in the last column is the argument of this article in one row each. Anything that operates above the syscall boundary can ask an agent to behave. Only the rows enforced by the scheduler can make it behave.

How do you wrap an agent process tree in a cgroup?

Launch the agent as a systemd unit or scope so the entire process tree inherits cpu, memory, and io limits, which is the same pattern distributions already use to bind cgroup hierarchies to the unit tree. Per the Red Hat guide, this binding moves resource management from the individual process to the application level, and “application” here means the agent and everything it spawns.

In practice this is a launch-time change, not a code change. Instead of running the agent bare on a shared box, run it scoped:

Terminal window
systemd-run --scope --same-dir --wait --collect \
-p MemoryMax=6G -p CPUQuota=200% -p IOWeight=100 \
-- claude

The specific values are a starting posture, not a prescription; the directives themselves (MemoryMax, CPUQuota, IOWeight) are documented in systemd.resource-control. What matters is the shape: the agent, its shell, its compilers, and its test runners all live in one cgroup, so a runaway tool call hits the limit and dies inside the scope instead of pressuring the host’s global memory and triggering an OOM kill against an innocent neighbor. On CI runners, the equivalent move is wrapping the agent step in its own scope or unit per job, so a poisoned task in one lane cannot starve the others. CPU quota prevents a fork-happy build from saturating every core; the IO controller keeps a package-install storm from destroying latency for co-located work.

Sizing deserves the AgentCgroup numbers as input. Given the paper’s measured 15.4x peak-to-average memory ratio from its SWE-rebench characterization, a memory limit set near the agent’s observed average will kill real tasks. Set limits from observed peaks, or accept adaptive control as a requirement and watch for tooling in that space. And budget for the enforcement itself: with OS-level execution already at 55 to 60 percent of task latency per the same measurements, the control plane lives on the hot path.

What are the limits of this evidence?

The headline numbers rest on a narrow base: 144 tasks1 from a single benchmark, SWE-rebench, run across two LLM models, as stated in the AgentCgroup paper. That is enough to establish that tool-call-driven spikiness exists and is large. It is not enough to treat 15.4x or the 55-60 percent range as constants for your workload. A data-analysis agent shelling out to Python, or an agent driving browser automation, will have a different spike profile than a software-engineering benchmark. Measure your own agents before setting limits from someone else’s histogram.

The deployment story has its own caveats. AgentCgroup’s controller depends on sched_ext and memcg_bpf_ops, eBPF facilities that require a very recent kernel, and those are not widely deployed on the shared dev boxes and CI runners where this problem is most acute. Most teams adopting cgroup discipline for agents in the near term will do it with the conventional controllers, not the paper’s adaptive machinery. Meanwhile, the vendor reference most teams will reach for first, the RHEL 7 guide, documents cgroups v1: blkio, split hierarchies, the old layout. If your fleet runs v2, and most current distributions do, the names and mount model differ, and v1-era instructions will mislead you in small, annoying ways.

None of this weakens the core claim. It localizes it. The evidence says agent resource consumption is spiky, tool-attributable, and currently unenforced. The exact magnitude of the spikes and the availability of the fanciest controller are workload- and kernel-dependent. The enforcement layer question is not.

Should you wrap your agents in cgroups now?

Yes: treat every agent process tree like a container and place it in a dedicated cgroup with explicit cpu, memory, and io limits enforced by the OS scheduler, because framework permission prompts gate actions and will not stop an OOM. That holds for Claude Code on a shared dev box, Cursor on a build machine, and custom agent harnesses on CI runners. The mechanism is two decades old, documented since Google started “process containers” in 2006 per Wikipedia’s account, and already trusted for every container you run.

The honest counterweight comes from the same research. The gap AgentCgroup formalizes is an adoption gap, not a capability gap, so no new kernel is required to start; systemd’s unit-tree binding, described in the Red Hat documentation, gets you enforcement today. But enforcement has a cost on a pipeline where OS execution is already the majority of latency, the spike evidence comes from 144 tasks1 and two models, and the paper’s adaptive controller needs kernel features your runners probably lack. Start with static limits sized to observed peaks, measure your own agents, and treat AgentCgroup’s sched_ext machinery as a preview of where control planes are heading rather than a prerequisite.

The kernel feature is nearly two decades old. The only new thing is what you point it at.

Frequently Asked Questions

Can I use cgroups v1 blkio to bound agent memory on modern Linux?

No. The blkio controller only limits block device I/O, not RAM. Memory bounding requires the memory controller, which is part of the unified cgroups v2 hierarchy on modern distributions. Using v1 memory controllers on a v2-only system will fail silently or produce errors, leaving the agent unbounded.

Does AgentCgroup work on standard CI runners today?

Not yet. The AgentCgroup controller relies on sched_ext and memcg_bpf_ops, which require kernel versions and eBPF capabilities not present on most shared CI runners or dev boxes. Teams must currently use static cgroup limits via systemd rather than the paper’s adaptive, tool-call-aligned enforcement.

What happens if I set a memory limit below the agent’s average usage?

The kernel OOM killer will terminate the agent process before it completes its task. Given the 15.4x peak-to-average memory spike ratio observed in tool calls, setting limits based on average idle memory will cause frequent mid-task failures. Limits must be sized to observed peaks or managed via adaptive controllers to avoid killing the agent.

How does cgroup enforcement differ from framework permission prompts?

Permission prompts are userspace checks that gate which actions an agent may initiate, but they do not restrict the CPU or memory those actions consume once executed. Cgroups operate at the kernel scheduler level, enforcing hard limits on resource consumption regardless of the agent’s intent or the framework’s approval state.

sources · 3 cited

  1. Cgroupsen.wikipedia.orgcommunityaccessed 2026-07-23