From 6379bb05ef57acc616b2f3c35d30b82351304468 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sat, 14 Nov 2026 23:26:56 -0800 Subject: [PATCH 1/2] quest: plan resending sessions once their branch's PR opens Co-Authored-By: Claude Opus 5.5 --- quest/m0/benchmark/resend-sessions.md | 67 +++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 quest/m0/benchmark/resend-sessions.md diff --git a/quest/m0/benchmark/resend-sessions.md b/quest/m0/benchmark/resend-sessions.md new file mode 100644 index 00000000..4424c829 --- /dev/null +++ b/quest/m0/benchmark/resend-sessions.md @@ -0,0 +1,67 @@ +# [M] Resend a session once its branch's PR opens + +## Goal + +A session whose last stop came before its branch's PR opened reaches Kixel +Code's store with that PR in its record's `pulls`, so the PR's cost and +time count it. Today the sender uploads such a session once with +`pulls: []` and settles its queued stop, so it is never rebuilt. `quest +start` opens the PR at claim time, so this mostly hits ad hoc sessions +outside quests. + +Check: an ad hoc session on a fresh branch stops, then its PR opens; the +next session sent from the same checkout's repository resends the first, +and the store's copy names the PR, equal to what `quest usage --json` +prints for it. + +## Plan + +Found in [kixel-ai/kixel.quest#36](https://github.com/kixel-ai/kixel.quest/pull/36), +the record store; the maintainer decided there to fix it (✅ keep, +rejected: drop). The gap dates from +[moq-dev/quest#718](https://github.com/moq-dev/quest/pull/718), which +settles each queued stop after one upload: list it under `## Caused by`. + +How it works today, in `src/cli/send.ts`: each pass rebuilds only queued +sessions and their sub-agents, then settles their stops. It reads the PRs +updated since the group's cursor (`read/`), so a PR opened after an +upload is always read by the group's next pass, with no gap. The store +keeps each session's latest upload, so a resend replaces, never doubles. + +Decided 2026-11-14 (/quest-plan, unattended, so each is a recommendation +the PR's review can overturn): + +- Which sessions does the sender remember after uploading? + - ✅ Each emitted session (sub-agents included) whose latest branch + headed no PR in the read, since `attribute` prefers the latest branch: + this also catches a session that moved to a new branch whose PR opens + later + - Rejected: only records with `pulls: []`, which misses that case +- When is a remembered session resent? + - ✅ On a pass for its group (endpoint, origin, prefix) whose PR read + holds a PR it would be attributed to, by the same rule `attribute` + uses, shared rather than copied: the sender queues it again, so it + goes through the normal path and widens token-max's window only + once, when it is due. Waiting for the group's next queued session is + the "next `quest` run" the record store's PR recommended + - Rejected: polling each remembered session's group on a timer, which + spends GitHub quota every pass for a resend that can wait for the + repository's next session +- When is a remembered session forgotten? + - ✅ Once resent with a PR, or when token-max no longer lists it (its + transcript was pruned), silently, since nothing was lost that the + first upload did not already send. The harness's transcript retention + bounds what is kept, with no constant of our own + - Rejected: a fixed age; keeping it until its branch is deleted, which + costs a git or GitHub read per session +- Milestone? ✅ m0 under the benchmark, whose end-to-end check needs every + PR's cost to be whole (rejected: m1). + +Keep what is remembered in the sender's state directory beside `pending/`, +one small file per session, holding what matching needs: its branches with +their times and the repositories token-max resolved for it. + +## Related + +- [Record endpoint](/quest/m0/benchmark/records-endpoint.md) - turns the + sender on for each repository; this fixes what it sends From 67bee5bbd9459dfe026c51d4f81025e5ed4ccfba Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sat, 14 Nov 2026 23:29:27 -0800 Subject: [PATCH 2/2] quest: resend a session's whole tree, triggered by its latest branch Co-Authored-By: Claude Opus 5.5 --- quest/m0/benchmark/resend-sessions.md | 54 ++++++++++++++++----------- 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/quest/m0/benchmark/resend-sessions.md b/quest/m0/benchmark/resend-sessions.md index 4424c829..d90dd689 100644 --- a/quest/m0/benchmark/resend-sessions.md +++ b/quest/m0/benchmark/resend-sessions.md @@ -32,34 +32,46 @@ Decided 2026-11-14 (/quest-plan, unattended, so each is a recommendation the PR's review can overturn): - Which sessions does the sender remember after uploading? - - ✅ Each emitted session (sub-agents included) whose latest branch - headed no PR in the read, since `attribute` prefers the latest branch: - this also catches a session that moved to a new branch whose PR opens - later - - Rejected: only records with `pulls: []`, which misses that case -- When is a remembered session resent? + - ✅ Each queued session, as the root of the tree it emits, when any + session in that tree has a latest branch that headed no PR in the + read. `attribute` prefers the latest branch, so this catches a session + that moved to a new branch whose PR opens later. The whole tree is + remembered because `buildRecords` (`src/cli/records.ts`) passes PRs + from parent to sub-agent and back, so a parent on `main` takes its + PR from a sub-agent's fresh branch + - Rejected: only records with `pulls: []`, which misses the moved + session; only the session whose own branch lacks a PR, which leaves a + parent that inherits it at `pulls: []` +- When is a remembered tree resent? - ✅ On a pass for its group (endpoint, origin, prefix) whose PR read - holds a PR it would be attributed to, by the same rule `attribute` - uses, shared rather than copied: the sender queues it again, so it - goes through the normal path and widens token-max's window only - once, when it is due. Waiting for the group's next queued session is - the "next `quest` run" the record store's PR recommended - - Rejected: polling each remembered session's group on a timer, which + holds a PR that some member's unresolved latest branch heads, by the + rule `attribute` uses, shared rather than copied. A PR on an older + branch triggers nothing, since `attribute` falling back to it is + already what was sent. The sender queues the root again, so the tree + goes through the normal path, rebuilding every member's record, and + widens token-max's window only once, when it is due. Waiting for the + group's next queued session is the "next `quest` run" the record + store's PR recommended + - Rejected: polling each remembered tree's group on a timer, which spends GitHub quota every pass for a resend that can wait for the repository's next session -- When is a remembered session forgotten? - - ✅ Once resent with a PR, or when token-max no longer lists it (its - transcript was pruned), silently, since nothing was lost that the - first upload did not already send. The harness's transcript retention - bounds what is kept, with no constant of our own - - Rejected: a fixed age; keeping it until its branch is deleted, which - costs a git or GitHub read per session +- When is a remembered tree forgotten? + - ✅ Each upload of a root decides afresh by the rule above, so a resend + that still leaves a member's latest branch without a PR keeps the + tree; it is dropped when token-max no longer lists the root (its + transcript was pruned), silently, since the first upload already sent + everything but the PR. The harness's transcript retention bounds what + is kept, with no constant of our own + - Rejected: forgetting after one resend, which loses a branch whose PR + opens later still; a fixed age; keeping it until its branch is + deleted, which costs a git or GitHub read per session - Milestone? ✅ m0 under the benchmark, whose end-to-end check needs every PR's cost to be whole (rejected: m1). Keep what is remembered in the sender's state directory beside `pending/`, -one small file per session, holding what matching needs: its branches with -their times and the repositories token-max resolved for it. +one small file per root, holding its `Pending` and, per unresolved member, +what matching needs: its latest branch with its times and the +repositories token-max resolved for it. ## Related