Root cause of the 2026-11-07 20:14 OOM: the queue granted jobs per kind
only, so several heavy kinds and direct heavy commands outside the queue
ran side by side until the kernel killed the editor.
- queue.py: every grant checks the sum of the learned RAM and CPU
footprints of active and granted jobs against budget_percent; an
unlearned kind takes the whole budget and runs alone. RAM used plus
what young jobs (RAMP_SECONDS) may still take must stay below
load_limits. Memory stage stop blocks every grant.
- watch.py: memory stage from MemAvailable, swap, PSI full and the swap
of the container holding most of it; the block hook classifies
builds, make install/verify-*, and pytest or unittest beyond one file
as heavy through wrappers, sh -c, command substitutions and heredocs
a shell reads.
- history.py: footprints per kind and signature, seed RAM for pending
shares.
- operator.py: [h]old queues a heavy row through queue.py hold and asks
again on its grant, with a flushed tty and a timeout; sudo rows, which
need the terminal, stay direct.
Residuals: a shell reading code from a pipe or here-string, its own
script, a program named by a substitution, quotes inside an expanding
heredoc, an escaped backslash before a substitution; a lone job waits
while the host is over load_limits.
🎯 Fix: 92 %
💥 Break: 4 %
💸 Cost: 100 %
⏱️ Time: –
🛡️ Security: 100 %
🏆 Score: 88
🚦 Gate: ✅ 90/5, cost and time <= 100, security 100 %
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
operator.py run walks the open rows in list order and shows each step's directory, command (control characters made visible), reason, check and a preview of the change (commits and diffstat to push, files an install changes, what a removal deletes, the repository state) before asking yes, skip, diff or quit; an approved command runs through $SHELL -ic so aliases work, and the row closes once its check passes. list prints one bash block per step plus the run block. The new check kind decision keeps decisions and browser steps out of blocks and run. The skill now requires one row per step and moves the check kinds and the peer sweep into side files.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Legacy register rows carry a prose title and no directory. They stay in the table but no longer land in the copy-paste bash block as if they were commands; with no runnable row the block is omitted.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Collecting the operator's pending commands meant messaging every agent, waking each for a full turn. operator.py add records a step as register task operator/<slug> (command, absolute directory, reason, operator as R, the adding session as A) with a check from a fixed list: pushed:<repo> (nothing ahead of the upstream), installed:<source>=<installed> (same files, same bytes), binary:<name>, absent:<path> or manual. A stored shell command is refused on purpose, together with relative paths and shell metacharacters, so no agent can run code in another's session through the shared register. list runs the checks, closes what passed, and prints the open steps in dependency order as a table and one bash block; done closes a manual step.
The skill now has every agent add a step the moment it arises and prints list on invocation; a peer sweep by message only on the operator's explicit request. test_operator covers every check kind, the open states and the refusals.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>