Commit Graph
14 Commits
Author SHA1 Message Date
Kevin Veen-BirkenbachandClaude Opus 5.5 151031ab4a fix(kvb-org-queue): light kinds meet only running and granted jobs, never a reservation
Every entry blocked by budget or disk added its share to the tally of all entries behind it, and a head the host load keeps out counted as granted while a job ran, so test-nostack jobs needing 0 GB and 11 % CPU waited behind 'CPU 170-242 % in use or reserved' (live queue 2026-11-12). Operator pick on the chair's ask: a light_kinds entry is now checked only against running and granted entries; reservations, a load-held head included, hold back heavy kinds alone. resources() returns what a job takes or reserves, so evaluate() keeps both tallies. Heavy-only queues give master's verdicts (skeptic probe: 0 of 40000 random cases differ); a stream of light jobs can delay a heavy head, as the docstring and SKILL.md now state. Tests kill the reviewer's three mutants and fail on master.

🎯 Fix: 92 %
💥 Break: 4 %
💸 Cost: 100 %
⏱️ Time: 100 %
🛡️ Security: 100 %
🏆 Score: 88
🚦 Gate: ✅ 90/5, cost and time <= 100, security 100 %

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-12 08:47:26 +00:00
Kevin Veen-BirkenbachandClaude Opus 5.5 256fe2c327 fix(kvb-org-queue): an idle queue no longer waits on a head the host load alone keeps out
evaluate() counted a non-hotfix entry that passed every queue check but was held back by the live host load as granted for every entry after it: its slot, the exclusive-kind drain and its CPU/RAM share. With RAM held outside the queue the head never started and nothing behind it did either (2026-11-12 03:50 UTC: 27 entries, no job active, host idle). While no queue job is active, such an entry now reserves nothing and holds nothing back; with jobs active it keeps its reservation, and a load-held hotfix still holds back every entry after it. Replayed on the live queue, the fix grants test-nostack-1035 at once.

🎯 Fix: 93 %
💥 Break: 4 %
💸 Cost: 100 %
⏱️ Time: 100 %
🛡️ Security: 100 %
🏆 Score: 89
🚦 Gate: ✅ 90/5, cost and time <= 100, security 100 %

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-12 08:08:40 +00:00
Kevin Veen-Birkenbach 4ab13b6e65 Merge fix/kvb-org-queue-footprint-noise into master
🧪 Test / 🧪 Lock + lint (push) Waiting to run
2026-11-11 21:31:48 +00:00
Kevin Veen-BirkenbachandClaude Opus 5.5 c1a88c6556 fix(kvb-org-queue): learn CPU by median, path-free signatures, exclusive gate kinds
The CPU share learned the p90 of the host-wide peak minus before, which carries every neighbour's spike: 30 days of history give test-nostack a p90 of 73.8 against a median of 9.7, and rows that no other job overlaps learn the same p90, so concurrency is not the cause. signature() also kept the --repo value of a script as its operand, so every worktree's scan learned apart and fell back to the kind (130 of 301 test-nostack rows). The CPU share now learns the median while RAM keeps the p90, and an operand holding a path separator leaves the signature. New optional config keys exclusive_kinds and light_kinds: a job of an exclusive kind runs only beside light kinds, and an exclusive entry that only running jobs of other kinds keep out holds back every later non-light entry, so no lower-ranked job overtakes a waiting gate.

🎯 Fix: 91 %
💥 Break: 4 %
💸 Cost: 100 %
⏱️ Time: 100 %
🛡️ Security: 100 %
🏆 Score: 87
🚦 Gate: ✅ 90/5, cost and time <= 100, security 100 %

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 20:39:19 +00:00
Kevin Veen-BirkenbachandClaude Opus 5.5 ca54420f4d fix(kvb-org-queue): restart holders on stale queue code; link host skills into projects
Every holder judges the shared queue.json with its own loaded code, so the project copies from 2026-11-07 (no RAM/CPU budget) granted heavy jobs on the slot limit alone and overloaded the host at 18:24. Each entry now stamps a hash of queue.py and the sibling scripts it loads; a poll that finds other code installed in ~/.agents/skills leaves the queue ungranted and re-execs the same hold with the installed queue.py, on the realpath interpreter, with signals reset and a caught signal ending the hold instead. queue.py list prefixes entries on other or unstamped code. A project install now symlinks every skill the global install holds instead of copying it, so the project hooks and ../ references stay valid and one host runs one version.

