From 1b771025dacc3dfa77bc8384df161ee82b7fa6c6 Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Thu, 22 Oct 2026 11:39:13 -0700 Subject: [PATCH 1/3] docs(quest): plan follow-ups from the 2026-10-22 merge wave Plan runner hardening (one runner per home, paged review threads) first in the Kix epic; squash-only merges and a stricter re-review rule first in helpers; Worker test isolation and an idle-host gate benchmark in m0; a shared seeded GitHub fake in Kix; quest status holds in helpers; and incremental history passes in m1. Widen the lifecycle quest to cover the macOS queue timeout, and add the branch-reset check to operator setup. Co-Authored-By: Claude Opus 5.5 --- quest/m0/README.md | 4 +++- quest/m0/gate-bench.md | 25 +++++++++++++++++++++++ quest/m0/helpers/README.md | 2 ++ quest/m0/helpers/merge-rules.md | 35 ++++++++++++++++++++++++++++++++ quest/m0/helpers/status-holds.md | 20 ++++++++++++++++++ quest/m0/kix/README.md | 2 ++ quest/m0/kix/github-fake.md | 19 +++++++++++++++++ quest/m0/kix/operator-setup.md | 5 +++++ quest/m0/kix/runner-hardening.md | 34 +++++++++++++++++++++++++++++++ quest/m0/lifecycle-ebusy.md | 21 ------------------- quest/m0/lifecycle-flakes.md | 30 +++++++++++++++++++++++++++ quest/m0/worker-tests.md | 21 +++++++++++++++++++ quest/m1/README.md | 1 + quest/m1/history-pass.md | 22 ++++++++++++++++++++ 14 files changed, 219 insertions(+), 22 deletions(-) create mode 100644 quest/m0/gate-bench.md create mode 100644 quest/m0/helpers/merge-rules.md create mode 100644 quest/m0/helpers/status-holds.md create mode 100644 quest/m0/kix/github-fake.md create mode 100644 quest/m0/kix/runner-hardening.md delete mode 100644 quest/m0/lifecycle-ebusy.md create mode 100644 quest/m0/lifecycle-flakes.md create mode 100644 quest/m0/worker-tests.md create mode 100644 quest/m1/history-pass.md diff --git a/quest/m0/README.md b/quest/m0/README.md index 834ed3a4..98a86a04 100644 --- a/quest/m0/README.md +++ b/quest/m0/README.md @@ -35,7 +35,9 @@ ownership-manifest design: ## Required -- [Lifecycle EBUSY](/quest/m0/lifecycle-ebusy.md) - the Windows lifecycle check stops failing at random while removing its temp directory +- [Lifecycle flakes](/quest/m0/lifecycle-flakes.md) - lifecycle CI jobs stop failing on Windows `EBUSY` and unstarted macOS jobs +- [Worker tests](/quest/m0/worker-tests.md) - every Worker test passes alone and in any order +- [Gate bench](/quest/m0/gate-bench.md) - measure headroom admission on an idle host - [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 diff --git a/quest/m0/gate-bench.md b/quest/m0/gate-bench.md new file mode 100644 index 00000000..bda576a2 --- /dev/null +++ b/quest/m0/gate-bench.md @@ -0,0 +1,25 @@ +# [XS] Measure gate headroom on an idle host + +## Goal + +The gate's idle-host behaviour is measured: `scripts/gate-bench.sh gated 8` +and `ungated 8` run on an otherwise idle host, and the results are recorded +in a PR. A gated burst that starts together, close to the ungated makespan, +confirms headroom admission; anything else becomes a follow-up. + +## Plan + +Decided 2026-10-22 (user) as a follow-up of +[kixelated/quest#189](https://github.com/kixelated/quest/pull/189), whose +benchmarks ran while other agents kept the host's load at 19-42 on 24 CPUs, +so they measured the busy-host path, not the idle burst the quest was for. +The earlier baseline, from +[kixelated/quest#177](https://github.com/kixelated/quest/pull/177), was 104 s +gated against 19 s ungated for a burst of 8. + +- Run on a host with no other agents or builds; record the 1-minute load + before each run alongside the results. +- Delete this quest in the PR that records the results. +- Dropped (user): asking Bun upstream to make `os.userInfo().homedir` ignore + `$HOME` (the child-process lookup costs about 10 ms), and handling a macOS + hostname that changes with the network, until it is seen in practice. diff --git a/quest/m0/helpers/README.md b/quest/m0/helpers/README.md index 32f6f85f..a71fe8a2 100644 --- a/quest/m0/helpers/README.md +++ b/quest/m0/helpers/README.md @@ -57,7 +57,9 @@ commands from Sep-Oct. Session counts in each child come from that window. ## Required +- [Merge rules](/quest/m0/helpers/merge-rules.md) - squash merges only, and no merge with changes no review covered - [Complete loop](/quest/m0/helpers/complete-loop.md) - `/quest-complete` re-scans PRs, keeps picks and holds, and counts rounds per PR +- [Status holds](/quest/m0/helpers/status-holds.md) - `quest status` shows a PR's `quest:*` hold - [Headless agents](/quest/m0/helpers/headless-agents.md) - `/quest-spawn api` launches, resumes, and stops billed agents through one command auto mode allows ## Related diff --git a/quest/m0/helpers/merge-rules.md b/quest/m0/helpers/merge-rules.md new file mode 100644 index 00000000..043ab634 --- /dev/null +++ b/quest/m0/helpers/merge-rules.md @@ -0,0 +1,35 @@ +# [XS] Land squash merges, and review the final head + +## Goal + +Pull requests in this repository land as squash merges, and no pull request +merges with changes no review covered, other than merges from its base or a +reviewer's own suggested patch applied unchanged. + +## Plan + +Decided 2026-10-22 (user) from a merge wave in which +[kixelated/quest#189](https://github.com/kixelated/quest/pull/189) landed as +a merge commit, and [#183](https://github.com/kixelated/quest/pull/183), +[#185](https://github.com/kixelated/quest/pull/185), +[#188](https://github.com/kixelated/quest/pull/188), +[#189](https://github.com/kixelated/quest/pull/189), and +[#190](https://github.com/kixelated/quest/pull/190) merged with fixes pushed +after their last review, up to about 150 lines and including #188's security +rework. The user asked for these root-instruction, guide, and setting changes +through this plan. + +- **Squash (user).** Add a line to this repository's root `AGENTS.md` saying + pull requests land as squash merges; the merge skill picks + `quest merge --land --squash` from it. Also set the repository to allow + only squash merges (`allow_merge_commit` and `allow_rebase_merge` false, + through `gh api`), approved by the user on 2026-10-22, so a wrong method + fails instead of landing. Rejected: either one alone. +- **Re-review (user).** Tighten the guide's Reviews section in + `assets/AGENTS.md`: "trivial changes since the last review" means only + merges from the base or a reviewer's suggested patch applied unchanged. + Any other change waits for a review of the head; if none arrives within + the merge skill's wait bound, the agent asks the user instead of merging. + Rejected: re-review on every head, and leaving "trivial" to the agent. +- Ranked first in the epic: prose and one setting, and it stops unreviewed + merges now. diff --git a/quest/m0/helpers/status-holds.md b/quest/m0/helpers/status-holds.md new file mode 100644 index 00000000..1d0cb7e5 --- /dev/null +++ b/quest/m0/helpers/status-holds.md @@ -0,0 +1,20 @@ +# [XS] Show PR holds in quest status + +## Goal + +`quest status ` shows whether a `quest:*` hold is on the PR: its label, +how long ago it was taken, and the hold's timeline event, so `/quest-complete` +can skip PRs another session holds without a separate label read. + +## Plan + +Decided 2026-10-22 (user) as a follow-up of +[kixelated/quest#185](https://github.com/kixelated/quest/pull/185), which +added the holds: + +- Read the hold the same way the state commands do, through the shared + GitHub client, and report a draft its starter still holds the same way. +- Update the `complete` skill to read holds from `quest status`, and the + README's status example. +- Ranked after the [complete loop](/quest/m0/helpers/complete-loop.md), which + also edits `complete`. diff --git a/quest/m0/kix/README.md b/quest/m0/kix/README.md index a441b76f..8da0e5e8 100644 --- a/quest/m0/kix/README.md +++ b/quest/m0/kix/README.md @@ -110,6 +110,7 @@ fixes what it finds: ## Required +- [Runner hardening](/quest/m0/kix/runner-hardening.md) - one runner per home, and `context` reads every review thread - [Deploy kix.quest](/quest/m0/kix/deploy.md) - the operator registers the App and deploys the Worker with GitHub sign-in at kix.quest - [Operator setup](/quest/m0/kix/operator-setup.md) - fork namespace, pilot installations, and a verified test PR - [Per-project forks](/quest/m0/kix/forks.md) - each project publishes through its own fork, recorded by repository ID @@ -119,5 +120,6 @@ fixes what it finds: - [Agent activity](/quest/m0/kix/activity.md) - agent cards and map highlights built from checkpoint summaries - [Web planning](/quest/m0/kix/planning.md) - issue-to-quest interviews produce draft planning PRs - [Web execution](/quest/m0/kix/execution.md) - spawn and iterate through the UI, ending in contribution PRs +- [Seeded GitHub fake](/quest/m0/kix/github-fake.md) - the local server runs publication end to end, and tests share its fake - [Onboarding guide](/quest/m0/kix/guide.md) - a complete maintainer path, from runner setup through a reviewed PR - [Merged claims](/quest/m0/kix/merged-claims.md) - the board ignores branches whose pull request merged, as the CLI does diff --git a/quest/m0/kix/github-fake.md b/quest/m0/kix/github-fake.md new file mode 100644 index 00000000..16428f73 --- /dev/null +++ b/quest/m0/kix/github-fake.md @@ -0,0 +1,19 @@ +# [S] Run publication end to end against the seeded fake GitHub + +## Goal + +The seeded local server's fake GitHub (`cloudflare/dev/github.ts`) models +commits, fast-forward ref updates, and pull request creation, so the local +server can run a job's publication end to end, and publication tests use the +same fake instead of their own. + +## Plan + +Decided 2026-10-22 (user) as a follow-up of +[kixelated/quest#190](https://github.com/kixelated/quest/pull/190): + +- `cloudflare/test/publish.test.ts` keeps its own stateful GitHub fake, while + `dev/github.ts` models only what the inbox flows need. Merge the two into + `dev/github.ts` and delete the test-local copy. +- The epic's end-to-end test starts from the seeded fake before the live + rehearsal, so this ranks before the onboarding guide. diff --git a/quest/m0/kix/operator-setup.md b/quest/m0/kix/operator-setup.md index 6b5f0388..1855459b 100644 --- a/quest/m0/kix/operator-setup.md +++ b/quest/m0/kix/operator-setup.md @@ -28,6 +28,11 @@ and a bounded test PR verify the integration before contributors are invited. background reconcile from [kixelated/quest#170](https://github.com/kixelated/quest/pull/170) only tests against the fake. +- Check what GitHub does to an open pull request when "Start over" resets + its service-fork head branch to the base, which + [kixelated/quest#190](https://github.com/kixelated/quest/pull/190) handles + either way (adopt it if open, open a new one if closed) but tests only + against the fake. Added 2026-10-22 (user). - Rehearse on the operator's own runner and an explicitly selected operator account. - Record nonsecret setup/run instructions and deployment identifiers. diff --git a/quest/m0/kix/runner-hardening.md b/quest/m0/kix/runner-hardening.md new file mode 100644 index 00000000..322dd0be --- /dev/null +++ b/quest/m0/kix/runner-hardening.md @@ -0,0 +1,34 @@ +# [S] Fix the runner's post-merge review findings + +## Goal + +Two `quest runner` processes never work in the same runner home at once, and +the `context` tool returns every unresolved review thread of a pull request, +however many threads it has. + +## Plan + +Decided 2026-10-22 (user) from Codex's post-merge review of +[kixelated/quest#188](https://github.com/kixelated/quest/pull/188) +([review](https://github.com/kixelated/quest/pull/188#pullrequestreview-5618747276)), +which covered rework that merged without a review: + +- **One runner per home (user).** `quest runner run` takes an exclusive lock + on its runner home and refuses to start while another process holds it. + The per-repository lock in `src/runner/workspace.ts` serializes only within + one process, so a second runner on the same home could reuse and modify a + job worktree while the first one's agents are still stopping after the hub + reassigns their jobs + ([P1](https://github.com/kixelated/quest/pull/188#discussion_r4351848499)). + Rejected: inter-process locks per repository and worktree, which allow + several runners per home at more complexity. The lock must release when its + holder dies, on Linux and macOS. +- **Page review threads.** The context query in + `cloudflare/src/github/service.ts` reads only the first 100 review threads + before filtering unresolved ones, so a newer unresolved thread on a busy PR + is silently omitted + ([P2](https://github.com/kixelated/quest/pull/188#discussion_r4351848505)). + Follow `pageInfo` cursors until every thread is examined. +- Ranked first in the epic, since both fix merged code. Test a second runner + refused on a held home, a lock released after a crash, and a pull request + with more than 100 review threads. diff --git a/quest/m0/lifecycle-ebusy.md b/quest/m0/lifecycle-ebusy.md deleted file mode 100644 index b905358e..00000000 --- a/quest/m0/lifecycle-ebusy.md +++ /dev/null @@ -1,21 +0,0 @@ -# [S] Stop the Windows lifecycle check failing with EBUSY - -## Goal - -The lifecycle check (`scripts/lifecycle-check.mjs`) never fails a Windows CI -job with `EBUSY` while removing its temporary directory, because nothing still -holds that directory when the check deletes it. - -## Plan - -Decided 2026-10-22 (user) as a follow-up of -[kixelated/quest#192](https://github.com/kixelated/quest/pull/192): - -- The Windows arm64 Release job failed after its own checks passed, in the - final `rmSync(work, { recursive: true, force: true })`, on #192 and on the - `quest/m0/worktree` branch (run 39679528766). A rerun passed both times. -- Find which process or handle still holds the directory (for example a - child process the check started, or a file it left open) and fix that - cause. No retries, sleeps, or ignored errors. -- Reproduce it on a native Windows runner before fixing, and keep the fix - covered by the existing lifecycle jobs. diff --git a/quest/m0/lifecycle-flakes.md b/quest/m0/lifecycle-flakes.md new file mode 100644 index 00000000..11ea183e --- /dev/null +++ b/quest/m0/lifecycle-flakes.md @@ -0,0 +1,30 @@ +# [S] Stop the lifecycle CI jobs failing at random + +## Goal + +The lifecycle CI jobs fail only when the code under test is broken: the +Windows check never fails with `EBUSY` while removing its temporary +directory, and a macOS job that never gets a runner does not show as a +failed check. + +## Plan + +Decided 2026-10-22 (user) as follow-ups of +[kixelated/quest#192](https://github.com/kixelated/quest/pull/192) and +[kixelated/quest#191](https://github.com/kixelated/quest/pull/191): + +- **Windows `EBUSY`.** The Windows arm64 Release job failed after its own + checks passed, in the final `rmSync(work, { recursive: true, force: true })` + of `scripts/lifecycle-check.mjs`, on #192 and on the `quest/m0/worktree` + branch (run 39679528766). A rerun passed both times. Find which process or + handle still holds the directory (for example a child process the check + started, or a file it left open) and fix that cause. No retries, sleeps, or + ignored errors. Reproduce it on a native Windows runner before fixing. +- **macOS queue timeout.** On #191 and + [kixelated/quest#185](https://github.com/kixelated/quest/pull/185), the + optional `lifecycle (macos-14)` job waited 15 minutes for a hosted runner + during a GitHub macOS capacity notice, was cancelled without running, and + showed as failed. Decide from the workflow (`.github/workflows/check.yml`) + whether to shorten its queue limit, report it as skipped, or drop it from + pull request runs, keeping macOS covered somewhere. +- Keep both fixes covered by the existing lifecycle jobs. diff --git a/quest/m0/worker-tests.md b/quest/m0/worker-tests.md new file mode 100644 index 00000000..d7ccf090 --- /dev/null +++ b/quest/m0/worker-tests.md @@ -0,0 +1,21 @@ +# [S] Make every Worker test pass on its own + +## Goal + +Each Worker test (`cloudflare/test/`) passes when run alone, in any order, or +in the full suite, because no test depends on D1 state another test left +behind. + +## Plan + +Decided 2026-10-22 (user) as a follow-up of +[kixelated/quest#188](https://github.com/kixelated/quest/pull/188): + +- A test in `cloudflare/test/publish.test.ts` passed in the full suite but + failed alone with a foreign-key error until it inserted its second user + itself, so D1 state leaks between tests in a full run. +- Find how state is shared (per-file or per-suite storage, seeded rows, + module-level fixtures) and make each test set up what it needs, rather than + fixing tests one at a time as they surface. +- Prove it by running each Worker test file alone and the suite in a shuffled + order, and keep a check in CI that would catch a new order dependence. diff --git a/quest/m1/README.md b/quest/m1/README.md index 7a03ef31..a9e7775d 100644 --- a/quest/m1/README.md +++ b/quest/m1/README.md @@ -16,3 +16,4 @@ additional login providers. This milestone does not gate the first release. - [ChatGPT hosted runs](/quest/m1/chatgpt-hosted.md) - conditional on provider approval and current terms - [ChatGPT approval](/quest/m1/chatgpt-waitlist.md) - resolve hosted-use approval before implementation - [Batched inbox reads](/quest/m1/inbox-reads.md) - a fixed number of D1 queries per project on each inbox load +- [Incremental history](/quest/m1/history-pass.md) - each history pass reads only what changed since the last diff --git a/quest/m1/history-pass.md b/quest/m1/history-pass.md new file mode 100644 index 00000000..396e3c2c --- /dev/null +++ b/quest/m1/history-pass.md @@ -0,0 +1,22 @@ +# [S] Read only what changed in each history pass + +## Goal + +A history pass over a project reads only the issues and pull requests that +changed since the last pass, and fetches pull request bodies only where the +board needs them, instead of paging every entity each time. + +## Plan + +Decided 2026-10-22 (user) as a follow-up of +[kixelated/quest#182](https://github.com/kixelated/quest/pull/182): + +- Each history page now fetches up to 50 pull request bodies, and a full pass + still pages every entity: about 126 pages on moq-dev/moq. +- Page by update time from the last pass's high-water mark, and read bodies + only for open drafts, whose `## Decisions needed` section decides whether + the board shows them as claimed or handed back. +- m1, next to [batched inbox reads](/quest/m1/inbox-reads.md): m0 pilots are + small enough that full passes are affordable. +- Dropped (user): caching installation tokens across an inbox load, until + pilots show slow inboxes. From 4c63c674c9e49b159db74495398593c3c83001ae Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Thu, 22 Oct 2026 11:47:25 -0700 Subject: [PATCH 2/3] docs(quest): prove each Worker test case alone, not just each file Addresses Codex's review: tests in one file share D1 state too. Co-Authored-By: Claude Opus 5.5 --- quest/m0/worker-tests.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/quest/m0/worker-tests.md b/quest/m0/worker-tests.md index d7ccf090..3932a516 100644 --- a/quest/m0/worker-tests.md +++ b/quest/m0/worker-tests.md @@ -17,5 +17,6 @@ Decided 2026-10-22 (user) as a follow-up of - Find how state is shared (per-file or per-suite storage, seeded rows, module-level fixtures) and make each test set up what it needs, rather than fixing tests one at a time as they surface. -- Prove it by running each Worker test file alone and the suite in a shuffled - order, and keep a check in CI that would catch a new order dependence. +- Prove it by running each Worker test case alone (not just each file, since + tests in one file share state too) and the suite in a shuffled order, and + keep a check in CI that would catch a new order dependence. From 4108a9e127ba297f0696915cb985d2e0fdba832d Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Thu, 22 Oct 2026 12:02:25 -0700 Subject: [PATCH 3/3] docs(quest): only conflict-free base merges skip re-review The user narrowed decision 6 from Codex's review: a base merge that needed a hand-resolved conflict is reviewed like any other change. Co-Authored-By: Claude Opus 5.5 --- quest/m0/helpers/merge-rules.md | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/quest/m0/helpers/merge-rules.md b/quest/m0/helpers/merge-rules.md index 043ab634..8c27d4ac 100644 --- a/quest/m0/helpers/merge-rules.md +++ b/quest/m0/helpers/merge-rules.md @@ -3,8 +3,8 @@ ## Goal Pull requests in this repository land as squash merges, and no pull request -merges with changes no review covered, other than merges from its base or a -reviewer's own suggested patch applied unchanged. +merges with changes no review covered, other than conflict-free merges from +its base or a reviewer's own suggested patch applied unchanged. ## Plan @@ -27,9 +27,13 @@ through this plan. fails instead of landing. Rejected: either one alone. - **Re-review (user).** Tighten the guide's Reviews section in `assets/AGENTS.md`: "trivial changes since the last review" means only - merges from the base or a reviewer's suggested patch applied unchanged. - Any other change waits for a review of the head; if none arrives within - the merge skill's wait bound, the agent asks the user instead of merging. - Rejected: re-review on every head, and leaving "trivial" to the agent. + conflict-free merges from the base or a reviewer's suggested patch applied + unchanged. A base merge that needed a hand-resolved conflict is reviewed + like any other change (narrowed by the user from Codex's review of + [#195](https://github.com/kixelated/quest/pull/195)). Any other change + waits for a review of the head; if none arrives within the merge skill's + wait bound, the agent asks the user instead of merging. Rejected: + re-review on every head, exempting every base merge, and leaving + "trivial" to the agent. - Ranked first in the epic: prose and one setting, and it stops unreviewed merges now.