Skip to content

perf: 暇なときの報告の束ね待ちを 30 秒に伸ばす(質問・許可確認・人間は待たせない) - #5404

Merged
takecchi merged 3 commits into
mainfrom
perf/report-settle-window
Oct 28, 2026
Merged

takecchi merged 3 commits into
mainfrom
perf/report-settle-window

Conversation

@takecchi

@takecchi takecchi commented Oct 28, 2026 •

Copy link
Copy Markdown
Owner

何をするか

暇なときに最初に届いた報告(manager_message の kind==='report' かつ synthesized でないもの)だけ、固定窓 W 秒(先頭の報告が着いてから W 秒で閉じる。後続の報告でも延ばさない)待ってから束ねる。既定 W=30 秒。

  • 設定: 環境変数 ALTEROID_REPORT_SETTLE_MS(0〜120000ms、0 で無効=今の挙動)、clone のコンストラクタ引数 reportSettleMs。空・読めない・範囲外は既定で走り noteUnreadableRecord で跡を残す(resolveInboxSettleMs と同じ書き方)。
  • 外部の合図(external)の待ちは変えない(既定 2.5 秒・ALTEROID_INBOX_SETTLE_MS・上限 10 秒)。
  • 質問・許可確認・人間の発言は待たせない(今の isSettleCandidate のまま)。担当の失敗・停止の知らせ(世代の 409・停止・quotaStop)は manager.ts の #emit が kind: 'report'・synthesized: true で出すので、30 秒の窓の対象から外し(isPlainReport)、従来どおり待ち候補のまま短い窓(外部と同じ 2.5 秒・人間起点だけで切る)で扱う。報告の窓の途中に届けば窓を切る。
  • 背景は Issue 報告ターンの束ね窓を伸ばす案(設計。報告ターンが日の 76%・batchSize=1 が 81.5%) #5400(閉じない。参照のみ)。報告の束は TOKEN-1(mgr-ae470c81)の領域と重なる。

測りの数字(何を測ったか)

