Skip to content

feat(agent): enforce per-sandbox cgroup v2 memory limits - #3142

Open
daryllimyt wants to merge 15 commits into
mainfrom
feat/agent-sandbox-cgroup-limits
Open

feat(agent): enforce per-sandbox cgroup v2 memory limits#3142
daryllimyt wants to merge 15 commits into
mainfrom
feat/agent-sandbox-cgroup-limits

Conversation

@daryllimyt

@daryllimyt daryllimyt commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Why

A production agent-executor pod was kernel-OOM-killed (exit 137) when ten concurrent agent sandboxes plus the worker process crossed the pod's 16Gi memory limit. Because the container cgroup is the only memory boundary, the kernel group-killed the entire container — all ten in-flight agent sessions failed simultaneously and, with maximum_attempts=1, every parent workflow failed with them.

Today the only per-sandbox control is rlimit_as, which is per-process virtual address space: it bounds neither the sandbox process tree in aggregate nor tmpfs, so nothing stops total usage short of the pod limit.

What

  • Per-sandbox cgroup v2 memory caps. When enabled and available, the nsjail config now includes use_cgroupv2 / cgroup_mem_max (= TRACECAT__AGENT_SANDBOX_MEMORY_MB) / cgroup_mem_swap_max: 0, so each sandbox tree gets its own kernel-enforced memory.max and a runaway session OOMs alone instead of taking down the worker and its neighbors. Field names verified against the nsjail commit pinned in the Dockerfile. Existing rlimit_* lines are kept as defense in depth.
  • One-time cgroup preparation at worker startup (tracecat/agent/sandbox/cgroup.py): moves worker PIDs into a leaf cgroup, enables the memory controller on the namespace root (the cgroup v2 no-internal-process rule), and verifies with a throwaway probe cgroup. Any failure — including macOS/dev and non-privileged environments — logs one warning and falls back to running without cgroup limits; sandbox launches are never blocked by cgroup setup.
  • Memory-budget clamp: max_concurrent_activities is clamped to (container memory.max − TRACECAT__AGENT_EXECUTOR_MEMORY_RESERVE_MB) / TRACECAT__AGENT_SANDBOX_MEMORY_MB, with a loud error log when the configured value exceeds the budget. Clamping (not crashing) is deliberate so a config mistake cannot crash-loop the fleet.
  • Readiness sentinel: best-effort file written after the Temporal worker starts and removed on shutdown, for a future Kubernetes exec readiness probe (the pod currently reports Ready ~6s before the worker actually polls).

New env vars: TRACECAT__AGENT_SANDBOX_CGROUP_ENABLED (default true, graceful fallback), TRACECAT__AGENT_EXECUTOR_MEMORY_RESERVE_MB (default 4096), TRACECAT__AGENT_EXECUTOR_READY_FILE.

Testing

  • 25 unit tests: nsjail config emission (enabled/disabled/unavailable), clamp math table (incl. unlimited/missing/garbage memory.max), probe failure matrix on fake cgroup trees (permission errors, vanishing PIDs), readiness sentinel lifecycle with a mocked worker.
  • ruff check, ruff format, basedpyright --warnings clean on all changed files including tests.
  • Not yet exercised on a real privileged container; recommend a staging soak before enabling in production (flag defaults on but degrades gracefully where cgroupfs is unavailable).

Summary by cubic

Adds per-sandbox cgroup v2 memory limits for agent sandboxes and a container-aware concurrency budget so a runaway session OOMs alone. Includes a Compose sandbox overlay that delegates cgroups only when nsjail is enabled, plus a readiness sentinel and healthchecks.

  • New Features

    • Detect the container’s cgroup v2 root from /proc/self/cgroup; set use_cgroupv2, cgroupv2_mount, cgroup_mem_max (from TRACECAT__AGENT_SANDBOX_MEMORY_MB), and cgroup_mem_swap_max: 0 in nsjail when available.
    • Prepare cgroups at worker start: move worker PIDs into a main leaf, enable the memory controller, verify with a probe cgroup; on failure, log once and continue without per-sandbox limits (root kept for budget checks).
    • Harden detection/delegation: require a unified v2 entry and an existing cgroup.controllers; keep the pre-move root authoritative; treat ancestor limits as boundaries; gate root-side delegation on TRACECAT__AGENT_SANDBOX_CGROUP_ENABLED=true and TRACECAT__DISABLE_NSJAIL=false.
    • Enforce memory budget: clamp max_concurrent_activities to (min finite memory.max across the cgroup and its ancestors − TRACECAT__AGENT_EXECUTOR_MEMORY_RESERVE_MB) / TRACECAT__AGENT_SANDBOX_MEMORY_MB; if no slot fits, fail fast.
    • Apply the prepared cgroup mount to agent sandboxes and stdio MCP probe sandboxes; cap probe memory at min(1024, TRACECAT__AGENT_SANDBOX_MEMORY_MB). Action sandboxes are unchanged.
    • Readiness sentinel: clear any stale file before startup validation, write after the worker starts, remove on shutdown; Compose healthchecks read TRACECAT__AGENT_EXECUTOR_READY_FILE.
    • Compose sandbox overlay: start as root with an entrypoint that remounts /sys/fs/cgroup rw, delegates the container’s cgroup v2 subtree to apiuser only when nsjail is enabled, then drops privileges.
  • Migration

    • No action required; limits auto-enable and fall back when unavailable.
    • Worker exits early if the container limit minus reserve cannot fit one sandbox; increase container memory or tune TRACECAT__AGENT_EXECUTOR_MEMORY_RESERVE_MB / TRACECAT__AGENT_SANDBOX_MEMORY_MB.
    • Config validation added: sandbox memory (>=1), executor reserve (>=0), and worker concurrency (>=1).
    • To enable nsjail with cgroups in Compose, set TRACECAT__DISABLE_NSJAIL=false and layer docker-compose.sandbox.yml; it configures privileges and cgroup delegation.

Written for commit 594c9c5. Summary will update on new commits.

Review in cubic

Ten concurrent agent sandboxes on one executor pod crossed the pod
memory limit and the kernel group-killed the container, failing all
in-flight sessions at once. rlimit_as bounds neither the sandbox
process tree nor tmpfs, so aggregate memory was unbounded.

- emit use_cgroupv2/cgroup_mem_max nsjail config (fields verified
  against the pinned nsjail commit) so each sandbox tree gets its own
  kernel-enforced memory.max and a runaway session fails alone
- prepare the cgroup root at worker startup (move PIDs to a leaf,
  enable the memory controller, verify with a probe cgroup); any
  failure logs one warning and falls back to no cgroup limits
- clamp max_concurrent_activities to the container memory budget
  (limit - reserve) / per-sandbox cap instead of overcommitting
- write a best-effort readiness sentinel for a future exec probe
@daryllimyt daryllimyt added enhancement New feature or request engine Improvements or additions to the workflow engine labels Jul 28, 2026
@zeropath-ai

zeropath-ai Bot commented Jul 28, 2026

Copy link
Copy Markdown

No security or compliance issues detected. Reviewed everything up to 594c9c5.

Security Overview
Detected Code Changes
Change Type Relevant files
Enhancement ► Dockerfile
    Add agent-executor-entrypoint.sh and make it executable
► docker/scripts/agent-executor-entrypoint.sh
    Introduce per-sandbox cgroup delegation entrypoint
Enhancement ► docker-compose.dev.yml
    Add environment vars for sandbox memory settings and readiness file
► docker-compose.dev.yml
    Add healthcheck for agent executor readiness
Enhancement ► docker-compose.local.yml
    Add environment vars for sandbox memory settings and readiness file
► docker-compose.local.yml
    Add healthcheck for agent executor readiness
Enhancement ► docker-compose.sandbox.yml
    Configure nsjail sandbox with privileges, entrypoint, env vars, and healthcheck for readiness
Enhancement ► docker-compose.yml
    Documentation comment about nsjail and sandbox privileges
► docker-compose.yml
    Add environment variables for sandbox memory settings and readiness file
Enhancement ► tests/unit/test_agent_executor_worker_resources.py
    Add tests for agent executor readiness sentinel and related behavior
Enhancement ► tests/unit/test_agent_sandbox_cgroup.py
    Add extensive unit tests for agent sandbox cgroup logic (detectors, preparation, memory limits, clamping)
Enhancement ► tests/unit/test_agent_sandbox_config.py
    Add tests for build_agent_nsjail_config including memory limit integration
Enhancement ► tests/unit/test_agent_sandbox_litellm.py
    Update tests to incorporate cgroup-related wiring and NSJAIL integration toggles

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 94fa42c242

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread tracecat/agent/sandbox/config.py
Comment thread tracecat/agent/executor_worker.py Outdated
A SIGKILLed worker cannot run its cleanup, and the sentinel may live on
a filesystem that survives container restarts, so a file-existence
readiness probe would report the restarted worker ready during startup.
Remove any existing sentinel before startup work begins and remove it
unconditionally on exit.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 842e26f357

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread tracecat/config.py Outdated
Address review blockers on the per-sandbox cgroup limits:

