Skip to content

feat(records)!: send each session's record to Kixel Code - #718

Merged
kixelated merged 17 commits into
mainfrom
quest/m0/benchmark/records
Nov 14, 2026
Merged

kixelated merged 17 commits into
mainfrom
quest/m0/benchmark/records

Conversation

@kixelated

@kixelated kixelated commented Nov 14, 2026 •

Copy link
Copy Markdown
Collaborator

Finishes quest/m0/benchmark/records.md: when an agent session's turn ends, quest queues its record, and one sender per user posts it to the Kixel Code endpoint the checkout names. Kixel Code's endpoint does not exist yet, so the last step, naming it in each repository, moves to a new quest that waits on kixel-ai/kixel.quest.

Summary

  • Record type (src/core/record.ts, exported from quest/core): SessionRecord holds harness, session, parent, repository, branch, quest, pulls, role, per-model tokens and price, start/end, and timed waits. parseRecord and parseUpload refuse a malformed record or upload whole, so Kixel Code stores exactly this shape and never keeps part of an upload.
  • One builder for usage and sending (src/cli/records.ts): quest usage and the sender build records the same way. A session on no PR branch takes its parent's PRs, unless its own repository is known to be another (the rule moq-dev/quest#721 added to quest usage, moved into the builder when merging main). A session on several PRs counts its equal share in feat(usage)!: cost and first rounds per writer and reviewer cell, for the routing charts #721's per-seat spend split and writer A/B rows too, and once per row. A coordinator gets every PR its sub-agents worked on, and its cost is split equally among them, so a PR's records add up to its whole cost. Role is classifying for the risk classifier a PR's ## Review record names (main's new role, merged in), reviewing for a reviewer it names, else the first quest skill the session ran gives it (planning, implementing, iterating, reviewing, merging, coordinating). Without a skill, a sub-agent takes its parent's role, and the older heuristics apply after that. Reviewer sessions are read from PR bodies' Session: lines alone (reviewSessions), so a ## Review record this version cannot parse, which any PR author can write, does not fail every pass.
  • Waits timed by the tool that waited (src/cli/waits.ts): an outermost quest gate logs gate-queue and gate-run. quest wait logs pull, with until set to the first activity, or merge-queue when it ended on merged or a dequeue. Both log only when CLAUDE_CODE_SESSION_ID or CODEX_THREAD_ID names the session, to $XDG_STATE_HOME/quest/waits, one file per day, kept for 90 days. person waits run from a blocking question to its answer, read from the transcript (src/cli/transcript.ts). Only times and the skill name leave the host.
  • Queueing (src/cli/send.ts): Claude Code's and Codex's Stop hooks (Codex's Stop is newly installed by quest setup) only queue the session, one immutable file per stop. The endpoint is named in the checkout's committed .quest.json. The token comes from the user's ~/.config/quest/records.json, keyed by endpoint. A checkout that names no endpoint, or an endpoint the user has no token for, queues nothing and says so once.
  • One sender per user (quest records sender, src/cli/service.ts): it runs until stopped, sending the queue every minute at low priority, so a busy host's stops cannot use up the shared GitHub quota. quest records install writes a systemd user service on Linux or a launchd agent on macOS that runs it with the installer's PATH and the directories it reads (XDG_CONFIG_HOME, XDG_STATE_HOME, XDG_DATA_HOME, CLAUDE_CONFIG_DIR, CODEX_HOME, GH_CONFIG_DIR, when set; never a secret), and (re)starts it. quest records uninstall removes it. The service manager runs exactly one sender, so passes never overlap and a session's uploads reach the store in order. The lock, its takeover, and the detached per-stop sender are gone. A 2xx removes the stops it read. A 401 or 403 stops sending to that endpoint until the token changes. Each upload is checked with parseUpload before it is posted. Any other failure keeps the session queued and is reported once at the next stop. The failing endpoint and repository then back off, one minute doubling to six hours, sitting out passes meanwhile so they never widen other groups' window. The backoff file is written whole and renamed into place, and an unreadable one counts as no backoff. A Stop with no transcript (codex exec --ephemeral) queues nothing.
  • Sub-agents routed by their own checkout: a queued session brings the sub-agents its stop hook never names, but only those whose checkout routes them to the same place: the checkout holding its folder, or the nearest parent still there, must be a queued one's, or name the same origin and endpoint; with no checkout left, every repository token-max resolved for it must be the group's. A queued session token-max no longer lists is dropped and said once, rather than holding back its group.
  • No sender running: the stop still queues, so nothing is dropped, and says once per outage that no sender runs and to run quest records install. The sender records its PID. The hook takes a dead PID, or none, as an outage, keyed by that PID, so each new outage is said again.
  • quest usage keeps a session with no ID (token-max's session is optional), told apart from another by its host and transcript files: its record has session: null, which parseUpload refuses, so none is ever sent. It also prints each PR's sessions from these records: a shared session shows its share, and a waits: line totals each kind of wait.
  • Tests no longer log their gates as the session's waits: vitest.config.ts blanks the session variables.

Decisions

Maintainer decision at the design checkpoint, moq-dev/quest#718 (comment):

  • ✅ (c) One long-running sender per user, as a systemd user service or a launchd agent, so only one process drains the queue (rejected: (a) tolerate vanished queue files and stamp records with their build time; (b) a kernel file lock). This removes the race between senders that take over a dead sender's lock. The Stop hooks now only queue.
  • ✅ Keep the committed .quest.json endpoint with a user-held token.
  • ✅ Keep the equal coordinator split.
  • ✅ Keep the coordinating role.
  • ✅ Keep the records-endpoint follow-up quest.
  • ✅ Plan the T3 Code delegated-thread parent link in kixel-ai/token-max's quest tree (see - Review rounds 1, 2, and 4 of the new session (Codex gpt-6.1-sol xhigh, with the high shadow) found 10 issues, all fixed with a test each: sub-agents in another repository or an opted-out checkout, a lost session blocking its group, the service dropping custom XDG directories, waits begun before the window, ID-less sessions, insteadOf` remotes, sub-agents in deleted folders or deleted nested clones, and ID-less sessions with identical spans. Round 5 reported none. Pullfrog's four threads on 317b308 (ephemeral Codex stop, endless retries, a stale README sentence, a leftover test field) were fixed in 3d177c7, with a reply in each thread.

Follow-ups`).

Maintainer decision at the round-3 design checkpoint, moq-dev/quest#718 (comment):

  • ✅ (a) Continue: resolve a deleted working folder's checkout from its nearest existing parent before falling back, and tell ID-less sessions apart by their transcript files (rejected: (b) route sub-agents from SubagentStop; (c) reject token-max sessions with no ID).

Maintainer decision at the round-6 design checkpoint, moq-dev/quest#718 (comment):

  • ✅ (a) Write the backoff file whole and rename it into place, as queue files are, and treat an unreadable backoff file as no backoff (rejected: (b) keep backoff only in the running sender's memory; (c) drop the backoff and discard a group after a permanent rejection).

Implementer's choices on (c), for review:

  • Install with an explicit quest records install, not from quest setup. Only a user with an endpoint and a token needs a sender, and the hook names the command the first time a record waits. quest setup runs per repository, but the service is per user. It is also rerun after upgrading quest, since restart picks up the new binary.
  • The service runs quest as PATH finds it, unresolved, with the installer's PATH, so it finds token-max, gh, and bun as the hooks do, and follows an upgrade behind a profile link.
  • No sender: fail loud once per outage, never drop. Records stay queued until a sender runs.
  • A pass each minute, whatever the queue holds. Polling an empty directory is cheaper than a file watcher, and the minute already bounds how often the GitHub quota is spent.
  • The store keeps the latest upload. One sender sends a session's copies in order, so the "keep the copy that ends latest" rule and its tie problem are gone. records-endpoint.md asks Kixel Code for that.
  • No guard against a second sender started by hand. The help says to start none. A PID check would misfire on a reused PID and bring back the takeover the decision removed.
  • Earlier choices kept: the token is tied to its endpoint in the user's own file, so a hostile branch's .quest.json cannot collect it. A record is rebuilt on every stop and replaces its earlier copy. The sender, not the hook, runs token-max and calls GitHub. Sub-agents are sent with their parent. Sessions read over --ssh have waits: null. Person waits are windowed like logged waits.

Public API and wire impact

  • New quest/core exports: SessionRecord, SessionWait, ModelSpend, RecordUpload, ROLES, Role, WAIT_KINDS, WaitKind, parseRecord, parseUpload.
  • New wire format: POST <endpoint> with {"records": SessionRecord[]} and Authorization: Bearer <token>. SessionRecord.session is string | null; an upload's records always name one.
  • New CLI commands: quest records sender, quest records install, quest records uninstall. quest setup now installs Codex's Stop hook (rerun quest setup).
  • quest usage --json: each PR's sessions entries are now records plus host. Roles add planning, merging, and coordinating to main's implementing, iterating, reviewing, and classifying. Nothing is in production, so there is no shim.

Validation

  • just check and just test pass at 4eddf30: 1308 tests, including test/records.test.ts, test/send.test.ts, and the new test/service.test.ts.
  • End to end under systemd, against a stand-in store. A local Bun server checked every upload with quest's own parseUpload, with scratch XDG_STATE_HOME and XDG_CONFIG_HOME, a token, and a .quest.json naming it. With the built dist/quest.js on PATH:
    1. Two Stop inputs for this session piped into quest hook. The first printed quest: no session record sender runs on this host, so this session's record waits in .../pending ...; start it with \quest records install``. The second printed nothing. Both stops stayed queued.
    2. The unit serviceDefinition("linux") generates passed systemd-analyze --user verify. Started as a real user unit (plus the scratch XDG variables), its first pass failed loud: token-max is not installed, and the queue was kept. With token-max built into scratch, the next pass failed on a real bug: moq-dev/quest#721's ## Review record uses a newer Rounds: format, and parsing every PR's whole record aborted the pass. The fix reads only Session: lines. After it, one pass drained both stops into one upload (iterating, the role from this session's /quest-iterate) and logged sent 1 records of moq-dev/quest.
    3. A stop while the unit ran printed nothing and was sent on the next pass a minute later. The store still held one record.
    4. After the unit stopped, the next stop printed the no-sender notice again, as a new outage.
  • Second, independent check of the sender's single-instance design: test/send.test.ts runs runSender with mocked time through a failed pass and two more. The failure was logged, the session was kept and sent on the next pass, the last pass sent nothing, and the PID was recorded. Earlier rounds checked each record's cost against token-max's own costUsd and against quest usage --pr 718 --json (field for field). That code is unchanged here.
  • The unit peaked at 3.3 GB of memory over its first run, mostly token-max reading this host's transcripts. That is the existing per-pass cost, now paid once a minute only while stops are queued.
  • launchd was not exercised: no macOS host was reachable. test/service.test.ts checks the plist and the launchctl bootout/bootstrap calls.

Follow-ups

  • Name Kixel Code's record endpoint in each repository (new, XS). It waits on kixel-ai/kixel.quest's record store serving the endpoint and saying how a person gets a token. Kept, per the decision above.
  • kixel-ai/dotfiles (that tree's /quest-plan): run quest records install on the maintainer's hosts, with the token, once the endpoint exists. Another repository's follow-up.
  • kixel-ai/token-max (that tree's /quest-plan): T3 Code's delegate_task children are separate threads with no parent in token-max sessions. Until they have one, a coordinator that delegates through T3 gets no PRs, and its cost stays unattributed. Native sub-agents are linked already. Another repository's follow-up, per the decision above; not created here.
  • kixel-ai/kixel.quest's quest/m0/records.md waits on "this quest is gone from its tree and the pinned quest exports the type". Both hold once this merges and a pin bump follows.

Models

Size: L
Implementer: Claude Code, claude-opus-5-5, high effort
Implementer: Claude Code, claude-opus-5-5, medium effort (iterate, M)

(Written by Claude Opus 5.5 with medium)

Review

Rewritten by quest review after each round.

  • Head: 4eddf30

  • Base: origin/main at 85d0a78

  • Risk: high, The change adds credentialed session-record uploads and a public record API while changing usage attribution and output, risking data disclosure or broken consumers for contributors and Kixel Code; reverting cannot recall uploaded data or remove installed sender services.

  • Classified: high by Codex, gpt-6.1-sol, low effort, session 01a1d7d1-a627-7e22-b285-39158a63b091

  • Reviewer: Codex, gpt-6.1-sol, xhigh effort

  • Session: 01a1d79b-2f87-70f1-b3f9-fd4e9ab5776b, round 9

  • Score: 10/10, The incremental change correctly deduplicates shared sessions within each writer-arm row and sums their proportional costs without introducing new findings.

  • Rounds: 12; findings 13 (medium 12, low 1): fixed 13, refuted 0, open 0

  • Findings: none

  • Reviewer: Codex, gpt-6.1-sol, high effort, shadow

  • Session: 01a1d79b-2f87-7380-a24b-9930f9091e69

  • Rounds: 6; findings 10 (medium 9, low 1): fixed 10, refuted 0, open 0

kixelated and others added 4 commits November 13, 2026 16:06
…e endpoint

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s dropped

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated kixelated changed the title Send each session's record to Kixel Code feat(records)!: send each session's record to Kixel Code Nov 14, 2026
kixelated and others added 3 commits November 13, 2026 16:46
…efusals per group, window person waits

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ss as the store keeps the latest copy

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated

Copy link
Copy Markdown
Collaborator Author

Maintainer decision (2026-11-14, design checkpoint at bc6c31d): ✅ (c) one long-running sender per user, as a systemd user service or launchd agent, so only one process ever drains the queue (rejected: (a) tolerate vanished queue files and stamp records with their build time; (b) a kernel file lock). Keep the committed .quest.json endpoint with the user-held token, the equal coordinator split, and the coordinating role. Keep the records-endpoint follow-up quest; plan the T3 Code parent link in kixel-ai/token-max's tree.

(Written by Claude Opus 5.5)

…hd agent; the Stop hook only queues

The maintainer picked one long-running sender per user so only one process
drains the queue, removing the lock takeover race. quest records install
writes and (re)starts the service; a stop with no sender running queues and
says so once per outage. The sender reads only reviewer sessions from PR
bodies, so a review record this version cannot parse no longer fails every
pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated

Copy link
Copy Markdown
Collaborator Author

Design change: per the maintainer decision above, records are now sent by one long-running sender per user (quest records sender, installed as a systemd user service or launchd agent by quest records install). The Stop hooks only queue. The send lock, its takeover, the again/last files, and the detached per-stop sender are removed, so the two open round-3 findings (queue files vanishing under overlapping senders, ordering equal-end rebuilds) no longer apply. A stop with no sender running still queues and says so once per outage. The next review starts with quest review --new.

(Written by Claude Opus 5.5 with medium)

kixelated and others added 2 commits November 13, 2026 17:20
… lost, keep service directories, keep ID-less sessions and pre-window waits

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…waits by any named session, count ID-less sessions apart

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…vice

# Conflicts:
#	README.md
#	src/cli/main.ts
#	src/cli/usage.ts
#	test/usage.test.ts
@kixelated

Copy link
Copy Markdown
Collaborator Author

Maintainer decision (2026-11-14, design checkpoint after round 3 at 7e26324): ✅ (a) continue: resolve a deleted working folder's checkout from its nearest existing parent before falling back, and tell ID-less sessions apart by their transcript files (rejected: (b) route sub-agents from SubagentStop; (c) reject token-max sessions with no ID).

(Written by Claude Opus 5.5)

kixelated and others added 2 commits November 13, 2026 17:41
…ckout; tell ID-less sessions apart by transcript

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… group's is never sent with it

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated
kixelated marked this pull request as ready for review November 14, 2026 01:46
@coderabbitai

coderabbitai Bot commented Nov 14, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 6 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: c8e44856-5552-4d5b-a077-46331d231045
Commits

Reviewing files that changed from the base of the PR and between 85d0a78 and 4eddf30.

Files selected for processing: 25

  • README.md
  • quest/m0/benchmark/breakage.md
  • quest/m0/benchmark/records-endpoint.md
  • quest/m0/benchmark/records.md
  • quest/m0/benchmark/time-split.md
  • src/cli/claims.ts
  • src/cli/gate.ts
  • src/cli/hooks.ts
  • src/cli/main.ts
  • src/cli/messages.ts
  • src/cli/records.ts
  • src/cli/review-record.ts
  • src/cli/send.ts
  • src/cli/service.ts
  • src/cli/transcript.ts
  • src/cli/usage.ts
  • src/cli/waits.ts
  • src/core/index.ts
  • src/core/record.ts
  • test/hooks.test.ts
  • test/records.test.ts
  • test/send.test.ts
  • test/service.test.ts
  • test/usage.test.ts
  • vitest.config.ts
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

Three issues need a look before merge, all inline. A Codex Stop with a null transcript_path fails the hook in every directory. A permanent non-auth rejection makes the sender retry every minute forever. The new README section still describes the out-of-order store rule that the single-sender decision removed.

Reviewed changes

I reviewed the whole diff at 317b308: the record type, the record builder shared by quest usage and the sender, wait logging, the Stop-hook queue, and the per-user sender service.

  • SessionRecord wire type: src/core/record.ts exports a strict parseRecord/parseUpload from quest/core.
  • Shared record builder: buildRecords assigns PRs (own, else parent's, else the sub-agents' PRs, split equally), role (classifier, reviewer, first quest skill, parent's, then heuristics), and waits.
  • Wait logging: an outermost quest gate and quest wait log waits under $XDG_STATE_HOME/quest/waits when an agent session is named in the environment. Person waits come from transcripts.
  • Queue and sender: Claude Code's and Codex's Stop hooks only queue the session. One quest records sender per user posts each endpoint and repository group every minute. A 401 or 403 drops the group. Any other failure keeps it queued.
  • Service install: quest records install|uninstall writes a systemd user unit or a launchd agent.
  • quest usage: rows are now records. A coordinator's cost is split across its PRs, and each PR gets a waits: line.

I ran test/records.test.ts, test/send.test.ts, and test/service.test.ts locally: 40 tests, all passing. I reproduced the Codex null-transcript_path throw directly with bun -e against hook(). test/send.test.ts covers only a 500 as a retried failure, so no test covers a non-auth 4xx.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using claude-opus-5-5 | 𝕏

Comment thread src/cli/hooks.ts Resolved
Comment thread src/cli/send.ts Resolved
Comment thread README.md Resolved
Comment thread test/hooks.test.ts Resolved
… skip transcript-less stops

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated
kixelated marked this pull request as draft November 14, 2026 01:58

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ No critical issues. The fixes for all four earlier threads hold up. One minor inline about the new backoff file, which matches the open finding already listed in the PR's ## Waiting section.

Reviewed changes

I reviewed the changes from 317b308 to 3d177c7, which respond to the previous Pullfrog review.

  • Per-group backoff: a failing endpoint/origin/prefix group now waits one minute, doubling up to six hours, and sits out passes until then. since is computed only over active groups, so a stuck group no longer widens the window for the others.
  • Local parseUpload before posting: an upload this version builds wrong now fails on the host and is never sent.
  • Ephemeral Codex stops: a Stop with transcript_path: null now returns "" before validation.
  • Docs and help: the README and quest records sender help now describe the latest-upload rule and the backoff.
  • Tests: added tests for doubling backoff, window isolation, local parseUpload refusal, and the null-transcript stop, and removed the stale send dep. test/send.test.ts and test/hooks.test.ts pass locally (39 tests).

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using claude-opus-5-5 | 𝕏

Comment thread src/cli/send.ts Resolved
@kixelated

Copy link
Copy Markdown
Collaborator Author

Maintainer decision (2026-11-14, design checkpoint after round 6 at 3d177c7): ✅ (a) write the backoff file whole and rename it into place, as queue files are, and treat an unreadable backoff file as no backoff (rejected: (b) keep backoff only in the running sender's memory; (c) drop the backoff and discard a group after a permanent rejection).

(Written by Claude Opus 5.5)

…e one is no backoff

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated
kixelated marked this pull request as ready for review November 14, 2026 02:04
Resolves #721's usage changes: its parent-PR rule for sub-agents
(not when known to be in another repository) moves into buildRecords, and a
session on several PRs counts its share in #721's seat split too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

The backoff fix in f48be9c holds up, and the rest of the sender reads correctly. Two things are left before merge. The writer A/B rollup double-counts a coordinator's spend, which is inline. The branch also conflicts with main in src/cli/usage.ts, where #721 added spend paths that need the same coordinator split.

Reviewed changes

I re-read the whole source diff at f48be9c. That covers the new commit, which writes backoff files whole and reads an unreadable one as no backoff.

  • Atomic backoff: backOff writes <digest>.new and renames it into place. readBackoff returns null for a missing, truncated, or ill-typed file, so a crash mid-write can no longer abort every group's pass.
  • Record type and builder: SessionRecord with strict parseRecord/parseUpload in quest/core. buildRecords assigns PRs (own, parent's, then sub-agents') and a role.
  • Queue and sender: the Stop hooks only queue. One quest records sender per user, run by its service, groups uploads by endpoint and repository, routes sub-agents by their own checkout, and backs off failing groups.
  • quest usage: rows are now records. A session on several PRs appears on each, and spendRows and the waits: totals divide it by its PR count.

I ran test/send.test.ts, test/records.test.ts, test/service.test.ts, and test/hooks.test.ts locally. All 67 tests pass.

⚠️ The branch conflicts with main's per-cell spend in src/cli/usage.ts

GitHub reports the PR as CONFLICTING. #721 (85d0a78) changed src/cli/usage.ts, src/cli/review-record.ts, src/cli/main.ts, README.md, and test/usage.test.ts. In usage.ts it adds its own module-level share and new spend totals per writer and reviewer cell. A textual merge leaves two share declarations. If the new cells add session.models the way armRows does, they will count a coordinator's full cost on every PR it spans.

Technical details
# Merge #721 into the records branch without losing the coordinator split

## Affected sites
- src/cli/usage.ts — this PR's `const share = (spent: Tokens, parts: number)` collides with #721's `share(reviewing, each.review)` helper
- src/cli/usage.ts — #721's `reviewerRows` (`add(to, spent)` over `session.models`) and the writer cells' `per(s.spent, ...)` spend
- src/cli/review-record.ts, src/cli/main.ts (usage `--help`), README.md, test/usage.test.ts — also touched by both sides

## Required outcome
- One `share` name per meaning, with tsc clean
- Every per-PR spend total introduced by #721 divides a session's spend by `session.pulls.length`, as `spendRows` does, so a PR's cells still add up to its whole cost
- `--help` and the README `quest usage` paragraph describe both #721's new rows and this PR's coordinator split

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using claude-opus-5-5 | 𝕏

Comment thread src/cli/usage.ts Resolved
…l share

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ No new issues in the merge. The main conflict is resolved correctly. The earlier inline thread on armRows is still open: armRows still adds each session's full session.models on every PR row.

Reviewed changes

I reviewed 332d305, which merges origin/main (#721) into the branch.

  • Resolved the share name collision: #721's exported seat splitter share(spent, seats) keeps its name. This PR's per-PR split is now portion/shareOf, built on #721's part.
  • Applied the coordinator split to #721's new spend cells: accuracyRows (writer cells) and reviewerRows (reviewer cells) now take shareOf(session), as spendRows takes portion. A session on several PRs now adds up to its whole cost across those PRs.
  • Moved #721's parent-inheritance rule into buildRecords: attribute now returns { own, known }. pullsOf drops a parent's PRs whose repository the sub-agent is known not to be in. A new test in test/records.test.ts and #721's test in test/usage.test.ts cover it.
  • Folded #721's place() role logic into the record builder: roles come from buildRecords alone, and SessionRow takes parent from SessionRecord.
  • Merged the docs: the README and quest usage --help now cover both the parent rule and the coordinator split.

I ran tsc --noEmit (clean) and test/usage.test.ts, test/records.test.ts, and test/send.test.ts locally. All 56 tests pass.

Pullfrog  | Fix it ➔ | View workflow run | Using claude-opus-5-5 | 𝕏

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found. The armRows double count from the earlier thread is fixed.

Reviewed changes

I reviewed 4eddf30, which changes armRows so a session on several PRs is counted once per arm row.

  • Coordinator split in the writer A/B rollup: armRows now keeps a seen set for each row, keyed by the new shared identity() helper. It also adds shareOf(session) instead of the session's full session.models, so an arm's spend adds up to each session's whole cost exactly once.
  • Shared session identity: spendRows now uses the same identity() helper in place of its inline JSON.stringify key.
  • Test: the writer A/B test now includes a coordinator whose two sub-agents worked on both arm-B PRs. That arm counts 4 sessions and ~$9.00, which is $3 + $4 + $1 + $1, each counted once.

I ran test/usage.test.ts locally, and all 15 tests pass.

Pullfrog  | View workflow run | Using claude-opus-5-5 | 𝕏

@kixelated
kixelated added this pull request to the merge queue Nov 14, 2026
Merged via the queue into main with commit ca637fe Nov 14, 2026
15 checks passed
@kixelated
kixelated deleted the quest/m0/benchmark/records branch November 14, 2026 02:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant