roundabout
five agents, one machine, one youtube video. roundabout makes the builds take turns.
agents say roughly how big a build is. a tiny daemon, started on demand and gone once idle, lets it run when there’s room. two heavy builds never overlap, nothing gets promised disk that isn’t there, and small stuff keeps slipping through while the big one grinds. roundabout doesn’t run anything itself, it just holds the door.
use
the cli is roundabout, short enough that agents don’t mind typing it
roundabout run --size m -- cargo build
roundabout run --ram 6G --cores 8 --time 10m -- nix build .#thing
roundabout status
roundabout suggest -- cargo build --release
roundabout expect --size l # building soon, save me a seat
roundabout watch 7 # exits when lease 7 is done or overdue
roundabout kill 7
roundabout extend 7 10m
roundabout artifacts check | list | gc [--cap] [--project] | pin <path>run exits with the build’s code, 124 if --deadline killed it, 75 if --max-wait ran out.
a granted build gets CARGO_BUILD_JOBS, MAKEFLAGS, CMAKE_BUILD_PARALLEL_LEVEL and a NIX_CONFIG with cores/max-jobs to match its grant.
on linux, roundabout creates a transient scope through the systemd user manager’s D-Bus StartTransientUnit method. the command waits until its scope is active, then runs with its original environment and terminal I/O. CPUQuotaPerSecUSec caps CPU time at the granted core count, and MemoryMax caps the whole build at its granted RAM multiplied by overrun.ram_slack, with at least the granted RAM available. these limits cover descendants too, even when a tool ignores the job-count environment variables. linux requires a running systemd user manager and user bus, a failed scope refuses the build rather than running it without limits. other platforms retain the job-count environment variables without cgroup enforcement.
the grant can be bigger than the ask. idle cores go to whoever’s building, minus what other sessions have said they’ll want via roundabout expect. spare cores never hold up admission, oversubscribing cpu only slows things down, but each one is charged ram at the build’s own ram per core since more jobs eat more memory. a session’s build counts as an expect too, so agents that forget still leave a mark. --exact opts out.
claude code
nix build .#plugin
claude plugin marketplace add ./result
claude plugin install roundabout@roundaboutor on nixos
imports = [ roundabout.nixosModules.default ];
programs.roundabout.enable = true;
programs.roundabout.settings.budget.ram = "16GiB"; # lands in /etc/roundabout/config.tomlthat installs roundabout and a socket-activated systemd user unit for the daemon (socketActivation = false to let clients spawn it instead). programs.roundabout.claude has read-only hooks, skill and plugin for whatever writes your claude config, e.g. settings.hooks = config.programs.roundabout.claude.hooks.
the plugin brings a skill and hooks. unwrapped builds get bounced back with instructions, overdue builds get mentioned to the agent that started them, and agents over their artifact cap get told to tidy up before they stop. roundabout needs to be on PATH for the agent too, so install .#roundabout alongside.
codex
nix build .#codex-plugin
codex plugin marketplace add ./result
codex plugin add roundabout@roundaboutrestart codex and review the plugin hooks with /hooks before using them. install .#roundabout as well so roundabout run and roundabout expect are on PATH. codex can also load the hooks directly from ~/.codex/hooks.json or a trusted project’s .codex/hooks.json using the hooks object in result/plugins/roundabout/hooks/hooks.json. use one installation route to avoid duplicate reminders.
on nixos, programs.roundabout.codex exposes read-only hooks, skill and plugin, alongside the claude options. the codex hooks deny unwrapped builds and suggest roundabout run instead of silently rewriting the command, which keeps the normal tool approval flow intact even when enforce.unwrapped is set to wrap. roundabout run and roundabout expect associate builds with the current codex session, and session-end hooks release its expectation.
omp
nix build .#omp-plugin
omp plugin link ./resultrestart omp after linking. install .#roundabout on PATH for the agent’s bash tool, or set ROUNDABOUT_CLI to the roundabout binary for the extension. the plugin bundles the skill and checks shell builds before they run. it also delivers overdue reminders, checks artifact caps before the main agent stops, and releases expectations when a session closes or switches. wrapped builds and roundabout expect inherit the omp session id through the bash tool’s environment. on nixos, programs.roundabout.omp exposes read-only plugin and skill options.
config
~/.config/roundabout/config.toml, falling back to /etc/roundabout/config.toml, or $ROUNDABOUT_CONFIG over both. the daemon picks up edits on save, on linux anyway. a broken config gets complained about and ignored rather than taking every agent down with it. everything’s optional, these are the defaults
project settings can live in a hidden .resrc file. each client command starts in its current directory and walks upward until it reaches the git root. the first .resrc it finds wins, so a nested file takes precedence over one at the repository root. directories outside a git worktree do not load a project file. .resrc uses the same TOML settings, and omitted values keep the user or system settings
# .resrc
[budget]
ram = "16GiB"
cores = 8
[artifacts]
project_cap = "20GiB"[budget]
ram = "75%" # of total, or "24GiB"
ram_reserve = "4GiB" # MemAvailable never drops below this on our account
cores = "100%"
disk_reserve = "20GiB" # per fs, capped at a tenth of it
disk_paths = ["/nix/store"]
psi_max = 40.0 # memory psi avg10 above this holds heavies back
[heavy] # any one of these makes a build heavy
ram = "4GiB"
cores = "50%"
disk = "5GiB"
[sizes.m] # s m l xl built in, add your own
ram = "2GiB"
disk = "1GiB"
cores = 4
time = "5m"
[queue]
linger = "5s"
backfill_max_time = "2m"
# max_wait = "30m"
elastic = true # hand idle cores to whoever's building
intent_ttl = "20m" # how long a `roundabout expect` or a build keeps its claim
[overrun]
ram = "warn" # or "throttle", MemoryHigh at granted RAM on linux
ram_slack = 1.25
time_grace = 1.5 # overdue at this multiple of --time
remind_every = "5m"
[enforce]
unwrapped = "deny" # "wrap" guesses a size for them, "allow" looks away
default_size = "m"
[artifacts]
project_cap = "10GiB"
global_cap = "60GiB"
idle_after = "24h"
size_ttl = "10m"
over_cap = "warn" # "block" refuses new builds until cleaned
stop = "nudge" # "block" "warn" "off"
nix_gc = false
nix_min_free = "30GiB"
# extra_rules = [{ name = "zig-cache", marker = "build.zig" }]
[nix]
inject = true
cores_per_job = 4
[detect]
# extra = ["just build"]license
EUPL-1.2