- Derive the cgroup v2 root from /proc/self/cgroup instead of assuming
  /sys/fs/cgroup is the delegated root, so privileged host-namespace
  containers (Kubernetes/containerd) never prepare children in the node
  hierarchy. Preparation returns a typed PreparedCgroup; the root is
  retained on preparation failure so budget validation still reads the
  container's memory.max.
- Fail fast at worker startup when reserve + one sandbox exceeds the
  container memory limit instead of forcing concurrency to one.
- Apply the prepared cgroup to stdio MCP probe sandboxes and cap probe
  memory at the per-slot sandbox limit.
- Validate sandbox memory (>=1), executor reserve (>=0), and worker
  concurrency (>=1) at the configuration boundary.
- Expose the sandbox memory env vars in compose files and add an
  agent-executor healthcheck on the readiness sentinel.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 72679fb114

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread docker-compose.yml Outdated
Interpolate TRACECAT__AGENT_EXECUTOR_READY_FILE into both the
agent-executor environment and the healthcheck test so an operator
override keeps the worker and healthcheck pointed at the same sentinel
path.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7a037f5ce0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread tracecat/agent/executor_worker.py
Move the stale-sentinel cleanup ahead of concurrency validation and the
memory-budget check so a fail-fast crashloop cannot leave a SIGKILLed
predecessor's sentinel visible to healthchecks.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ed5ce8b2b4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread tracecat/agent/sandbox/cgroup.py
Start the agent-executor entrypoint as root in the sandbox Compose
overlay, remount /sys/fs/cgroup read-write, chown the cgroup v2
delegation files to apiuser, and drop privileges via setpriv before the
worker starts. Without delegation the root-owned cgroupfs made
preparation fail with EACCES as UID 1001, silently disabling per-sandbox
memory limits in the only compose deployment that enables nsjail.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ffbe671d1b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread docker/scripts/entrypoint.sh Outdated
setpriv changes only IDs, so the delegation path left HOME=/root for the
UID 1001 worker, breaking HOME-dependent tooling (npx MCP probes, SSH
helpers). Set HOME/USER/LOGNAME explicitly instead of --reset-env, which
would clear the service configuration environment.
Main removed the shared entrypoint.sh migration wrapper (#3147), which
carried this branch's cgroup delegation logic. Reconcile by moving
delegation into docker/scripts/agent-executor-entrypoint.sh, baked into
both image stages and referenced only by the sandbox overlay's
entrypoint override. The script now derives the container's own cgroup
v2 directory from /proc/self/cgroup, so it delegates the correct
subtree under both private (compose) and host (privileged Kubernetes)
cgroup namespaces and never touches anything above it; environments
without the needed privileges degrade to rlimit-only limits unchanged.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 080c889b35

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread docker/scripts/agent-executor-entrypoint.sh Outdated
Comment thread tracecat/agent/sandbox/cgroup.py Outdated
Require a unified cgroup v2 entry and an existing cgroup.controllers
file before the entrypoint delegates ownership, so cgroup v1 hosts never
get their cgroupfs root chowned, and drop the basename-based main guard
in detect_cgroup_root: a runtime-assigned cgroup legitimately named main
must be treated as the container boundary, not escaped to its parent.
The cached pre-move root remains authoritative for budget validation.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f544e7e227

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread docker/scripts/agent-executor-entrypoint.sh
Skip the root-side remount/chown when
TRACECAT__AGENT_SANDBOX_CGROUP_ENABLED is falsy (mirroring env_bool),
matching the Python-side preparation skip. The privilege drop stays
unconditional so a root-started container never reaches the worker as
root regardless of the flag.
@blacksmith-sh

This comment has been minimized.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 17c69a92fe

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread tracecat/agent/sandbox/cgroup.py Outdated
Walk every visible hierarchy level from the executor's cgroup up to the
cgroupfs mount and take the minimum finite memory.max, so task-scoped
limits (e.g. an ECS task limit with no container-level limit) are not
misread as unlimited when the leaf reports max. PreparedCgroup carries
the cgroupfs boundary so the walk never leaves the mount.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ab6c868ce9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread docker-compose.sandbox.yml

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7276d2fb67

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread docker-compose.yml
@blacksmith-sh

This comment has been minimized.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cb99c561ef

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread docker/scripts/agent-executor-entrypoint.sh Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 594c9c5a4f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread tracecat/agent/executor_worker.py
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

engine Improvements or additions to the workflow engine enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant