Files
skills/skills/kvb-org-queue/scripts/queue.py
T
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

1016 lines
41 KiB
Python
Executable File

#!/usr/bin/env python3
"""Queue the heavy jobs of parallel agents and grant them in a fixed order, without messages.
Usage:
queue.py hold --kind KIND --task TASK [--need-gb N] [--hotfix SLUG] [--session ID] -- CMD...
queue.py list
queue.py history
queue.py cancel ID [--session ID]
queue.py block hook (PreToolUse on Bash)
Environment:
AGENTS_COUNCIL_ROOT: shared state directory; /tmp/agents-council unless set,
because every agent on the host must agree on it. The queue is
<root>/queue.json, every read-modify-write holds <root>/queue.lock.
Each hold keeps an exclusive flock on <root>/holds/<tag>.lock, shared
with its command, from before its entry exists until it leaves; an
entry whose lock nobody holds is dead. Pids cannot tell, because every
sandboxed tool call has its own PID namespace.
Every finished job appends a row (history.py) to
<root>/queue-history.jsonl; the watch hook of watch.py keeps a copy in
~/.local/state/agents-council/queue-history.jsonl, and reading merges
both by row id.
AGENTS_COUNCIL_CONFIG: the config of the kvb-org-organize watch.py;
~/.claude/agents-council/config.json unless set.
CLAUDE_CODE_SESSION_ID: the caller's session, taken over --session; other
runtimes pass --session.
CLAUDE_PROJECT_DIR: block reads the project's .claude/settings.json and
settings.local.json there, next to the managed and user settings.
Args:
hold: enqueue CMD, then every POLL_SECONDS evaluate the whole queue under
the lock until this entry is granted; measure disk, RAM and CPU, run
CMD in the current directory with inherited stdio in a session of its
own, forward SIGINT and SIGTERM to its process group, measure every
POLL_SECONDS and at its end, record its history row and leave the
queue. The entry carries its code (a hash of this file and the
SIBLINGS); a poll that finds other code installed in
~/.agents/skills leaves the queue ungranted and restarts the same hold
with the installed queue.py.
--kind: a key of slot_need_gb in the config.
--task: an open task of the kvb-org-rasci register; its rank orders
the queue, and the operator's agent order (`rasci.py agents
order`) of the holding session breaks ties.
--need-gb: disk the job takes, in GB; when omitted the need learned
for the command's signature, else slot_need_gb[kind].
--hotfix: the slug of the register row hotfix/<slug> the job proves;
hotfix jobs go first.
--session: the caller's session for runtimes other than Claude Code.
list: a header, then one line per entry: position (run for active jobs),
id, status, kind, task, need GB, minutes waited, ETA minutes (until a
running job ends or a waiting one starts, from learned medians; - while
one is unknown), workdir, blocking reason (the one the holder saw at
its last poll, host load included; prefixed when the entry runs other
code than the installed one). Entries of dead holders are dropped
first.
history: a markdown table per command signature and one per kind of what
the last 30 days of history teach, with each slot limit's source.
cancel ID: remove the caller's waiting entry ID; its hold exits 3.
block: read the hook payload on stdin. The permission matcher sees only
`queue.py hold`, so every hold in the command is parsed like hold
itself, and a held command any of whose word-started tails meets a
Bash deny rule of the settings is refused, one meeting an ask rule
asks the operator, with or without queue_enforce; a hold that cannot
be parsed or checked is refused. With queue_enforce true in the config,
refuse a command that watch.heavy() calls heavy or that runs
`gate.py run|hold`, unless it matches never_block or runs `queue.py
hold`.
Config keys: budget_percent {ram, cpu}, required: the active and granted jobs'
learned footprints (history.footprint, the whole budget for a kind not learned
yet) may sum to at most these percentage points, a job alone always fits;
slot_limits {kind: N}, optional, caps the learned slot limit of a kind from
above (history.slot_limits); exclusive_kinds [kind] and light_kinds [kind],
optional: a job of an exclusive kind runs only beside jobs of light kinds, and
no job of another kind starts beside it; a light job meets only the budget and
disk of running and granted jobs, never a reservation; queue_enforce (bool), optional, read by block
only. The watch.py keys disk, thresholds_gb (stop is the floor every grant
leaves free), slot_need_gb (the disk seed per kind), load_limits (every grant
also needs CPU busy below cpu_percent and RAM used plus the job's learned RAM
share below mem_percent; both bound the learned slot limits), memory_limits (no
grant at memory stage stop), heavy_commands and never_block are used as
watch.py documents them. A signature is `<repository>: <command signature>`
(qualified()).
Exit codes:
hold: the command's exit code unchanged, 128+N when signal N ended it;
a command exiting 75 (EX_TEMPFAIL: it did no work, such as a declined
`operator.py granted`) leaves no history row;
before the command started 2 on a usage or config error or when it
cannot start or cannot restart with the installed code, 3 when cancel
removed the entry, 128+N when signal N reached hold; after a restart,
the installed hold's exit code.
list, history: 0 done, 2 config error.
cancel: 0 removed, 2 no waiting entry of the caller with that id.
block: 0 allow, or ask when it prints an ask decision, or a council root
that watch.py refuses; 2 refuse the tool call; every other error allows.
list, history, cancel, hold: 2 on a council root that watch.py refuses.
"""
from __future__ import annotations
# Trip-wire: run as a script, this file shadows the stdlib queue module, so nothing here
# may import a module that imports queue (logging.handlers, concurrent.futures, multiprocessing).
import argparse
import contextlib
import fcntl
import functools
import hashlib
import importlib.util
import json
import os
import re
import shlex
import signal
import subprocess
import sys
import time
from collections import Counter
from collections.abc import Iterator
from pathlib import Path
from types import ModuleType
from typing import NoReturn
SKILLS = Path(__file__).resolve().parents[2]
SIBLINGS = {
"watch": SKILLS / "kvb-org-organize" / "scripts" / "watch.py",
"council": SKILLS / "kvb-org-organize" / "scripts" / "council.py",
"rasci": SKILLS / "kvb-org-rasci" / "scripts" / "rasci.py",
"history": Path(__file__).resolve().parent / "history.py",
"nofollow": SKILLS / "kvb-org-organize" / "scripts" / "nofollow.py",
}
HOST_SKILLS = Path.home() / ".agents" / "skills"
EXE = os.path.realpath(sys.executable)
CODE_FILES = tuple(path.relative_to(SKILLS) for path in (Path(__file__).resolve(), *SIBLINGS.values()))
POLL_SECONDS = 20
CLAIM_SECONDS = 3 * POLL_SECONDS
TICK_SECONDS = 0.2
HOUR = 3600
CANCELLED = 3
NOT_RUN = 75
RAMP_SECONDS = 900
SETUP =frozenset({"cd", "pushd", "set", "source", ".", "export", "umask", "shopt", "trap"})
FORWARDED =(signal.SIGINT, signal.SIGTERM)
GATE = re.compile(r"\bgate\.py\s+(run|hold)\b")
THROUGH_QUEUE = re.compile(r"\bqueue\.py\s+hold\b")
BASH_RULE = re.compile(r"Bash(?:\((.*)\))?", re.DOTALL)
RULE_PART = re.compile(r"( \*|\*)")
MANAGED = Path("/etc/claude-code")
BUDGET_KEYS = ("budget_percent.ram", "budget_percent.cpu")
HOTFIX = "hotfix/"
@functools.cache
def sibling(name: str) -> ModuleType:
spec = importlib.util.spec_from_file_location(f"kvb_queue_{name}", SIBLINGS[name])
loaded = importlib.util.module_from_spec(spec)
spec.loader.exec_module(loaded)
return loaded
watch = sibling("watch")
history = sibling("history")
nofollow = sibling("nofollow")
STORE = watch.ROOT / "queue.json"
LOCK = watch.ROOT / "queue.lock"
HOLDS = watch.ROOT / "holds"
TAG = re.compile(r"\d+-\d+")
SILENCE = watch.ROOT / "radio-silence.json"
HISTORIES = (watch.HISTORY, watch.KEPT_HISTORY)
def code_of(root: Path) -> str | None:
try:
return hashlib.sha256(b"".join((root / name).read_bytes() for name in CODE_FILES)).hexdigest()[:12]
except OSError:
return None
CODE = code_of(SKILLS)
class Stale(Exception):
pass
def fail(message: str) -> NoReturn:
print(f"queue: {message}", file=sys.stderr)
sys.exit(2)
def config() -> dict:
cfg = watch.load_config()
if cfg is None:
fail(f"no config at {watch.CONFIG}; the queue reads the kvb-org-organize watch config")
problem = watch.missing(cfg, (*BUDGET_KEYS, *watch.MEMORY_KEYS))
if problem is not None:
fail(problem)
return cfg
def caps(cfg: dict) -> dict[str, int]:
return cfg.get("slot_limits", {})
def learned(cfg: dict, now: float) -> tuple[dict[str, dict], dict[str, dict]]:
return history.learn(history.read(HISTORIES), now, cfg)
def limits(cfg: dict, kinds: dict[str, dict], entries: list[dict]) -> dict[str, tuple[int, str]]:
return history.slot_limits(kinds, caps(cfg), {*cfg["slot_need_gb"], *(e["kind"] for e in entries)})
@functools.cache
def repository(path: str) -> str:
try:
common = subprocess.run(
["git", "rev-parse", "--path-format=absolute", "--git-common-dir"],
cwd=path, capture_output=True, text=True, check=False,
)
except OSError:
return os.path.basename(path)
if common.returncode != 0:
return os.path.basename(path)
return os.path.basename(os.path.dirname(common.stdout.strip()))
def command_words(argv: list[str]) -> list[str]:
"""Returns:
The command a wrapper runs: watch.program() unwrapped, the first simple
command of `sh -c` code that is no setup such as cd, set or source,
the tail after `--` of a Python script such as gate.py hold or
operator.py granted.
"""
run = watch.program(argv) or argv
code = watch.inline_code(run)
inner = first_command(code) if code is not None else script_tail(run)
return run if inner is None else command_words(inner)
def first_command(code: str) -> list[str] | None:
try:
parts = [part for part in map(watch.program, watch.segments(code)) if part and os.path.basename(part[0]) not in SETUP]
except ValueError:
return None
return parts[0] if parts else None
def script_tail(run: list[str]) -> list[str] | None:
if len(run) > 2 and watch.PYTHON.fullmatch(os.path.basename(run[0])) and run[1].endswith(".py") and "--" in run[2:-1]:
return run[run.index("--", 2) + 1 :]
return None
def qualified(workdir: str, argv: list[str]) -> str:
"""Returns:
`<repository>: <history.signature of command_words>`: the directory
holding the main checkout's .git, so every worktree of one repository
shares it, else the workdir's name; one make test of two repositories
stays apart.
"""
return f"{repository(workdir)}: {history.signature(command_words(argv))}"
def signature_of(entry: dict) -> str:
return qualified(entry["workdir"], shlex.split(entry["cmd"]))
def identity(pid: int) -> str | None:
"""Name a live process by its start time, so a reused pid does not match.
Args:
pid: the process id.
Returns:
The start time from /proc, `running` where /proc is unreadable, None for
a gone or zombie process.
"""
try:
os.kill(pid, 0)
except ProcessLookupError:
return None
except PermissionError:
pass
try:
fields = Path(f"/proc/{pid}/stat").read_text(encoding="utf-8").rsplit(")", 1)[1].split()
except OSError:
return "running"
return None if fields[0] == "Z" else fields[19]
def alive(pid: int | None, start: str | None) -> bool:
return pid is not None and start is not None and identity(pid) == start
def held(tag: object) -> bool:
if not isinstance(tag, str) or not TAG.fullmatch(tag):
return False
path = HOLDS / f"{tag}.lock"
try:
fd = nofollow.open_below(watch.ROOT, path, os.O_RDONLY, 0)
except FileNotFoundError:
return False
with open(fd, encoding="utf-8") as handle:
try:
fcntl.flock(handle, fcntl.LOCK_EX | fcntl.LOCK_NB)
except BlockingIOError:
return True
with contextlib.suppress(FileNotFoundError):
nofollow.unlink(watch.ROOT, path)
return False
@contextlib.contextmanager
def holding() -> Iterator[tuple[str, int]]:
tag = f"{os.getpid()}-{time.time_ns()}"
path = HOLDS / f"{tag}.lock"
with nofollow.locked(watch.ROOT, path, 0o600, None) as handle:
try:
yield tag, handle.fileno()
finally:
with contextlib.suppress(OSError):
nofollow.unlink(watch.ROOT, path)
def living(entry: dict) -> bool:
if "lock" in entry:
return held(entry["lock"])
return alive(entry["pid"], entry["pid_start"]) or (
entry["status"] == "active" and alive(entry["child"], entry["child_start"])
)
def prune(entries: list[dict]) -> list[dict]:
return [e for e in entries if living(e)]
def read_store() -> dict:
try:
return json.loads(STORE.read_text(encoding="utf-8"))
except FileNotFoundError:
return {"seq": 0, "entries": []}
except ValueError:
fail(f"{STORE} is not valid JSON; move it aside to start over")
@contextlib.contextmanager
def store() -> Iterator[dict]:
watch.ROOT.mkdir(parents=True, exist_ok=True)
with nofollow.locked(watch.ROOT, LOCK, 0o600, None):
data = read_store()
yield data
nofollow.replace(watch.ROOT, STORE, json.dumps(data, indent=1, sort_keys=True), 0o600)
def ordered(waiting: list[dict], ranks: dict[str, int], agents: dict[str, int], now: float) -> list[dict]:
def key(entry: dict) -> tuple:
hours = int((now - entry["enqueued"]) // HOUR)
rank = ranks[entry["task"]] if entry["task"] in ranks else len(ranks)
agent = agents[entry["name"]] if entry["name"] in agents else len(agents)
return (not entry["hotfix"], rank - hours, agent, entry["seq"])
return sorted(waiting, key=key)
def over_budget(used: dict[str, float], share: dict[str, float], budget: dict[str, float]) -> str | None:
over = [resource for resource in budget if used[resource] + share[resource] > budget[resource]]
if not over:
return None
return ", ".join(
f"{resource.upper()} {used[resource]:.0f} % in use or reserved + {share[resource]:.0f} % > budget {budget[resource]:g} %"
for resource in over
) + "; reserved"
def evaluate(entries: list[dict], free_gb: float, floor: float, stage: str, memory: str, silenced: bool,
slot_limits: dict[str, int], footprints: dict[str, dict[str, float]], budget: dict[str, float],
ranks: dict[str, int], agents: dict[str, int], loaded: dict[str, str],
exclusive: set[str], light: set[str], now: float) -> dict[str, str | None]:
"""Decide every waiting entry in grant order, without disk or processes.
Args:
entries: the queue's entries, active and waiting.
free_gb: free disk in GB.
floor: GB every grant must leave free (thresholds_gb.stop).
stage: the watch disk stage: ok, warn, stop or critical.
memory: the watch memory stage: ok, warn or stop.
silenced: whether a radio-silence mayday holds.
slot_limits: {kind: max concurrent jobs}, an upper cap beside the budget.
footprints: {entry id: {ram, cpu}}, the learned share of every entry
(history.footprint), in percentage points.
budget: {ram, cpu}: the sum of the footprints of active and granted
entries may not pass it; a job alone always fits.
ranks: {task: index in the register's ranking}; a task missing there
ranks after all of them.
agents: {agent name: index in the operator's agent order}; it breaks
ties of equal rank only, an entry whose holder is missing there after
all of them.
loaded: {entry id: why live CPU or RAM holds it back (load_reason())}
for the waiting entries the host load keeps out.
exclusive: kinds that run only beside light kinds (exclusive_kinds).
light: kinds allowed beside an exclusive one (light_kinds).
now: epoch seconds; each whole hour waited moves an entry one rank up.
Returns:
{entry id: None when granted, else the blocking reason}, in grant order.
An unblocked entry counts as granted for the entries after it, whoever
holds it, so a lower entry never takes a higher entry's slot; one
blocked by disk or budget reserves its need against the entries after it.
A light entry meets only the disk, RAM and CPU of running and granted
entries: reservations, a head the host load keeps out included, hold
back heavy kinds alone, so light jobs that keep coming can delay a heavy
head without bound. A hotfix entry that passes every check but the host
load holds back every entry after it, so lighter jobs cannot keep the
load up. While no queue job is active, any other entry the host load
alone keeps out reserves nothing and holds nothing back: that load lies
outside the queue, so holding the queue frees none of it. An exclusive
entry that only running jobs of other kinds keep out holds back every
entry after it but light ones, so none overtakes it. A waiting entry
whose holder has not polled for CLAIM_SECONDS reserves nothing and comes
last.
"""
active = [e for e in entries if e["status"] == "active"]
available = free_gb - sum(e["need_gb"] for e in active)
used = {resource: sum(footprints[e["id"]][resource] for e in active) for resource in budget}
taken = len(active)
granted = used, available, taken
counts = Counter(e["kind"] for e in active)
busy = {e["workdir"] for e in active}
verdicts: dict[str, str | None] = {}
waiting = [e for e in entries if e["status"] == "waiting"]
held = drain = None
for entry in ordered([e for e in waiting if not silent(e, now)], ranks, agents, now):
if held is not None:
verdicts[entry["id"]] = f"waits behind hotfix {held}, which the host load holds back"
continue
kind, need, workdir, share = entry["kind"], entry["need_gb"], entry["workdir"], footprints[entry["id"]]
if drain is not None and kind not in light:
verdicts[entry["id"]] = f"waits behind {drain}, which needs the host beside light kinds only"
continue
reason = host_reason(stage, memory, silenced, need) or slot_reason(kind, workdir, slot_limits, counts, busy)
if reason is None:
reason = alone_reason(kind, counts, exclusive, light)
if reason is not None and kind in exclusive:
drain = entry["id"]
if reason is None:
pool_used, pool_available, pool_taken = granted if kind in light else (used, available, taken)
reason, took, disk = resources(need, share, pool_used, pool_available, floor, budget, pool_taken)
if reason is None and entry["id"] in loaded and (entry["hotfix"] or not active):
reason = loaded[entry["id"]]
if entry["hotfix"]:
held = entry["id"]
else:
used, available = plus(used, took), available - disk
if reason is None:
taken += 1
counts[kind] += 1
busy.add(workdir)
if entry["id"] not in loaded:
granted = plus(granted[0], took), granted[1] - disk, granted[2] + 1
verdicts[entry["id"]] = reason
for entry in waiting:
if silent(entry, now):
verdicts[entry["id"]] = f"no poll for {now - entry['polled']:.0f} s; reserves nothing until it polls again"
return verdicts
def silent(entry: dict, now: float) -> bool:
return entry["status"] == "waiting" and now - entry.get("polled", now) > CLAIM_SECONDS
def rejoin(data: dict, entry: dict, now: float) -> None:
if silent(entry, now):
data["seq"] += 1
entry.update(seq=data["seq"], enqueued=now)
entry["polled"] = now
def plus(used: dict[str, float], share: dict[str, float]) -> dict[str, float]:
return {resource: used[resource] + share[resource] for resource in used}
def resources(need: float, share: dict[str, float], used: dict[str, float], available: float, floor: float,
budget: dict[str, float], taken: int) -> tuple[str | None, dict[str, float], float]:
"""Returns:
(reason or None, RAM and CPU share, disk GB) the job takes or
reserves: one passing the budget beside other jobs reserves its share,
one cutting into the floor its disk, a granted one takes both.
"""
if taken and (reason := over_budget(used, share, budget)) is not None:
return reason, share, 0
if available - need < floor:
return f"{need:g} GB would cut into the {floor:g} GB floor; reserved", dict.fromkeys(share, 0), need
return None, share, need
def host_reason(stage: str, memory: str, silenced: bool, need: float) -> str | None:
if stage in ("stop", "critical"):
return f"disk stage {stage}"
if stage == "warn" and need > 0:
return "disk stage warn admits only jobs that need no disk"
if memory == "stop":
return "memory stage stop"
if silenced:
return "radio silence"
return None
def slot_reason(kind: str, workdir: str, slot_limits: dict[str, int], counts: Counter, busy: set[str]) -> str | None:
if kind not in slot_limits:
return f"slot_limits has no {kind}"
if counts[kind] >= slot_limits[kind]:
return f"all {slot_limits[kind]} {kind} slots taken"
if workdir in busy:
return f"{workdir} runs another job"
return None
def alone_reason(kind: str, counts: Counter, exclusive: set[str], light: set[str]) -> str | None:
if kind in light:
return None
running = sorted(k for k, n in counts.items() if n and k not in light)
if kind in exclusive and running:
return f"{kind} runs only beside {', '.join(sorted(light)) or 'nothing'}, but {', '.join(running)} runs"
blocking = [k for k in running if k in exclusive]
if blocking:
return f"{', '.join(blocking)} runs only beside {', '.join(sorted(light)) or 'nothing'}"
return None
def register_ranks() -> dict[str, int]:
rasci = sibling("rasci")
return {task: index for index, task in enumerate(rasci.ranked(rasci.merged(False)))}
def agent_ranks() -> dict[str, int]:
return {name: index for index, name in enumerate(sibling("rasci").agent_order())}
def stage(cfg: dict, free_gb: float, now: float) -> str:
state = watch.read_json(watch.STATE)
return state["stage"] if "stage" in state else watch.stage_for(free_gb, [], cfg, now)
def silenced(now: float) -> bool:
state = watch.read_json(SILENCE)
return "until" in state and state["until"] > now
def footprint(entry: dict, signatures: dict[str, dict], kinds: dict[str, dict]) -> dict[str, float] | None:
return history.footprint(entry["kind"], signature_of(entry), signatures, kinds)
def held_back(cfg: dict, entries: list[dict], now: float, signatures: dict[str, dict], kinds: dict[str, dict],
cpu: float, mem: float) -> dict[str, str]:
loaded = {}
for entry in entries:
if entry["status"] == "waiting":
pending = pending_ram(entries, entry["id"], now, signatures, kinds)
if (reason := load_reason(cfg["load_limits"], cpu, mem, pending)) is not None:
loaded[entry["id"]] = reason
return loaded
def decide(cfg: dict, entries: list[dict], free_gb: float, now: float, signatures: dict[str, dict],
kinds: dict[str, dict], loaded: dict[str, str]) -> dict[str, str | None]:
slots = {kind: limit for kind, (limit, _) in limits(cfg, kinds, entries).items()}
budget = cfg["budget_percent"]
footprints = {}
for entry in entries:
share = footprint(entry, signatures, kinds)
footprints[entry["id"]] = dict(budget) if share is None else share
return evaluate(
entries, free_gb, cfg["thresholds_gb"]["stop"], stage(cfg, free_gb, now),
watch.memory_stage(watch.memory(), cfg), silenced(now), slots, footprints, budget,
register_ranks(), agent_ranks(), loaded,
set(cfg.get("exclusive_kinds", [])), set(cfg.get("light_kinds", [])), now,
)
def load_reason(limits: dict, cpu: float, mem: float, ram_share: float) -> str | None:
"""Args:
limits: load_limits of the config.
cpu: CPU busy now, in percent.
mem: RAM used now, in percent; stacks and jobs outside the queue
included.
ram_share: what the job and the active ones may still take, in
percentage points (pending_ram()).
Returns:
None when CPU stays below cpu_percent and RAM used plus the share
below mem_percent, else why not.
"""
if cpu >= limits["cpu_percent"]:
return f"CPU {cpu:.0f} % busy reaches the limit {limits['cpu_percent']} %"
if mem + ram_share >= limits["mem_percent"]:
return f"RAM {mem:.0f} % used + {ram_share:.0f} % still to come reach the limit {limits['mem_percent']} %"
return None
def ram_share(entry: dict, signatures: dict[str, dict], kinds: dict[str, dict]) -> float:
share = footprint(entry, signatures, kinds)
return history.seed_ram(kinds) if share is None else share["ram"]
def pending_ram(entries: list[dict], entry_id: str, now: float, signatures: dict[str, dict],
kinds: dict[str, dict]) -> float:
"""Returns:
The RAM share of entry_id plus the whole share of every active job
granted less than RAMP_SECONDS ago (or without a grant time): what
may still come once they ramp up. An older job counts as ramped, its
RAM inside the RAM used; one that peaks later is bounded by the
memory stage only.
"""
mine = [e for e in entries if e["id"] == entry_id]
young = [e for e in entries if e["status"] == "active" and now - e.get("granted", now) < RAMP_SECONDS]
return ram_share(mine[0], signatures, kinds) + sum(ram_share(e, signatures, kinds) for e in young)
def workdir() -> str:
try:
top = subprocess.run(
["git", "rev-parse", "--show-toplevel"], capture_output=True, text=True, check=False
)
except OSError:
return os.path.realpath(os.getcwd())
path = top.stdout.strip() if top.returncode == 0 else ""
return os.path.realpath(path or os.getcwd())
def enqueue(fields: dict) -> dict:
with store() as data:
data["seq"] += 1
entry = {"id": f"{fields['kind']}-{data['seq']}", "seq": data["seq"], **fields}
data["entries"].append(entry)
return entry
def update(entry_id: str, **fields: object) -> None:
with store() as data:
for entry in data["entries"]:
if entry["id"] == entry_id:
entry.update(fields)
def leave(entry_id: str) -> None:
with store() as data:
data["entries"] = [e for e in data["entries"] if e["id"] != entry_id]
def pause(caught: list[int]) -> None:
deadline = time.monotonic() + POLL_SECONDS
while not caught and time.monotonic() < deadline:
time.sleep(TICK_SECONDS)
def wait(cfg: dict, entry_id: str, caught: list[int]) -> tuple[int | None, int]:
"""Poll until the grant.
Args:
cfg: the config.
entry_id: this hold's entry.
caught: signals the caller caught; the first ends the wait.
Returns:
(None, other active jobs at the grant) once granted, else (the exit
code, 0).
"""
shown = None
while not caught:
host = code_of(HOST_SKILLS)
if host not in (None, CODE):
raise Stale(host, caught)
cpu, mem = watch.load(cfg)
free, _ = watch.disk(cfg)
now = time.time()
signatures, kinds = learned(cfg, now)
with store() as data:
data["entries"] = prune(data["entries"])
mine = [e for e in data["entries"] if e["id"] == entry_id]
if not mine:
print(f"queue: {entry_id} was cancelled", file=sys.stderr)
return CANCELLED, 0
rejoin(data, mine[0], now)
loaded = held_back(cfg, data["entries"], now, signatures, kinds, cpu, mem)
verdicts = decide(cfg, data["entries"], free, now, signatures, kinds, loaded)
reason = verdicts[entry_id] or loaded.get(entry_id)
mine[0]["reason"] = reason
if reason is None:
concurrent = sum(e["status"] == "active" for e in data["entries"])
mine[0].update(status="active", granted=now)
return None, concurrent
position = list(verdicts).index(entry_id) + 1
if (position, reason) != shown:
print(f"queue: {entry_id} waits at position {position}: {reason}", file=sys.stderr, flush=True)
shown = (position, reason)
pause(caught)
return 128 + caught[0], 0
def send(child: subprocess.Popen, signum: int) -> None:
if child.returncode is None:
with contextlib.suppress(ProcessLookupError, PermissionError):
os.killpg(child.pid, signum)
def measure(cfg: dict) -> dict[str, float]:
free, _ = watch.disk(cfg)
cpu, mem = watch.load(cfg)
return {"free_gb": free, "mem": mem, "cpu": cpu}
def supervise(cfg: dict, child: subprocess.Popen, samples: list[dict[str, float]]) -> int:
"""Wait for the child, measuring every POLL_SECONDS and once at its end.
Args:
cfg: the config.
child: the running command.
samples: the measurements so far; each new one is appended.
Returns:
The child's return code.
"""
code = None
while code is None:
with contextlib.suppress(subprocess.TimeoutExpired):
code = child.wait(timeout=POLL_SECONDS)
samples.append(measure(cfg))
return code
def keep(row: dict) -> None:
try:
nofollow.append(watch.ROOT, watch.HISTORY, history.line(row), 0o600)
except OSError as error:
# A full disk refuses the row; the job's exit code must still come through.
print(f"queue: no history row for {row['id']}: {error.strerror}", file=sys.stderr)
def record(job: dict, start: float, code: int, samples: list[dict[str, float]], concurrent: int) -> None:
if code != NOT_RUN:
keep(history.row(job, start, time.time(), code, samples, concurrent))
def cmd_hold(args: argparse.Namespace) -> int:
cfg = config()
if args.kind not in cfg["slot_need_gb"]:
fail(f"kind {args.kind!r} is not in slot_need_gb of {watch.CONFIG}")
rasci = sibling("rasci")
if args.task not in rasci.merged(False):
fail(f"{args.task} is no open task of the kvb-org-rasci register")
if args.hotfix is not None and HOTFIX + args.hotfix not in rasci.load():
fail(f"the register has no row {HOTFIX}{args.hotfix}")
here = workdir()
signature = qualified(here, args.command)
signatures, _ = learned(cfg, time.time())
need = args.need_gb
if need is None:
need = history.need_gb(signatures, signature)
if need is None:
need = cfg["slot_need_gb"][args.kind]
if need < 0:
fail("--need-gb must be 0 or more")
council = sibling("council")
session, _ = council.caller(args.session)
caught: list[int] = []
child: subprocess.Popen | None = None
def forward(signum: int, _frame: object) -> None:
if child is None:
caught.append(signum)
else:
send(child, signum)
for signum in FORWARDED:
signal.signal(signum, forward)
with holding() as (tag, lock_fd):
entry = enqueue({
"kind": args.kind, "task": args.task, "need_gb": need, "hotfix": args.hotfix,
"workdir": here, "session": session, "name": council.live_sessions().get(session) or session,
"lock": tag, "code": CODE, "pid": os.getpid(), "pid_start": identity(os.getpid()), "child": None,
"child_start": None, "status": "waiting", "enqueued": time.time(), "granted": None,
"cmd": shlex.join(args.command),
})
try:
code, concurrent = wait(cfg, entry["id"], caught)
if code is not None:
return code
if caught:
return 128 + caught[0]
samples = [measure(cfg)]
start = time.time()
try:
child = subprocess.Popen(args.command, start_new_session=True, pass_fds=(lock_fd,))
except OSError as error:
print(f"queue: cannot start {args.command[0]}: {error.strerror}", file=sys.stderr)
return 2
for signum in caught:
send(child, signum)
update(entry["id"], child=child.pid, child_start=identity(child.pid))
code = supervise(cfg, child, samples)
code = code if code >= 0 else 128 - code
job = {"id": f"{entry['id']}-{time.time_ns()}", "kind": args.kind, "signature": signature,
"workdir": entry["workdir"]}
record(job, start, code, samples, concurrent)
return code
finally:
leave(entry["id"])
def cmd_list(_: argparse.Namespace) -> int:
cfg = config()
free, _ = watch.disk(cfg)
now = time.time()
signatures, kinds = learned(cfg, now)
with store() as data:
data["entries"] = prune(data["entries"])
entries = list(data["entries"])
verdicts = decide(cfg, entries, free, now, signatures, kinds, {})
by_id = {e["id"]: e for e in entries}
running = [(signature_of(e), e["granted"]) for e in entries if e["status"] == "active"]
rows = [
("run", e, history.eta(signatures, [(signature_of(e), e["granted"])], [], now), "-")
for e in entries if e["status"] == "active"
]
ahead: list[str] = []
for n, (entry_id, reason) in enumerate(verdicts.items(), 1):
e = by_id[entry_id]
polled = reason if silent(e, now) else e.get("reason") or reason
rows.append((str(n), e, history.eta(signatures, running, ahead, now), polled or "granted on its next poll"))
ahead.append(signature_of(e))
host = code_of(HOST_SKILLS) or CODE
print("position\tid\tstatus\tkind\ttask\tneed_gb\twaited_min\teta_min\tworkdir\treason")
for position, e, eta, reason in rows:
if e.get("code") != host:
reason = f"queue code {e.get('code') or 'without a stamp'}, the host runs {host}: {reason}"
waited = ((e["granted"] or now) - e["enqueued"]) / 60
print(tab_line(
[position, e["id"], e["status"], e["kind"], e["task"], f"{e['need_gb']:g}", f"{waited:.0f}",
"-" if eta is None else f"{eta:.0f}", e["workdir"], reason]
))
return 0
def tab_line(fields: list[object]) -> str:
return "\t".join("".join(c if c.isprintable() else c.encode("unicode_escape").decode() for c in str(f)) for f in fields)
def cmd_history(_: argparse.Namespace) -> int:
cfg = config()
signatures, kinds = learned(cfg, time.time())
print(history.report(signatures, kinds, limits(cfg, kinds, [])), end="")
return 0
def cmd_cancel(args: argparse.Namespace) -> int:
session, _ = sibling("council").caller(args.session)
with store() as data:
mine = [
e for e in data["entries"]
if e["id"] == args.id and e["status"] == "waiting" and e["session"] == session
]
if not mine:
fail(f"no waiting entry {args.id} of this session; `queue.py list` shows the ids")
data["entries"].remove(mine[0])
print(f"cancelled {args.id}")
return 0
def settings_files(project: Path) -> list[Path]:
user = Path.home() / ".claude"
return [
MANAGED / "managed-settings.json",
*sorted((MANAGED / "managed-settings.d").glob("*.json")),
user / "settings.json",
user / "settings.local.json",
project / ".claude" / "settings.json",
project / ".claude" / "settings.local.json",
]
def bash_rule(rule: str) -> re.Pattern | None:
match = BASH_RULE.fullmatch(rule)
if match is None:
return None
body = match.group(1) or "*"
if body.endswith(":*"):
body = body[:-2] + " *"
return re.compile(
"".join(
r"(?:\s.*)?" if part == " *" else ".*" if part == "*" else re.escape(part)
for part in RULE_PART.split(body)
),
re.DOTALL,
)
def permission(commands: list[str], project: Path) -> str | None:
settings = []
for path in settings_files(project):
with contextlib.suppress(FileNotFoundError):
settings.append(json.loads(path.read_text(encoding="utf-8")).get("permissions") or {})
for decision in ("deny", "ask"):
rules = [bash_rule(rule) for permissions in settings for rule in permissions.get(decision) or []]
if any(rule and rule.fullmatch(command) for rule in rules for command in commands):
return decision
return None
def held_commands(command: str) -> list[list[str]]:
return [
cli().parse_args(part[at + 1 :]).command
for part in watch.segments(command)
for at, token in enumerate(part)
if token.endswith("queue.py") and part[at + 1 : at + 2] == ["hold"]
]
def tails(argv: list[str]) -> list[str]:
return [shlex.join(argv[at:]) for at, word in enumerate(argv) if not word.startswith("-")]
def cmd_block() -> int:
payload = json.load(sys.stdin)
raw = (payload.get("tool_input") or {}).get("command") or ""
try:
holds = held_commands(raw)
commands = [t for argv in holds for t in tails(argv)]
decision = permission(commands, Path(os.environ["CLAUDE_PROJECT_DIR"])) if commands else None
except (ValueError, KeyError, OSError, SystemExit) as error:
if "queue" in raw:
print(
f"kvb-org-queue: cannot check the held command against the settings ({error!r}); "
"fix the settings or run the command outside queue.py hold.",
file=sys.stderr,
)
return 2
holds, decision = [], None
shown = " ; ".join(shlex.join(argv) for argv in holds)
if decision == "deny":
print(f"kvb-org-queue: the held command `{shown}` meets a deny rule of the settings.", file=sys.stderr)
return 2
if decision == "ask":
reason = f"kvb-org-queue: the held command `{shown}` meets an ask rule of the settings"
print(json.dumps({"hookSpecificOutput": {
"hookEventName": "PreToolUse", "permissionDecision": "ask", "permissionDecisionReason": reason,
}}))
return 0
cfg = watch.load_config()
if cfg is None or cfg.get("queue_enforce") is not True:
return 0
gated = {**cfg, "heavy_commands": [*cfg["heavy_commands"], GATE.pattern]}
if watch.heavy(raw, gated, (THROUGH_QUEUE.pattern, *cfg["never_block"])):
print(
f"kvb-org-queue: heavy jobs run through the queue: start `python3 {Path(__file__).resolve()} hold "
"--kind <kind> --task <register task> -- <command>` in the background; it runs the command on its grant.",
file=sys.stderr,
)
return 2
return 0
def cli() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
sub = parser.add_subparsers(dest="cmd", required=True)
hold = sub.add_parser("hold")
hold.add_argument("--kind", required=True)
hold.add_argument("--task", required=True)
hold.add_argument("--need-gb", type=float)
hold.add_argument("--hotfix")
hold.add_argument("--session")
hold.add_argument("command", nargs="+")
sub.add_parser("list")
sub.add_parser("history")
cancel = sub.add_parser("cancel")
cancel.add_argument("id")
cancel.add_argument("--session")
sub.add_parser("block")
return parser
def main() -> int:
args = cli().parse_args()
refused = watch.guard_root(hook=args.cmd == "block")
if refused is not None:
return refused
if args.cmd == "block":
try:
return cmd_block()
except (OSError, ValueError, KeyError, TypeError, AttributeError, re.error):
return 0
commands = {"hold": cmd_hold, "list": cmd_list, "history": cmd_history, "cancel": cmd_cancel}
try:
return commands[args.cmd](args)
except Stale as stale:
host, caught = stale.args
for signum in FORWARDED:
signal.signal(signum, signal.SIG_DFL)
if caught:
return 128 + caught[0]
script = HOST_SKILLS / CODE_FILES[0]
print(
f"queue: this hold ran queue code {CODE} from {SKILLS}, but the queue code installed on this host is "
f"{host} in {HOST_SKILLS}. Holders on different code each apply their own rules to the shared queue "
f"and together overrun the RAM and CPU budget, so this hold grants nothing on its own code. "
f"Restarting this hold with {script}; run every queue.py command from that path.",
file=sys.stderr, flush=True,
)
try:
os.execv(EXE, [EXE, str(script), *sys.argv[1:]])
except OSError as error:
fail(f"cannot restart with {script}: {error.strerror}; run the same hold with {script}")
if __name__ == "__main__":
sys.exit(main())