Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
6a04208
chore: claim local command gate quest
kixelated Oct 16, 2026
108fe1a
feat: preview Linux command admission with owned systemd scopes
kixelated Oct 16, 2026
603b3a0
fix(gate): retry admission when the occupied slot was already collected
kixelated Oct 16, 2026
f990f46
Merge remote-tracking branch 'origin/main' into iterate/local-gate
kixelated Oct 16, 2026
ce65b7c
Merge remote-tracking branch 'origin/main' into iterate/local-gate
kixelated Oct 16, 2026
de73191
feat(gate): admit by load average everywhere and gate just check/test
kixelated Oct 16, 2026
b337d15
feat(gate): derive admission limits from host CPU and memory
kixelated Oct 16, 2026
83b7d3d
Merge remote-tracking branch 'origin/main' into iterate/local-gate
kixelated Oct 16, 2026
5e9871f
Merge remote-tracking branch 'origin/main' into quest/m0/local-gate
kixelated Oct 16, 2026
0ade9d9
fix: close gate admission and ownership races
kixelated Oct 16, 2026
063918a
Merge remote-tracking branch 'origin/main' into iterate-local-gate
kixelated Oct 21, 2026
5684307
fix(gate): wait on live slots and keep admission host-local
kixelated Oct 21, 2026
6b39051
Merge remote-tracking branch 'origin/main' into quest/m0/local-gate
kixelated Oct 21, 2026
fea6a08
refactor(gate): admit on load and memory only
kixelated Oct 21, 2026
1ddfa3d
Merge remote-tracking branch 'origin/main' into quest/m0/local-gate
kixelated Oct 21, 2026
e98f6f3
fix(gate): keep global options after gate and walk only mounted cgroups
kixelated Oct 21, 2026
6ff9c2d
Merge remote-tracking branch 'origin/main' into quest/m0/local-gate
kixelated Oct 21, 2026
c6fafd2
fix(gate): detect cgroup paths outside the mount on every separator
kixelated Oct 21, 2026
900eb59
Merge remote-tracking branch 'origin/main' into quest/m0/local-gate
kixelated Oct 21, 2026
e144408
fix(gate): keep admissions a full window apart
kixelated Oct 21, 2026
5322a33
Merge remote-tracking branch 'origin/main' into quest/m0/local-gate
kixelated Oct 21, 2026
af7dfe0
fix(gate): forward the received signal to the command
kixelated Oct 21, 2026
9069da7
Merge remote-tracking branch 'origin/main' into quest/m0/local-gate
kixelated Oct 21, 2026
b2ca9cc
Merge remote-tracking branch 'origin/main' into quest/m0/local-gate
kixelated Oct 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,40 @@ same facts as one object with the fields `url`, `state`, `draft`, `head`,
as in `quest wait`. Any PR state exits 0, invalid arguments exit 2, and other
failures exit 1.

## Gate expensive commands

`quest gate <command> [args...]` holds an expensive command until the host has
room for it, across all of a user's repositories and worktrees:

```sh
quest gate bun run test
quest gate cargo build --release
```

Admission is sized from the host, with no configuration. Every platform waits
while the host-wide 1-minute load average exceeds the host's CPU count, or while
available memory is below one command's budget: 4 GiB, or a quarter of RAM on
smaller hosts, where RAM is capped by any container memory limit. On Linux,
available memory includes reclaimable host and cgroup file cache and is bounded
by the remaining budget of each enclosing memory cgroup. Load and memory count
work from every session and user. The load average lags new work, so the gate
admits at most one waiting command every 5 seconds per host rather than
releasing every waiter at once. Windows reports no load
average and macOS counts reclaimable cache as used memory, so those signals are
skipped there.

Admission is the gate's only job: it holds no slot or lock while the command
runs and does not track the command's descendants. A nested invocation runs
immediately inside the admitted command. Internal compiler or test worker
parallelism remains the command's responsibility.

This repository's `just check`, `just test`, `just worker-check`, and
`just worker-test` run through the gate.

Arguments, working directory, environment and terminal streams pass through to
the executable, and waiting happens before it starts. There is no fairness
guarantee or timeout: a command waits as long as the host stays busy.

