Use before any heavy work on the shared machine - builds beyond the ordinary gate, mutation or stress runs, LAN or lossy e2e, renders, local-AI generation, training - to wrap it in the machine lock or its background mode, read status, and handle busy and reservation refusals.
Install
npx skillscat add josiahallard/agentic-project-manager-kit/machine-lock-run Install via the SkillsCat registry.
Machine lock
One machine is shared by every repo, every host's sessions, the local generators and training. Heavy work runs inside the lock; light work (ordinary gates, unit tiers) never locks. One queue, first come first served; sizes are whole cores plus gpu slots from the machine's layout; an agent run takes at most the agent cap of cores.
- Look first:
python <kit>/tools/machine_lock.py status(each waiting run's position and expected start). - A tool that takes the lock itself needs no wrapper and runs straight through inside a locked run:
python <kit>/tools/gate.py -C <worktree>,<kit>/optional/comfy/comfy_gen.py, the repo's own e2e or daily runners. The profile names others:python <kit>/tools/doc_index.py -C <worktree> --find lock. - Anything else heavy:
python <kit>/tools/machine_lock.py run --repo <repo> --slug <slug> --kind <kind> [--cores N] [--expect D] [--max-wait 5m | --no-wait] -- <command>, with the kind and ports your brief assigns. It waits with no limit by default. - The kind sets the size (
<kit>/kitlib/lock/kinds.json): engine-test, engine-run, build medium; graph-build, daily-full, cpu-eval heavy; local-ai, render medium plus the gpu; measure heavy plus the gpu.--cores Nchanges the count, up to the agent cap. Pass--expect <duration>when you know it: a run that ends before the runs ahead start may go first. - Read your threads from
MACHINE_LOCK_CPUS(alsoMACHINE_LOCK_CPUMASK), never assume them. Docker: pass--cpuset-cpus "$MACHINE_LOCK_CPUS"todocker run, ordocker update <container>beforedocker exec; run container work under an in-containertimeout -k 30 <limit>and add--on-release "<command that kills it>". --hold <id>only with a hold id the owner or PM gave you (exit 12 if it cannot take the run);--gpu-share trainingonly for model training on local-ai or render;--detachprints<run id> pid <n>, then follow it withstatus <run id>.- Training:
python <kit>/tools/machine_lock.py background --repo <repo> --slug <slug> -- <launcher>; the trainer pollsshould_pause()in<kit>/kitlib/lock/bg_client.pyand checkpoints and exits 75 when a due run needs its slots. - Output goes to a log file; read the log when the run ends.
- A run past its kind's
warn_mingets a WARNING, never a kill: check it is still progressing. - End a run you started:
python <kit>/tools/machine_lock.py stop <run id>.
Refusals: exit 6 busy (--no-wait), 8 --max-wait ran out: waiting is normal. Exit 11: a reservation leaves too few slots: wait or ask your coordinator, and pass --expect when the run is shorter than its kind's guide. Exit 2 nested: a run needing more than the outer locked run holds; run it outside. Exit 10 (lock not taken), 1 (could not start the command), 12: report. A bare bash is Git Bash, never WSL; none found is refused before booking: pass a full path. Build daemons a gate leaves are stopped only when that gate alone used them (machine config machine_lock "daemons"). Exclusive window declared for another session: pause every gate, build, heavy test and landing until it closes.
Never
- Run heavy work unlocked because the queue is busy, or take an environment variable claiming a lock as proof of one.
- Truncate a locked run's live output (
head,Select-Object -First): it kills the wrapper. - Touch another run's lock, records or processes, or use
stop --force,hold,divisionorgame: report the run id andstatustext to your coordinator. - Time a measurement under load without labelling it "shared machine".
Details: <kit>/docs/lock-api.md; read-only lock tools over MCP: <kit>/docs/lock-mcp.md.