Declarative Resource Limits per Agent
Status: Partially implemented — operator-level [docker.grants] (memory, memory_reservation, cpus, pids, nofile) are shipped, validated, and translated to Docker flags, and OOM kills are detected, preserved, and surfaced in jackin status; remaining work is one-shot per-field CLI overrides, non-Docker backend translation, and a push-notified (not just pull-queried) OOM signal (Phase 1, Agent Orchestration Program)
Shipped facts
Operators declare resource ceilings in [docker.grants] (global config.toml or per-workspace), not in the role manifest: memory, memory_reservation, cpus, pids, nofile. Each Docker security profile (locked, hardened, standard, compat) has its own baseline for these fields, and an explicit grant can only raise a dimension above the profile default (raise_to_max semantics) — see crates/jackin-runtime/src/runtime/docker_profile.rs. Values are range/consistency-validated (memory_reservation ≤ memory, pids > 0, cpus finite and positive, nofile > 0) before launch. Full schema and examples: Docker Security Profiles.
The resolved grants are translated into real Docker CLI flags at launch (resource_flags in the same file): --memory, --memory-reservation, --cpus, --pids-limit, and --ulimit nofile=N:N. The role manifest's own [docker] table intentionally does not carry these fields (only min_profile, dind, allowed_hosts, capabilities_add) — resource ceilings are an operator/host concern, not something a role author declares.
OOM handling is shipped end-to-end: a kernel OOM kill is classified as AttachOutcome::OomKilled (crates/jackin-isolation/src/finalize.rs), which is treated the same as any non-zero exit — the isolated worktree is preserved by default rather than auto-cleaned. The outcome is persisted on the instance manifest as last_attach_outcome ("oom_killed") and printed by the attach/status text output ("Last attach outcome: oom_killed" in crates/jackin-runtime/src/runtime/attach.rs).
A one-shot profile override already exists: jackin load <role> <target> --docker-profile hardened (crates/jackin/src/cli/role.rs). There is no equivalent per-field override (e.g. --memory-max, --cpus) — only the named profile can be swapped for a single launch.
Remaining work
- Per-field one-shot CLI overrides (
--memory-max/--memory-high/--cpus/--nofileonjackin load) for "this one launch is on a smaller machine," on top of the existing--docker-profileoverride. - Non-Docker backend translation of the same grants (Apple
container, or any future backend) — blocked on selectable sandbox backends landing a second backend to translate into. - Push-notified OOM signal in the operator console/TUI. Today OOM is only visible by pulling
jackin status; there's no proactive banner. Depends on the console resource panel (Phase 2), which is still open.
Out of scope
- Disk I/O limits (
--blkio-weight) and network bandwidth limits — no operator demand yet. - Auto-pausing an OOM-killed agent instead of preserving+exiting — Docker doesn't expose a clean primitive for this.
- Role-manifest-level resource defaults — the shipped design deliberately keeps these operator/host-level, not role-authored; see the research page below for the design alternatives considered.
Related work
- Guide: Docker Security Profiles — full
[docker.grants]schema, profile table, examples - Research: Declarative resource limits: design alternatives — multicode inspiration, the backend-neutral
[runtime.limits]schema that was considered and not adopted, and open naming/inheritance questions - Selectable sandbox backends — cross-backend translator lives here long-term
- Console resource panel — consumer of the runtime side (current usage vs configured limit) and the natural home for OOM push-notification
- Autonomous task queue — queue parallelism limit interacts with per-agent memory limits