## Prunable local checkouts

The experimental `quest worktree` commands share one disk budget across this
Expand Down
1 change: 0 additions & 1 deletion assets/skills/complete.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ Each sub-agent blocks on its own waits and reports back only when done or blocke
For GitHub review or merge activity, it uses `quest wait` and resumes with the
returned cursor as described by `quest skill merge`, rather than polling GitHub.
Limit the number of active agents to the CPU core count.
Other sessions share this machine: hold new agents while the load average exceeds the core count.

Tell each merge sub-agent that you watch its merge queue, so it reports once
auto-merge is enabled. Wait on each such PR with `quest wait`, as `quest skill merge`
Expand Down
1 change: 0 additions & 1 deletion assets/skills/spawn.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ Start each quest in a background sub-agent with its own isolated worktree, since

Each agent reports back only when done or blocked.
Limit the number of active agents to the physical CPU core count.
Other sessions share this machine: hold new agents while the load average exceeds the core count.

When done, each agent's claim PR holds its work, still a draft if decisions or unfinished work remain.
Its report lists every open decision and suggested follow-up with a recommendation.
Expand Down
15 changes: 15 additions & 0 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,14 @@ default:
install:
bun install --frozen-lockfile

# Expensive recipes wait for host room through `quest gate`; nested calls run at once.
gate := "bun run --silent quest gate just"

# Check code, repository tooling, and the quest tree.
check:
{{gate}} _check

_check:
bun run check
bun run --silent quest check
bun run --silent quest --root examples/export check
Expand All @@ -21,6 +27,9 @@ check:

# Run tests, including the shell hooks and the lifecycle of both builds.
test:
{{gate}} _test

_test:
bun run test
bash .claude/hooks/direnv.test.sh
bash scripts/install-check.test.sh
Expand Down Expand Up @@ -52,10 +61,16 @@ run *args:

# Check generated binding types, TypeScript, formatting, and deployment bundle.
worker-check:
{{gate}} _worker-check

_worker-check:
bun run --cwd cloudflare check

# Run Worker integration tests in the Cloudflare runtime.
worker-test:
{{gate}} _worker-test

_worker-test:
bun run --cwd cloudflare test

# Serve the Worker on localhost with seeded data and a fake GitHub, for checking UI.
Expand Down
2 changes: 1 addition & 1 deletion quest/m0/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ ownership-manifest design:

## Required

- [Expensive command gate](/quest/m0/local-gate.md) - shared CPU admission across worktrees and repositories
- [Gate contention measurements](/quest/m0/local-gate.md) - evidence that gated checks stay responsive under shared load
- [Worktree process ownership](/quest/m0/worktree.md) - supervise checkout commands and release checkouts automatically
- [Agent lifecycle integration](/quest/m0/worktree-agents.md) - reclaim completed and suspended checkouts through their harness
- [Agent workflow helpers](/quest/m0/helpers/README.md) - CLI commands for CI waits, PR status, claims, quest moves, and serialized merges
Expand Down
72 changes: 49 additions & 23 deletions quest/m0/local-gate.md
Original file line number Diff line number Diff line change
@@ -1,33 +1,59 @@
# [M] Bound expensive commands with quest gate
# [S] Measure quest gate under representative contention

## Goal

`quest gate <cmd>` admits a bounded number of expensive commands for one user
on one host, shared across repositories and worktrees. Concurrent agent sessions
remain useful without multiplying full-machine builds and test stacks.
`quest gate <cmd>` holds expensive commands for one user on one host until the
host has CPU and memory room, shared across repositories and worktrees.
Concurrent agent sessions remain useful without multiplying full-machine builds
and test stacks.

Load and memory admission and its automatic use by `just check` / `just test`
have landed. What remains is measuring representative build/test contention
with and without the gate, for example how many concurrent Worker suites hit
their 5-second timeouts, before claiming a responsiveness improvement.

## Plan