2026-10-27(JST の暦日 = 2026-10-26T15:00Z〜2026-10-27T15:00Z)の本番の日誌(クローン teto が GET で書き出し、turn-census.mjs の節 (e) で再現計算)。

  • 報告のターン 767 本、到着 1,397 件、cache_read 平均 92.8 万/ターン。
  • 固定窓 B での減り方(実測の報告ターン数に対して): W=15 で -15.0%、W=30 で -28.0%、W=60 で -44.1%。
  • 節約の上限の目安(1 日): W=30 の固定窓で約 2.0 億 tokens(= 減ったターン数 × cache_read 平均)。
  • 到着の列だけからの理想化で、実際の減り方の上限の目安。日誌ベースの cache_read で、台帳とは別会計(台帳との食い違いは reset の二重計上が原因で、別 PR fix: 累積に載らなかったモデルを数え直しと誤認して台帳へ二重に積む #5402 で修正中)。

代償

報告への反応が最大 30 秒遅れる(固定窓なので 1 件あたりの追加待ちは 30 秒で頭打ち)。質問・許可確認・人間は待たせない。W は ALTEROID_REPORT_SETTLE_MS で変えられる(0 で無効)。

読んで確かめたこと(実装側)

  • 待ちは Inbox#settle の固定タイマー(先頭で 1 回だけ張る)。後続で延びない。
  • 束ね(#mergedManagerReportBatch・混合の束)は待ちの後で束が決まるので壊れない。turn_timing の queue 段は arrivedAt が合図の at のままなので、待ちは従来どおり queue に乗る(30 秒ぶん伸びる)。
  • close()(stop() の中)は待ちを即座に畳む。ただし stop() は close() より前に shutdown の蒸留を #postAndWait で積み、その完了を待つ。従来の打ち切り条件は人間起点だけなので、窓の途中に stop すると蒸留が窓の満了まで待たされ、stop が窓の長さぶん止まる(2.5 秒では目立たず、30 秒では実害)。→ 報告の窓の打ち切りを「合成していない報告(manager_message・kind=report・synthesized でない)以外が届いたら」に広げた(人間・質問・許可確認・合成の失敗の知らせ・外部の合図(runner-registry の知らせなど)・tick(自発・定期)・蒸留で切る)。初版は「待ち候補(報告・外部)以外」で切っていたため外部の合図が窓を切らず、さらに担当の失敗・停止の知らせが synthesized: true の kind: 'report' で出るために「報告」側に入り、窓の対象にも切らない側にも入って最大 30 秒待たされていた(レビュー指摘。初版の PR 本文は「失敗の知らせは待たせない」と逆のことを書いていた)。外部の窓自体(2.5 秒)は従来どおり人間起点だけで切る。試験で、この修正を外すと stop が 30 秒止まることを確認している(下の変異)。
  • 待ちの間 #inFlight は立てたまま(従来どおり)。人間の interruptTurn の分類(starting 扱い)は変わらない。tick・蒸留・質問は上の打ち切りで窓を切るので 30 秒ぶん塞がない。

試験

新規 clone-report-settle.test.ts(偽の時計): (1) 30 秒の窓に連続した報告は 1 ターン (2) 窓の外の報告は別ターン・窓は延びない (3) 窓の途中の人間の発言で打ち切り (4) 質問・許可確認は待たない(窓の途中の質問でも打ち切り) (5) reportSettleMs: 0 で即ターン (6) 外部の窓は 2.5 秒のまま (7) 窓の途中の停止・セッションがある状態の停止で 30 秒待たず抜ける、タイマーが残らない。環境変数の解決(既定・空・数値・0・上限・小数・範囲外・読めない値)。

既存の試験の直し方(期待は弱めていない):

  • clone-test-harness.ts の setup は inboxSettleMs: 0 と同じく reportSettleMs: 0 を既定にした(cloneOptions で上書き可)。
  • inboxSettleMs: 0 を直接渡していた試験 11 本は reportSettleMs: 0 を並べた。
  • clone-inbox-settle.test.ts は 2.5 秒の窓そのものを測る試験なので、ON を { inboxSettleMs: 2500, reportSettleMs: 2500 }、OFF を両方 0 にして固定(期待・コメントは変えていない)。
  • createClone を直接呼び設定を渡さず、これまで実時間で 2.5 秒待って通っていた試験 11 本は reportSettleMs: 2500 を渡して従来と同じ待ちに固定(30 秒になると落ちるため)。

変異の確認(足した試験が落ちること)

  1. 窓の選択(種別で ms を選ぶ所): 報告に外部の窓 2.5 秒を使わせる → clone-report-settle.test.ts 5 本が落ちた(窓の連続・窓は延びない・人間で打ち切り・質問で打ち切り・0 で即ターン)。戻して緑。
  2. 打ち切り: 報告の interruptWhen を () => false(打ち切らない)にする → 3 本が落ちた(人間で打ち切り・質問で打ち切り・セッションがある状態の停止が窓の間止まる)。戻して緑。
  3. 打ち切りの述語(1 回目のレビュー指摘): 当時の述語 !isSettleCandidate(queued)(外部の合図が窓を切らない)に戻す → 「窓の途中で外部の合図が届くと打ち切る」が落ちた。
  4. 合成の報告(2 回目のレビュー指摘): isPlainReport から synthesized !== true を外す(直す前と同じ扱い)→ 本物の形(manager_message・kind: 'report'・synthesized: true)を使う 2 本が落ちた(窓の途中に届いても窓が切れない/先に届いても 2.5 秒の窓で済まず待たされる)。戻して緑。
  5. 待ちに入る前の割り込み: Inbox#settle 冒頭の待ち行列の確認を外す → 「待ちに入る前に積まれていた人間の発言・蒸留・合成の失敗の知らせでは待たずに戻る」が落ちた。戻して緑。
    (初版の external の 'ci-failure' という試験は実在しない形だったので外し、上の本物の形の試験に置き換えた。)

検証

vitest run packages/core apps/daemon apps/cli scripts(と web の restart-required 試験) --maxWorkers=2: 19,315 件 失敗 0(最終コミット後。web の restart-required 試験は前回の実行で通過)。.env.example 149 行の「播種される7つ」も 9 つに直した。tsc --noEmit -p packages/core・eslint(変更した主要ファイル)・prettier 通過。

確かめていないこと

本番での実際の束なり方・減り方は未確認(上の数字は到着の列からの理想化)。実時間の stop の遅れは偽の時計の試験でのみ確認。自発・定期 tick 単体の打ち切りは、蒸留・質問と同じ「報告以外」の判定で切れる(同じ述語)が、tick を投げる専用の試験は足していない。ALTEROID_REPORT_SETTLE_MS は前例 #5369(ALTEROID_INBOX_SETTLE_MS)と同じく compose.yaml の素通り・APP_ENV_VAR_DEFAULTS(既定 30000。DAEMON_RESTART_REQUIRED_ENV_NAMES はここから導出されるので、credential set で restartRequired が返る)・.env.example(播種される 9 つ)に登録した。ALTEROID_INBOX_SETTLE_MS が現れる箇所は git grep で洗い、上の 3 箇所以外に件数・一覧・試験の期待は無かった(env-vars-boot.test.ts は APP_ENV_VAR_DEFAULTS.length から導出)。

待ちに入る前に届いた割り込み(Inbox#settle の修正)

Inbox#settle は待ちを張った後に push された合図しか interruptWhen にかけなかった。#pump は取り出してから待ちに入るまでに await(#closeRecoveredQuotaStopRows・#quotaFoldNoticeFor)を挟むので、その間に積まれた人間の発言・蒸留は窓の長さぶん待たされた。settle の冒頭で待ち行列に interruptWhen に当たるものが既にあれば待たずに戻す。外部の窓(2.5 秒・人間起点だけで切る)にも同じ判定が効く: 待ち行列に既に人間起点の合図があれば、従来は 2.5 秒待っていたのが即抜ける(人間を待たせない向きの変化のみ。人間起点以外は従来どおり窓を切らない)。

既知の挙動

窓の最中に人間が interruptTurn() で止めようとすると、止めるべき走行中のターンがまだ無いので 'idle' が返り、窓が明けると報告のターンが始まる(従来の 2.5 秒の窓でも同じ形で、30 秒に伸びたぶん目立つ)。人間の発言が届けば窓は切れる。

暇なときに最初に届いた報告だけ、固定窓 30 秒(既定)で待ってから束ねる。
外部の合図の待ち(既定 2.5 秒・ALTEROID_INBOX_SETTLE_MS・上限 10 秒)は変えない。
報告の窓は ALTEROID_REPORT_SETTLE_MS(0〜120000ms、0 で無効)かコンストラクタの
reportSettleMs で変える。読めない値・範囲外は既定で走り noteUnreadableRecord で跡を残す。

報告の窓の打ち切りは人間起点だけにしない(待ち候補以外が届いたら切る)。
人間起点だけだと、窓の途中で stop() が積む shutdown の蒸留が最大 30 秒待たされ、
stop() が同じだけ止まるため。

既存の試験のうち報告の到着から 2.5 秒前提のものは、報告の窓を 2.5 秒(または 0)に固定して
期待を変えずに通す。

Issue #5400 に触れる。
報告の窓の打ち切りが「待ち候補以外」だったため、外部の合図(runner-registry の
作業者の落ちなど)や失敗の知らせが窓を切らず最大 30 秒待たされていた。
「報告(manager_message かつ kind=report)以外が届いたら切る」に直す。
外部の窓自体(2.5 秒・人間起点だけで切る)は変えない。

ALTEROID_REPORT_SETTLE_MS を前例 #5369 と同じく APP_ENV_VAR_DEFAULTS(既定 30000)と
.env.example に登録し、credential set で restartRequired が返るようにする。
compose.yaml のコメントも実装に合わせる。

Issue #5400 に触れる。
担当の失敗・停止の知らせは manager.ts の #emit が kind: report・synthesized: true で
出すため、報告の長い窓の対象(と窓を切らない側)に入り、30 秒待たされていた。
長い窓の対象を synthesized でない報告だけにし、合成の報告は従来どおり短い窓
(外部と同じ 2.5 秒)で扱い、報告の窓の途中に届けば窓を切る。

Inbox#settle は待ちを張った後に push された合図しか interruptWhen にかけず、
#pump で取り出してから待ちに入るまでの await の間に積まれた人間の発言・蒸留は
窓の長さぶん待たされた。待ちに入る時点で待ち行列に割り込みがあれば待たない。
外部の窓でも、待ち行列に既に人間起点の合図があれば即抜けるようになる。

.env.example の「播種される7つ」を 9 つに直す。

Issue #5400 に触れる。
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