Skip to content

v0.92.0 — /nice

Choose a tag to compare

@ralyodio ralyodio released this 06 Sep 06:41
92ed699

/nice runs the CLIs the pit starts at a lower priority than the terminal you are typing in (#487).

/nice on          # nice -n10 + ionice -c2 -n7 for every engine started after
/nice mem 2G      # a memory ceiling, per engine
/nice cpu 15      # yield more (nice takes -20..19)
/nice             # what it is set to
/nice off         # normal priority — the default

Why

The pit's whole job is starting other people's programs, and some of them are not shy: a coding engine holding a big context, a bundler, a browser under test. Start a few at once on a box you also want to type on and you get the failure everyone knows — the machine stops answering. Nothing crashed. Every core is busy, the last of the RAM went to swap, and the swap went to disk.

nice is the classic answer to that, and it is worth being precise about which half of the problem it solves. It reorders CPU, so it buys back the part of a freeze you could have waited out. It does nothing about memory — and memory is the stall that actually costs you a session. Once free RAM runs out the kernel starts reclaiming, reclaim goes to disk, and no scheduling priority on earth makes that faster. A nice level cannot prevent an OOM kill.

So a throttle worth the name covers three resources, not one: nice for CPU, ionice for I/O so the engine's reads stop starving the rest of the box, and — where systemd can give us one — a cgroup with a hard memory ceiling, so a runaway process dies on its own instead of taking the machine down with it.

The levels are deliberately not the extremes. nice 19 and ionice idle both mean "run only when nothing else wants the machine", which sounds right and is not: an engine that yields completely can take minutes to answer while one background job holds the box, and a coding CLI that never finishes reads as broken rather than as polite. 10 and 7 are "last in line among normal work" — you keep your terminal, the engine keeps making progress.

It degrades rather than failing

A comfort feature must never become an outage, so every part of this is best-effort:

  • each wrapper is added only if its binary is actually present; a box with neither nice nor ionice spawns exactly what it spawned before
  • Windows is a no-op rather than a wrong guess about an equivalent
  • the memory ceiling is opt-in, because systemd-run --user needs a session bus that an ssh login without lingering does not have — and it fails outright rather than degrading, which would take the engine down with it. /nice tells you when a ceiling is set but cannot be enforced here, instead of silently doing nothing.
  • a corrupt ~/.moshcode/nice.json reads as "off", and one bad key loses only itself. This is parsed on the spawn path for every CLI the pit starts, so it can never throw.

Off by default: a throttle nobody asked for is a slow engine nobody can explain.

Where it applies

In spawnSpec(), the single seam both launch paths already share, so engines (/agents, /start) and the installers and updaters runCmd drives are covered by one change. It wraps outside executable resolution on purpose — an unresolved binary still produces its own ENOENT rather than one from a wrapper that hides which program was missing.

Settings live in ~/.moshcode/nice.json, owner-only like the history file.