🎯 Fix: 92 %
💥 Break: 4 %
💸 Cost: 100 %
⏱️ Time: 100 %
🛡️ Security: 100 %
🏆 Score: 88
🚦 Gate: ✅ 90/5, cost and time <= 100, security 100 %

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 20:33:54 +00:00
Kevin Veen-BirkenbachandClaude Opus 5.5 134b1d30f1 fix(kvb-org-queue): a hotfix held back by host load holds back the entries after it
The grant order ignored live load: a prio-1 hotfix that fit the budget but waited on RAM or CPU counted as granted for the entries after it, so CPU-light jobs behind it could start, keep the load up and starve it. Such a hotfix now holds back every entry after it until it starts (operator pick 2026-11-11 on Main's report). Each holder stores the reason of its last poll, and list shows it, so a job the load holds back no longer reads as granted, without list sampling the CPU.

🎯 Fix: 92 %
💥 Break: 4 %
💸 Cost: 100 %
⏱️ Time: 100 %
🛡️ Security: 100 %
🏆 Score: 88
🚦 Gate: ✅ 90/5, cost and time <= 100, security 100 %

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 16:34:37 +00:00
Kevin Veen-BirkenbachandClaude Opus 5.5 7043cdfd98 fix(kvb-org-queue): book a job at its own command's footprint
A command with 5+ runs is booked at its own p90 RAM and CPU share, an unlearned one at its kind's p90. The old rule took the larger of kind and command, and for an unlearned command the largest command of its kind: test-nostack-788 (docker build) sat 90 min at position 1, booked at 42.6 % RAM from a legacy gate.py hold row while test-nostack measures 5.6 %; skills make test (3.3 %) was booked at the test kind's 35 %.

🎯 Fix: 93 %
💥 Break: 5 %
💸 Cost: 99 %
⏱️ Time: 100 %
🛡️ Security: 100 %
🏆 Score: 89
🚦 Gate: ✅ 90/5, cost and time <= 100, security 100 %

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 12:07:13 +00:00
Kevin Veen-BirkenbachandClaude Opus 5.5 4df3945066 fix(kvb-org-queue): an unclaimed grant drops its reservation
evaluate counted every unblocked waiting entry as granted for the entries behind it, but only that entry's own wait() poll turns it active; a living holder that stopped polling kept its slot and share forever and wedged the queue. Every poll now stamps polled; a waiting entry silent for CLAIM_SECONDS (3 polls) reserves nothing and is listed last, and once it polls again it takes a fresh seq and enqueue time, so it rejoins at the back of its rank.

🎯 Fix: 92 %
💥 Break: 3 %
💸 Cost: 100 %
⏱️ Time: 100 %
🛡️ Security: 100 %
🏆 Score: 89
🚦 Gate: ✅ 90/5, cost and time <= 100, security 100 %

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 09:51:29 +00:00
Kevin Veen-BirkenbachandClaude Opus 5.5 4321b97529 fix(kvb-org-queue): global budget so no queue mix can OOM the host
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>
2026-11-08 06:54:09 +00:00
Kevin Veen-BirkenbachandClaude Opus 5.5 4540494c30 feat(orchestrator): hierarchy view from council offices with a separate agent order
The Agents views gain a Hierarchie view: an org chart of the chair and
the line offices read from the council claims. The operator appoints,
hands over and recalls an office there; the page signs each change with
the passkey and a host unit (client/offices.py, offices path unit)
verifies the signature, removes a live holder itself and runs a plain
council.py claim for the successor. council.py loses the --operator
flag, so no agent can override a live holder.

The agent order is now separate from task priority: rasci.py agents
order stores it in agent-order.json beside the register through the
nofollow helpers and never writes a --prio pin. It only breaks ties of
equal task rank, also among the queue grants. The Hierarchie view edits
it as a drag list.

Host units run the installed skill scripts, so orchestrator.sh keeps the
offices unit off while the installed council.py, nofollow.py or rasci.py
differ from the tree. Job files, council files and hub records are read
with O_NOFOLLOW and O_NONBLOCK and size caps; nofollow.rewrite replaces
a claim without creating directories, so a beat never revives a released
office.

🎯 Fix: 92 %
💥 Break: 4 %
💸 Cost: 100 %
⏱️ Time: 79 %
🛡️ Security: 100 %
🏆 Score: 100
🚦 Gate: ✅ 90/5, cost and time <= 100, security 100 %

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 17:46:39 +00:00
Kevin Veen-BirkenbachandClaude Opus 5.5 cb815216d2 fix(kvb-org-organize): no council hook follows a planted symlink or FIFO
The council root is writable by every sandboxed agent of the same uid while its hooks run outside the sandbox, so a planted symlink turned a hook's write into one outside the root, and a planted FIFO or held lock hung every agent's hooks.

- Add nofollow.py: open, read, lock, append, replace and unlink below a root through O_NOFOLLOW directory fds, O_NONBLOCK and regular files only; reads stop at 64 MiB and replace undecodable bytes; locks take an explicit wait.
- watch, queue, radio and council read and write everything below the council root through it; keep_rows copies no symlinked file into ~/.local/state.
- watch: read_json returns dicts only, alert lines that are no alert (deep nesting included) are skipped, cmd_cleanup makes and removes its lock through a root dir fd, and the cache trim walks with fwalk, counts allocated blocks and each inode once.
- radio: the hooks wait at most 2 s for a session lock and skip the delivery past it.
- council: read_claim ignores claims that are no object with session, runtime and beat, so a malformed claim no longer disables the claim gate.

MEDUSA acceptances (kvb-think-criticize 2026-11-06, skeptics A and B): MM-SCAN-058 on nofollow.replace callers, B105 and HPT006 in history.py, RT-SCAN-054 in council.py (name matches).

🎯 Fix: 92 %
💥 Break: 4 %
💸 Cost: 100 %
⏱️ Time: 100 %
🛡️ Security: 100 %
💰 Value: ?
🏆 Score: 88
🚦 Gate: ✅ 90/5, cost and time <= 100, security 100 %

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 13:26:59 +00:00
Kevin Veen-BirkenbachandClaude Opus 5.5 3843821674 fix(kvb-org-organize): harden the council hooks against sandboxed agents
- Move the council config to ~/.claude/agents-council/config.json, which the sandbox cannot write; the installer adds Edit(~/.claude/agents-council/**) to permissions.deny.
- Run every kvb hook as /usr/bin/python3 -I and rewrite old bare python3 entries.
- Refuse a council root that is a symlink, another uid's, or group or other writable, in watch, queue and radio.
- Run worktree_cleanup entries as argv without a shell in /, only for compose project names.
- queue block: tokenize every Bash command like bash (quotes, word-start comments, line continuations), and hold each held command to the Bash deny and ask rules of the managed, user and project settings; refuse a hold it cannot parse.
- radio: deliver fields on one line of at most 1000 characters, at most 10 rows.

Operator step after install: move ~/.config/agents-council/config.json to ~/.claude/agents-council/config.json.

MEDUSA acceptances (kvb-think-criticize 2026-11-06, skeptics A and B): ACP-SCAN-028 load_config (config outside the sandbox, Docker residual 093); CODEGEN-SCAN-004 and LLM06 run_cleanup and worktrees_command shell strings (operator config only); SAST-PATH-001 queue hold Popen (the caller's own command in its sandbox); PA-133 radio hook context (peer rows, clipped); JB-SCAN-641/616/2979 clip and MM-SCAN-058 replace (name matches).

🎯 Fix: 90 %
💥 Break: 5 %
💸 Cost: 100 %
⏱️ Time: 100 %
🛡️ Security: 100 %
💰 Value: ?
🏆 Score: 86
🚦 Gate: ✅ 90/5, cost and time <= 100, security 100 %

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 13:26:59 +00:00
Kevin Veen-BirkenbachandClaude Opus 5.5 12ed861a9a fix(kvb-org-queue): hold liveness by flock, not pids across PID namespaces
Every sandboxed tool call runs in its own PID namespace, so alive() saw every other hold as dead, pruned it and granted parallel heavy jobs. Each hold now keeps an exclusive flock on <root>/holds/<tag>.lock, shared with its command via pass_fds, from before its entry exists until it leaves; list and evaluate drop an entry only when nobody holds its lock. Entries without a lock fall back to the pid check. held() accepts only <pid>-<ns> tags, so an entry cannot unlink a path outside holds/.

MEDUSA: queue.py scores 25 % against 75 % for HEAD's queue.py alone, because MEDUSA-ACP-SCAN-028 flags two cfg = watch.load_config() lines that are byte-identical at HEAD; the Popen high (SAST-PATH-001) also pre-exists. None is accepted: kvb-think-criticize found the config agent-writable. The operator approved the commit after this comparison.

🎯 Fix: 95 %
💥 Break: 3 %
💸 Cost: 99 %
⏱️ Time: 100 %
🛡️ Security: 25 %
🏆 Score: 23
🚦 Gate: ⚠️ security 25 % < 100: ask the operator before the commit

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 13:26:59 +00:00
Kevin Veen-BirkenbachandClaude Opus 5.5 27559dabc4 feat(org): kvb-org-radio and kvb-org-queue replace chair and peer messages
kvb-org-radio is the default channel to other agents: broadcast and direct reach the receiver on its next prompt, security and pan-pan mid-turn through a PostToolBatch hook, and only mayday or direct --wake wake a session, through the receiver's own asyncRewake Stop hook (a SendMessage fallback is printed for sessions without a listener). It keeps the news.jsonl store and lock, so news.py stays a forwarding shim. A mayday holds radio silence for two hours: watch.py blocks new heavy commands and the operator gets a desktop notice. hotfix.py announces merged hotfixes as security items.

kvb-org-queue grants heavy jobs by rule instead of chair messages: queue.py hold waits under a flock with disk reservation, hotfix-first and register rank order, slot limits, stage and silence checks, then runs the command and releases. Every job is recorded; history.py learns disk need per command and slot limits per kind (p90, five runs), watch.py mirrors the history to ~/.local/state. enable-plugins.js registers the new hooks behind a file check so a missing script never blocks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 17:07:24 +01:00