- Decided 2026-10-15: gate expensive command phases, not agent lifetimes. Use a
simple configurable concurrency limit, with a CPU-count default. Repository
entrypoints invoke the gate automatically; ordinary reasoning and I/O do not
occupy a CPU slot. No adaptive scheduler or new daemon is needed for this scope.
- Wait before launching the command, its stack, or its test timeout clocks.
Preserve arguments, terminal I/O, exit status, and cancellation. Nested gated
calls must not deadlock or silently multiply the admitted concurrency.
- A slot remains owned while expensive descendants run. Cover interruption,
crashes, concurrent acquisition, and recovery without relying on stale PID
guesses. Unsupported ownership semantics fail explicitly.
- Reuse existing compiler scheduling such as mbx; this is admission for whole
expensive phases, not a replacement compiler scheduler. Internal worker
concurrency remains a separate consumer responsibility.
- Exercise contention across processes and repositories, cancellation, nesting,
and exact exit propagation with small fixtures. Measure admission overhead
and representative contention before claiming a responsiveness improvement.
- Update CLI help and existing usage docs inline. Keep the shared primitive
outside repository-specific policy and independent of the hosted runner.
- Decided 2026-10-15: gate expensive command phases, not agent lifetimes.
Repository entrypoints invoke the gate automatically; ordinary reasoning and
I/O are not gated. No adaptive scheduler or new daemon is needed for this scope.
- Decided 2026-10-15 (user): gate checks and CI-like suites on host load, not
agents, because load from every session and user is what starves test timers.
The agent-level load-average rule was removed from the spawn and complete skills.
- Decided 2026-10-21 (user): admission is load and memory only, on every
platform. This supersedes the 2026-10-15 decisions to add Linux systemd scope
slots on top: every review round on the scope handshake, `cgroup.kill` inode
pinning, and manager probes found another race, for little admission benefit.
Exact descendant ownership and orphan cleanup move to the
[worktree process-ownership quest](/quest/m0/worktree.md). The removed
backend, its probes, and its opt-in systemd suite are recoverable from commit
5684307 in [kixelated/quest#128](https://github.com/kixelated/quest/pull/128).
- Decided 2026-10-15 (user): Bun's `--no-orphans` stays a research option. Its
best-effort registration and bounded PID-tree walks do not establish exact
ownership by themselves.
- Decided 2026-10-15: at most one admission per 5-second window across all
callers, claimed by exclusively creating a per-window file. A claim is
refused while the previous window's claim exists, so admissions are always a
full window apart (revised 2026-10-21 after a Codex finding). The load average
lags new work and is sampled every 5 seconds, so waiters that all see it fall
must not start together. Nothing is held across the command, so a crash
leaves no stale lock. Claims live in `$XDG_RUNTIME_DIR`, or a per-hostname
cache directory, because a home directory may be shared between hosts.
- Decided 2026-10-15 (user): limits come from host specs, with no configuration
file. The load limit is the host's CPU count, because the load average counts
every CPU, including ones outside this process's affinity (revised
2026-10-21 from the usable core count). Admission waits while available
memory is below `min(4 GiB, RAM / 4)`: 4 GiB is a typical build or test
stack, and a small host still runs one command. RAM is capped by a cgroup
memory limit, and cgroup headroom subtracts inactive file cache from usage,
since it is reclaimable. macOS counts reclaimable cache as used memory, so
only its load is gated; Windows reports no load average, so only its memory is.
- Nested gated calls run immediately, identified by a marker the gate sets in
the admitted command's environment. Fairness is not promised.
- Measure admission overhead alongside contention; the earlier 37 ms figure
included the removed systemd probe.

## Related

- [Worktree process ownership](/quest/m0/worktree.md) - owns checkout command descendants, with its own process-ownership backend
- [Own-PC runner](/quest/m0/kix/runner.md) - reuse local command supervision
- [Own-PC runner](/quest/m0/kix/runner.md) - can gate expensive job phases on host room
2 changes: 1 addition & 1 deletion quest/m0/worktree.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,5 @@ work, stealing a live lock, or deleting scratch a running process still uses.

## Related

- [Expensive command gate](/quest/m0/local-gate.md) - independent CPU and memory admission
- [Gate contention measurements](/quest/m0/local-gate.md) - independent CPU and memory admission
- [Agent lifecycle integration](/quest/m0/worktree-agents.md) - native agents and workflow adoption
Loading
Loading