sysbudget is a daemonless macOS CLI that keeps the computer responsive by admitting process trees into strict, named resource-budget groups.
CPU control ships first. Memory budgets are planned; the public model and name deliberately describe resources rather than agents or build tools.
Requirements: macOS, Python 3, Git, and Xcode Command Line Tools.
git clone https://github.com/neumie/sysbudget.git
cd sysbudget
./scripts/install.shThe installer builds a pinned macOS-compatible cpulimit helper locally, migrates a v0.1 configuration when present, and installs without sudo under ~/.local.
A group owns a CPU capacity, a default request, a per-run maximum, and an admission queue. Foreground work has priority; FIFO order is preserved within the foreground and background classes. A workload waits before process creation until its full request fits. Background leases may borrow the complete group while it is idle, but are suspended and their capacity reclaimed when queued foreground work needs it. Additional groups are optional strict partitions with independent queues.
Reserved headroom is capacity that no sysbudget-managed group may allocate. It is not CPU affinity or an exclusive cpuset: unmanaged processes and macOS may still execute on every core.
Default configuration on a 10-vCPU Mac:
- 4 vCPU reserved as host headroom.
shared: capacity 6, default request 2, maximum request 3.sharedis selected when--groupis omitted.
The shared default avoids stranded capacity. If you add multiple groups, their capacities are strict and do not borrow from one another.
sysbudget run -n 2 -- make -j
sysbudget run -n 1 -- npm test -- --runInBand
sysbudget run -n 1 --label docs -- make docs
sysbudget run --class background -n 3 -- make test # borrow idle capacity
sysbudget status
sysbudget status --watch # live event-driven dashboard
sysbudget release <id>
sysbudget gc
sysbudget doctorUse --label "short purpose" to populate the queue and active-run LABEL
column. If it is omitted, the CLI falls back to the executable name. The Pi
guard forwards an AI background_job label and otherwise derives a short
command summary for automatically routed work.
status accounts for the complete machine. Reserved headroom appears as a
non-runnable budget beside the workload groups, so the displayed capacities
add up to every logical CPU:
CPU CAPACITY 10 vCPU
━━━━ ━━━━━━
BUDGETS
NAME KIND CAP ALLOC FREE CPU RUN WAIT DEFAULT MAX
reserve headroom 4 — — — — — — —
shared workload 6 0 6 0.0% 0 0 2 3 ★ default
Interactive output uses color when supported. On terminals narrower than 104
columns, status switches to compact tables with one continuation line per run
and middle-truncates long labels and paths. Policy, config, and the live-watch
indicator share one responsive header line with the title; active runs are
always the final section. The capacity bar remains visual while its values
appear once in the budgets table. --plain retains complete values.
status --watch opens a live terminal dashboard that reacts to lease, queue,
configuration, and terminal-width changes and refreshes elapsed times once per
second. Press Ctrl-C to return to the previous screen. Active rows show both the live process-tree CPU usage and the admitted
CPU cap; 100% actual CPU equals one fully used logical CPU. Each active and
queued run also shows the working directory from which it was submitted, so the
owning project is visible. status --plain preserves its existing tables and
appends a DETAILS table with actual CPU and JSON-encoded working directories,
a keyed SCHEDULING table containing each run's class and state, and a keyed
LIMITS table with each group's default and maximum request. Paused background
work remains visible and resumes automatically when capacity returns.
Use group list --plain for the stable unstyled group table.
Every run controls the target and all descendants. Builds, tests, subprocesses,
and nested tools share that run's CPU target. The target receives SYSBUDGET=1,
SYSBUDGET_GROUP, SYSBUDGET_VCPUS, SYSBUDGET_CLASS, and conservative
parallelism defaults for common build runtimes; explicit environment values are
preserved. Nested sysbudget run calls are rejected because overlapping
process-tree limiters cannot suspend a tree safely.
All capacity settings can be changed through the CLI:
# Keep 4 of this Mac's 10 logical CPUs outside managed groups.
sysbudget config set reserve 4
sysbudget config get reserve
sysbudget group list
sysbudget group set shared capacity 6
sysbudget group set shared default 2
sysbudget group set shared max 3
# Optional: divide the managed pool into strict specialized groups.
sysbudget group set shared capacity 5
sysbudget group add batch --capacity 1 --default 1 --max 1
sysbudget group remove batch
sysbudget config set default-group shared
sysbudget config show
sysbudget config pathThe sum of group capacities may not exceed logical CPUs - reserve. A run's -n request must not exceed its group's max; omitting -n uses default. Lower group capacities before increasing the reserve when needed. Lowering a capacity through the CLI also clamps its maximum request, but does not resize active workloads; the group admits nothing new until existing leases fit again.
Other settings remain available through sysbudget config set: ttl, timeout, poll, qos, nice, and cpulimit. The generated configuration lives at ~/.config/sysbudget/config and is atomically rewritten. config set pool remains a compatibility alias that derives the reserve.
Do not wrap the agent itself. Install the agent integration so sustained child commands enter the default shared pool (or another group the user configures):
sysbudget agents install
sysbudget agents status
sysbudget agents removeThe policy is installed for detected Pi, Codex, OpenCode, Claude Code, and Grok CLIs. Cursor receives a rule fallback for versions without Agent Skills. Agents inspect the selected group's limits and explicitly choose useful parallelism with -n—normally 1 for targeted/single-threaded work, 2 for ordinary work, and up to the configured maximum for genuinely parallel work. The runtime rejects oversized requests. Pi additionally receives a shell-tool hook that routes recognized CPU-intensive builds, compilers, test suites, and package operations through the current default group when an agent omits an explicit wrapper; those fallback runs use the configured default. Quick non-intensive commands, unknown utilities, and service/watch commands run directly. Recognized CPU-intensive background_job starts use the reclaimable background class. Other harness integrations remain best-effort.
See docs/agent-integration.md for paths and policy details.
sysbudget keeps a locked state file and records queue tickets before launching targets. Foreground tickets bypass background tickets within the same group; FIFO ordering remains stable within each class, while different groups admit work independently. A global managed-capacity check preserves newly increased headroom while older oversized leases drain.
Each admitted target stays in its normal terminal process group. A sibling limiter tracks the complete descendant tree, preserving interactive input, handled Ctrl-C, and shell stop/resume behavior. For reclaimable background work, the limiter handles the pause protocol itself: it stops the tracked tree, stops itself as an acknowledgement, and resumes ordinary throttling after SIGCONT. Capacity is released only after that acknowledgement.
- Only workloads started through
sysbudget runare controlled. - Reserved headroom constrains managed workloads; it does not prevent unmanaged processes from consuming CPU.
- CPU throttling and background suspension are enforced in user space with
SIGSTOP/SIGCONT. The configured value is an average target, so short startup bursts are possible. - Continuous foreground demand may intentionally starve background work until capacity returns.
- A child that deliberately daemonizes, moves into a VM, or is launched through
launchdmay escape the process tree and cannot be reliably suspended. - This is a responsiveness and admission-control tool, not hard CPU affinity or benchmark isolation.
- Docker VM and remote-host workloads need limits inside their own environment.
- Command:
~/.local/bin/sysbudget - Implementation/helper:
~/.local/lib/sysbudget/ - Configuration:
~/.config/sysbudget/config - Runtime state:
~/.local/state/sysbudget/
The full suite requires Node.js 22 or newer for the bundled Pi hook test.
make testThe test suite builds the pinned helper and verifies legacy-config migration, shared-pool defaults, optional strict groups, class-prioritized admission and independent resumption, verified process identities, fail-closed orphan cleanup, acknowledged background preemption/resume across a descendant tree, Pi CPU-cost classification, return codes, release cleanup, agent-policy installation, interactive TTY input, handled Ctrl-C, and whole-job stop/resume.
See CONTEXT.md for domain terms and product decisions.
sysbudget builds HiGarfield/cpulimit
at a pinned revision. Source metadata, the upstream license, and the Darwin and
background-control patches are under
third_party/cpulimit.
sysbudget is licensed under the GNU General Public License v3.0 or later (GPL-3.0-or-later).