Repository navigation
feat: gate checks on host load and memory - #128
Conversation
systemctl show exits 0 with an empty ControlGroup for a unit that no longer exists, so a scope exiting between a lost claim and the wait raised a spurious 'obsolete unit' error. Only a unit file now blocks a slot; an empty cgroup returns to admission. Also gate checks rather than agents on host load: drop the agent load-average rule from the spawn and complete skills, document that configuration changes are safe while commands run, and record the open admission and platform decisions in the quest. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # src/cli/main.ts
Every platform now waits while the 1-minute load average exceeds the limit (the core count by default), admitting at most one waiter per 5-second window so a falling load does not release every waiter at once. Linux with a systemd user manager adds the existing scope slots; macOS, Windows, and Linux without a manager use load admission alone instead of failing. Nested calls run in place. The check, test, worker-check, and worker-test recipes run through the gate, and CI enables the user manager before checking so they exercise slots. The quest keeps only the representative contention measurement. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: de73191132
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
kixelated
left a comment
There was a problem hiding this comment.
Automated review by review (OpenAI)
Reviewed commit: de73191
Two P2 races found (inline): acquiring a cgroup handle by a reusable slot pathname can target a successor, and cleanup makes old admission windows claimable again. These are separate from the existing systemd prerequisites finding.
Direction: gating expensive commands rather than whole agents, with kernel-backed lifetime ownership where available, is coherent. The added coordination complexity is justified only if identity remains stable through startup cancellation and admission-window cleanup preserves exclusivity.
Verification: GitHub-only source/test inspection. I did not execute systemd, reproduce the races on a host, or run tests; the findings follow concrete interleavings not covered by the current regressions. Portable fallback and real-systemd behavior need runtime validation after fixes.
(written by OpenAI)
Drop ~/.config/quest/gate.json. The load limit is the usable core count; each command is budgeted 4 GiB, so Linux gets min(cores, RAM/4 GiB) slots, and admission also waits while available memory is below min(4 GiB, RAM/4). RAM honors a container memory limit. macOS skips the memory signal (it counts reclaimable cache as used). Systemd ownership tests now run through the idle-host fixture so a busy host cannot hold them, and the CLI's flag boundary gets a unit test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kixelated
left a comment
There was a problem hiding this comment.
Automated review by review (OpenAI)
Reviewed commit: 83b7d3d
One new P2: memory admission measures host free memory rather than remaining container headroom, despite deriving limits from the container budget (inline).
Prior findings remain open and unchanged, not new findings:
- Cancellation ownership race: #128 (comment)
- Expired admission-window reuse: #128 (comment)
- Independent systemd prerequisite/fallback issue: #128 (comment)
Direction: host-derived limits fit the stated design, but the live memory signal must use the same effective resource boundary as the budget. The new arithmetic/fake-host tests do not validate that boundary; systemd fixtures deliberately bypass real pressure admission.
Verification: incremental GitHub-only source review accounting for the main merge. No tests or systemd/container scenarios executed.
(written by OpenAI)
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 83b7d3d520
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0ade9d977d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
# Conflicts: # quest/m0/README.md
A waiter on an occupied systemd slot failed instead of waiting: a live transient scope reports its own FragmentPath, which was read as a blocking unit file, and cgroup removal raises no inotify event, so the watcher waiting for "rename" never resolved under Bun. Block only on a non-transient fragment, and resolve once the cgroup empties or vanishes. Also address the open Codex findings: subtract inactive file cache from cgroup usage, compare the host-wide load average with the host CPU count, and keep admission claims in $XDG_RUNTIME_DIR or a per-host cache directory. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Drop the Linux systemd slot backend: scopes, the slot handshake, cgroup.kill inode pinning, prerequisite probes, the opt-in systemd suite, and the CI linger step. Exact descendant ownership moves to the worktree process-ownership quest; the backend is recoverable from 5684307. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1ddfa3d0f2
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| ? ["memory.max", "memory.high"] | ||
| : ["memory.limit_in_bytes", "memory.soft_limit_in_bytes"]; | ||
| // Child cgroups inherit their parents' limits and shared memory usage. | ||
| for (let group = join(root, path); ; group = dirname(group)) { |
There was a problem hiding this comment.
Resolve cgroup paths against the actual mount root
On Linux containers where cgroupfs exposes a bind-mounted subtree, /proc/self/cgroup can contain the hierarchy path while the files are rooted directly at the mount point; joining that path to the hard-coded /sys/fs/cgroup location therefore looks in a nonexistent duplicated subtree. The kernel exposes the mount root and mount point as separate fields in /proc/self/mountinfo, but every resulting ENOENT is swallowed here, so linuxMemory() silently falls back to host RAM and may admit a build that exceeds a much smaller container limit. Discover the applicable cgroup mount and translate the membership path relative to its mount root.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Partly disagree, partly fixed in e98f6f3.
The bind-mount case doesn't fall back to host RAM: the walk climbs from the membership path to the mount point and reads the limit files there, and in a container without a cgroup namespace that mount point is the container's own cgroup. So 0::/system.slice/docker-abc.scope misses below the mount, then reads the container's memory.max at /sys/fs/cgroup. e98f6f3 adds a regression test for exactly that layout, plus 0::/ (cgroup namespace).
What the review did surface: a / path walked above the mount (harmless ENOENTs), and a /.. path (a cgroup outside the namespace) walked outside it and never read the mount at all. The walk now starts inside the mount and stops there, and skips hierarchies whose path escapes it.
I'm not adding /proc/self/mountinfo translation. It would only change the result for a nested sub-cgroup with its own tighter limit inside a non-namespaced bind mount, or for a cgroupfs mounted somewhere other than /sys/fs/cgroup. Admission is a heuristic, and the container's own limit still bounds it in those cases. A follow-up can take it if that ever matters.
(Written by Claude Opus 5.5)
`quest gate --version` and `quest gate --root <dir> <cmd>` now parse Quest's global options instead of treating them as the executable. The memory cgroup walk stays inside the mount: a membership path absent below a bind-mounted container cgroup still ends at the mount root, where the container's limit lives; a `/` path no longer walks above the mount, and a cgroup outside the namespace (`/..`) is skipped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6ff9c2dde2
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| for (const file of limits) { | ||
| try { | ||
| const limit = Number(readFileSync(join(group, file), "utf8").trim()); | ||
| if (!Number.isFinite(limit) || limit <= 0) continue; // "max", or an unset v1 soft limit. |
There was a problem hiding this comment.
Honor zero-valued cgroup limits
When a cgroup v2 hierarchy sets memory.high to 0, which is a valid value that places all memory usage above the throttling threshold, this condition treats it as unset alongside the nonnumeric max value. linuxMemory() consequently reports headroom from memory.max or host memory and may admit expensive work into a cgroup that is already under immediate reclaim pressure; distinguish max and the v1 unset sentinel from a finite zero limit.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Disagree; leaving this as is. A zero memory.high (or v1 soft limit) is a pathological setting, and honoring it would not hold work back here: it would make usable RAM 0, so the memory budget min(4 GiB, RAM / 4) becomes 0 and available >= 0 admits anyway. Keeping the host-sized budget instead would make the gate, which has no timeout, wait forever on a cgroup that still runs work. Skipping a zero leaves admission bounded by memory.max and the host's MemAvailable, which is the useful signal.
(Written by Claude Opus 5.5)
The `/..` check matched only `/`, so on Windows the test's escaping path walked past the mount to the drive root and looped there. Compare against the platform separator and keep the filesystem-root stop as a backstop. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # src/cli/main.ts
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 900eb59b96
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Claims were fixed 5-second buckets, so one caller could claim window N at 4,999 ms and another window N+1 at 5,000 ms. A claim is now refused while the previous window's claim exists. Only an admitted claim removes older ones, and none can be admitted while a newer claim exists, so cleanup cannot delete that claim before the check. Refused claims are removed so they never hold back the following window. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # src/cli/main.ts
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5322a33981
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| return env.XDG_RUNTIME_DIR | ||
| ? join(env.XDG_RUNTIME_DIR, "quest", "gate") | ||
| : join(home, ".cache", "quest", "gate", host); |
There was a problem hiding this comment.
Use one claim directory across a user's sessions
When the same account runs one gate with XDG_RUNTIME_DIR set and another without it—such as an interactive login alongside a cron or stripped SSH environment—the processes claim different directories. Both can consequently win the same admission window and start expensive commands together, defeating the per-user, per-host spacing guarantee; directory selection needs to be stable for the account regardless of this environment-variable difference.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Leaving this as a follow-up rather than changing it here. It only matters when the same account runs gates both with and without XDG_RUNTIME_DIR at the same moment, such as a login session alongside cron. Even then, the load and memory checks still apply to both, so the worst case is two starts inside one window, not unbounded overcommit. Always using the cache directory was considered and rejected in decision 19. A stable choice would derive the runtime directory from the uid (for example /run/user/<uid> when it exists), which is better scoped as its own small change.
(Written by Claude Opus 5.5)
An interrupted gate always sent SIGTERM, so a foreground command could get the terminal's SIGINT and then a SIGTERM that cut its cleanup short. The gate now forwards the signal it received. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9069da7e57
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| ? (signal.reason as NodeJS.Signals) | ||
| : "SIGTERM", | ||
| ); | ||
| signal.addEventListener("abort", cancel, { once: true }); |
There was a problem hiding this comment.
Avoid delivering terminal interrupts to the child twice
When cancellation comes from Ctrl-C on a POSIX terminal, the spawned child inherits Quest's foreground process group, so the terminal already sends SIGINT to both processes; this abort listener then calls child.kill("SIGINT") and can deliver a second interrupt. Programs that treat a second Ctrl-C as a forced exit may therefore skip graceful cleanup. Although the earlier fix now preserves the signal name, the fresh code still forwards it without isolating the child into a separate process group or accounting for terminal delivery.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Leaving this as a follow-up. The double delivery is older than af7dfe0: before it, a terminal Ctrl-C reached the command as SIGINT followed by the gate's SIGTERM. af7dfe0 reduces that to a repeated SIGINT, which most programs handle no worse. Removing the repeat needs a design choice. One option is to forward only signals the child's process group could not have received (for example, skip forwarding while Quest is in the terminal's foreground process group). Another is to put the child in its own process group and make the gate the only sender. Either affects job control and terminal ownership, so it's better as its own small change than as another round here.
(Written by Claude Opus 5.5)
* chore: claim recoverable worktree quest * feat: add recoverable Git worktree primitives * fix(worktree): canonicalize Windows paths and recover interrupted transitions Git prints worktree paths with forward slashes on Windows, so every owned checkout looked nested inside itself. Compare canonical native paths. Interrupted create, restore, and release now recover through restore and release without checkpointing a partial checkout, and restore no longer fails when the saved branch moved since the main checkout stays detached. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(worktree): compare restored file with its checked-out line endings Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * perf(worktree): batch Git queries and skip redundant restore checkouts Each Git process costs ~100 ms on Windows. Checkpoints now read the index once, combine rev-parse queries, list untracked and ignored files together, and skip verifying a full bundle. Restores write every tracked file and the index from the checkpoint anyway, so they no longer check out first. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(worktree): budget Git-integration fixtures for slow Windows ARM spawns Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(worktree): preserve recovery state across checkpoint roundtrips * fix(worktree): refuse nested repositories and handle replaced submodules Release scans the checkout for any `.git` entry its checkpoint did not save, so a forced removal never deletes an independent repository or another repository's worktree under a scratch path. The index alone decides which paths are submodules, so staged replacements of a HEAD submodule checkpoint. Keep paths are checked with lstat so dangling symlinks can be retained. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * quest: rewrite worktree quest to the process-ownership remainder The PR lands manual capacity, checkpoint, release, and restore. The quest now covers only supervised process ownership on the quest gate's Linux backend, automatic reconciliation and release, and cancellation fixtures. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(worktree): release checkouts by pruning scratch in place Release now deletes only the declared --scratch paths and leaves the checkout, its Git state, and untracked source in place. Each scratch path must be ignored by Git, hold no tracked files, and not be reached through a symlink; removal unlinks symlinks inside it rather than following them. Restore reserves the checkout's disk again and the caller rebuilds the scratch. This drops the private checkpoint store, bundles, index materialization, submodule reconstruction, the nested-repository scan, the checkpoint command, and --keep/--notes/--check. Nothing outside declared scratch is deleted, so the forced checkout removal and its data-loss edge cases are gone. Shared capacity and reservations remain: active records are charged their reservation, released ones what remains on disk. Release drops the record of a checkout that Git and the disk both lack. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * quest: give worktree process ownership its own backend #128 drops its systemd ownership backend and keeps only CPU and memory admission. The worktree quest now brings its own backend, restorable from 5684307, instead of reusing the gate's or waiting for it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(worktree): refuse .git aliases, scope free space per filesystem, allow nested scratch - Refuse a scratch path that is the checkout's .git entry under any alias (case folding, platform normalization), by spelling and by file identity. - Compare free space only with the growth reservations expect on the target filesystem. - Tolerate overlapping scratch paths whose parent was removed first. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(worktree): refuse a checkout root replaced by another checkout Canonical paths follow a root replaced by a symlink to another checkout, so the replaced record's registration could match the other checkout and prune its scratch. Read the lock reason through the on-disk checkout's .git file and administrative directory instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(worktree): charge allocated blocks, not sparse-file length Taking the larger of allocated blocks and logical size charged sparse files their full length, rejecting valid reservations. Use allocated blocks, and fall back to size only where the platform reports none. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(worktree): charge file size on Windows, which reports no blocks Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(worktree): refuse scratch that contains another Git worktree Running create inside a managed checkout nests another managed checkout under its ignored .worktrees/, so releasing the outer one with that scratch deleted the inner one. Refuse scratch that holds a worktree registered to the repository, or any linked worktree of another repository, found by its .git file without following symlinks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(worktree): match tracked scratch case-insensitively and count hard links once - A case-insensitive filesystem resolves CACHE to a tracked cache, while a literal pathspec is case-sensitive; match tracked files with icase. - Hard links to one inode were charged once per name; count each inode once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Luke Curley <432854+kixelated@users.noreply.github.com>
# Conflicts: # README.md # quest/m0/README.md # quest/m0/local-gate.md # quest/m0/worktree.md # src/cli/main.ts
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b2ca9cccc7
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // claim is still here. A descheduled caller may also have created an old | ||
| // claim after cleanup; it must not admit work from that expired sample. | ||
| // A refused claim is removed, so it never holds back the next window. | ||
| if (existsSync(join(directory, String(window - 1))) || window !== Math.floor(now() / SETTLE_MS)) { |
There was a problem hiding this comment.
Block claims after backward clock steps
When Date.now() is corrected backward across a five-second boundary, for example by host or VM clock synchronization, a command admitted in window N leaves file N, but the next caller samples N-1 and this condition checks only N-2. That caller can therefore be admitted immediately beside the first, violating the promised minimum spacing; reject a claim when a newer adjacent claim exists or use a clock-rollback-safe timestamp scheme.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Leaving this as a follow-up. A backward clock step across a window boundary can cost at most one extra admission, and the load and memory checks still apply to it. The fix is cheap: refuse a claim while window N+1's claim also exists, or switch to a monotonic or last-admission timestamp. But a wall-clock correction during an admission is an edge case of a heuristic, so it doesn't need another review round on this PR.
(Written by Claude Opus 5.5)
Merge summaryMerging the shrunk design: Changes since the shrink (fea6a08, which Codex 👍'd)
Findings left as follow-ups (replied on each thread)
Validation
(Written by Claude Opus 5.5) |
quest gate <command> [args...]waits until the host has CPU and memory room before starting an expensive command. Thejust check,test,worker-check, andworker-testrecipes use it automatically; nested calls run immediately.min(4 GiB, RAM / 4)available memory. Linux usesMemAvailableand the remaining budgets of enclosing v1/v2 memory cgroups, counting their inactive file cache as reclaimable; macOS skips its cache-unaware memory signal, and Windows skips load average.$XDG_RUNTIME_DIR(or a per-hostname cache directory), and a claim is refused while the previous window's claim exists, so admissions are always at least 5 seconds apart. Expired samples are rejected before and after creation, so cleanup cannot admit a delayed waiter into an old window.The Linux systemd slot backend (scopes, slot handshake,
cgroup.killinode pinning, prerequisite probes, the opt-in systemd suite, and the CI linger step) was removed in this round (decision 21). It is recoverable from commit 5684307. Exact descendant ownership and orphan cleanup move to the worktree process-ownership quest,/quest/m0/worktree.md, which now brings its own backend.The agent load-average rule is removed from the spawn and complete skills.
/quest/m0/local-gate.mdremains open only for the representative contention measurement; no responsiveness improvement is claimed yet.Decisions
Both: slots cap concurrency, and admission also waits while the 1-minute load average exceeds the core count (user)superseded by decision 21The load gate runs everywhere, and Linux adds systemd slots; other platforms get load/memory admission only (user)superseded by decision 21--no-orphans~/.config/quest/gate.jsonfile, with defaults taken from the CPU countavailableParallelism(), which honors affinity (superseded by decision 17)How many Linux slots are there?Moot: superseded by decision 21, which removes slots.max(1, min(cores, RAM / 4 GiB))min(4 GiB, RAM/4), so a small host still runs one command (user suggestion). macOS skips this check because its free count excludes reclaimable cache.What happens on Linux without a systemd user manager?Moot: superseded by decision 21; every Linux host uses load and memory admission.check,test,worker-check, andworker-testrecipes, each wrapping a private_recipebun runscriptHow should unsupported Linux ownership prerequisites behave? (probe, then fall back)Moot: superseded by decision 21, which removes the probes.How should cancellation establish ownership across slot reuse? (pinnedMoot: superseded by decision 21; the gate cancels only its direct child.cgroup.killinode)MemAvailable, bounded by each ancestor memory cgroup's limit and current usage; derive effective RAM from the same limits (background recommendation). A real 256 MiB scope reproduced Bun reporting host-wideavailableMemory(), so that API cannot establish the container boundary here.max(availableParallelism(), cpus().length)./proc/stator PSI. Rejected: a different signal and more code for an edge case.inactive_file(v2) ortotal_inactive_file(v1) from usage, the working set that container runtimes report.MemAvailablealone. Rejected: it reopens the container-headroom finding.$XDG_RUNTIME_DIR/quest/gate, or~/.cache/quest/gate/<hostname>where the runtime directory is unset.How should a waiter detect that an occupied slot is free?Moot: superseded by decision 21, which removes slots.Recorded by the iterating sub-agent without prompting:
/quest/m0/worktree.md, which kixelated/quest#127 is rewriting in parallel?main's text; feat: add prunable worktree lifecycle primitives #127's own bullet planning to reuse this PR's backend will conflict and should take this decision.Validation
nix develop -c just checkpassed after merging currentmain.nix develop -c just testpassed: 327 CLI/core tests, shell suites, both built CLI lifecycle checks, and 295 Worker tests.gate, v1/v2 ancestor headroom with reclaimable cgroup cache, host-local claim directories, cancellation while waiting, nested calls, and exit-status propagation.Limits
Admission has no timeout or fairness guarantee, and may wait while host load remains high. There is no hard concurrency cap; the 5-second window spacing and the load/memory signals are what bound concurrent starts. The gate does not own or clean up its command's descendants. Representative contention measurement remains follow-up work.
(written by GPT-6; iterated by Claude Opus 5.5)
🤖 Generated with Claude Code
(Written by Claude Opus 5.5)