Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
26 changes: 18 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -387,12 +387,20 @@ 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 per 5-second window per host, never in
consecutive windows, rather than releasing every waiter at once: a burst of
waiters starts about 10 seconds apart. Windows reports no load
average and macOS counts reclaimable cache as used memory, so those signals are
skipped there.
work from every session and user. Windows reports no load average and macOS
counts reclaimable cache as used memory, so those signals are skipped there.

Both measures lag new work, so each command admitted in the last minute counts
as one more unit of load and takes one budget from available memory. While the
host has room even counting those, commands start at once: a burst of gated
test runs on an idle host starts together. Once only the raw load and memory
have room, commands start one at a time, 5 seconds after the previous admission.
A user's admissions on one host are recorded under
`~/.cache/quest/admissions/<hostname>`, whatever the caller's environment or
login session. The home directory comes from the user database, not `$HOME`;
an account missing from it, as in arbitrary-UID containers, uses
`/tmp/quest-<uid>/admissions/<hostname>`, and refuses that directory unless only
its UID can write it.

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
Expand All @@ -403,8 +411,10 @@ 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.
the executable, and waiting happens before it starts. A terminal's Ctrl-C
reaches the command once, directly; SIGTERM and SIGHUP sent to Quest are
forwarded. There is no fairness guarantee or timeout: a command waits as long as
the host stays busy.

## Headless agents

Expand Down
1 change: 0 additions & 1 deletion quest/m0/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ ownership-manifest design:

## Required

- [Gate headroom](/quest/m0/gate-headroom.md) - gated commands start at once while the host has room
- [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
99 changes: 0 additions & 99 deletions quest/m0/gate-headroom.md

This file was deleted.

20 changes: 13 additions & 7 deletions scripts/gate-bench.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
# Measure `quest gate` under contention. Run inside `nix develop`.
#
# scripts/gate-bench.sh overhead [runs]
# Admission cost: `quest gate true` with a fresh claim directory, so no
# window is ever taken, against the same call with the gate bypassed.
# Admission cost: `quest gate true` with the gate bypassed, and admission
# alone against a fresh ledger, so no earlier run counts as recent.
# scripts/gate-bench.sh gated|ungated <suites> [cpus]
# Start <suites> Worker test suites at once, each in its own detached
# worktree of HEAD, through the gate or bypassing it. `cpus` is a taskset
Expand Down Expand Up @@ -45,17 +45,23 @@ stats() {
}

overhead() {
local runs=${1:-20} i start bypass=() gated=()
local runs=${1:-20} i start bypass=() admit=()
cd "$root"
for ((i = 0; i < runs; i++)); do
start=$(now_ms)
QUEST_GATE_ADMITTED=1 bun run --silent quest gate true
bypass+=($(($(now_ms) - start)))
start=$(now_ms)
XDG_RUNTIME_DIR="$work/claims.$i" bun run --silent quest gate true
gated+=($(($(now_ms) - start)))
done
echo "runs=$runs $(stats bypass_ms "${bypass[@]}")$(stats gated_ms "${gated[@]}")"
# Every caller shares one ledger, so time admission itself against a fresh one per run.
# shellcheck disable=SC2016 # Bun expands it.
mapfile -t admit < <(BENCH_GATE="$root/src/cli/gate.ts" BENCH_WORK="$work" BENCH_RUNS="$runs" bun --eval '
const { admit, fileHost, hostLimits } = await import(process.env.BENCH_GATE);
for (let i = 0; i < Number(process.env.BENCH_RUNS); i++) {
const start = performance.now();
await admit(hostLimits(), fileHost(`${process.env.BENCH_WORK}/ledger.${i}`), new AbortController().signal);
console.log(Math.round(performance.now() - start));
}')
echo "runs=$runs $(stats bypass_ms "${bypass[@]}")$(stats admit_ms "${admit[@]}")"
}

contend() {
Expand Down
Loading
Loading