Skip to content

Repository files navigation

sysbudget

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.

Install

Requirements: macOS, Python 3, Git, and Xcode Command Line Tools.

git clone https://github.com/neumie/sysbudget.git
cd sysbudget
./scripts/install.sh

The installer builds a pinned macOS-compatible cpulimit helper locally, migrates a v0.1 configuration when present, and installs without sudo under ~/.local.

Model

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.
  • shared is selected when --group is omitted.

The shared default avoids stranded capacity. If you add multiple groups, their capacities are strict and do not borrow from one another.

Usage

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 doctor

Use --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.

Configuration CLI

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 path

The 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.

Agent-directed scheduling

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 remove

The 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.

How it works

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.

Limits

  • Only workloads started through sysbudget run are 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 launchd may 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.

Files

  • Command: ~/.local/bin/sysbudget
  • Implementation/helper: ~/.local/lib/sysbudget/
  • Configuration: ~/.config/sysbudget/config
  • Runtime state: ~/.local/state/sysbudget/

Development

The full suite requires Node.js 22 or newer for the bundled Pi hook test.

make test

The 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.

Third-party code

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.

License

sysbudget is licensed under the GNU General Public License v3.0 or later (GPL-3.0-or-later).

About

Shared resource budgets for macOS process trees

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages