Skip to content

使用量の帳簿が同じ message.id の最後の usage を捨て、続けたサブエージェントを 2 回数え、日をまたぐ会話を丸ごと採るか落とす → 会話の記録の合計と一致させる #1979

Open

Description

@takemi-ohama

依頼(原文)

何を見つけたか

スプリント PR 1973(#1953 の帳簿)の cross-review のラウンド 1 で、codex が重大(major)の指摘を 5 件出した。そのうち次の 4 件は帳簿の数え方の誤りで、修正も起票もされないまま develop へマージされ、10.17.74 で出た。今の develop(425820c72、v10.18.0 の後)にもそのまま残っている(2026-10-17 に指摘 1 を再現: 同じ message.id の 2 行で output_tokens が 1 → 50 の記録を transcript_usage が 1 と数える)。

番号 箇所 指摘
1 plugins/ndf/scripts/lib/usage_capture.py:195(transcript_usage) 同じ message.id の 2 行目以降を捨てるため、ストリーミングで更新された最後の usage が帳簿に入らず、少なく数える。scripts/token-usage.py の scan_file は「message.id で重複を除き、最後の usage を取る」
2 plugins/ndf/scripts/lib/usage_capture.py:306-322(record_subagent) SendMessage で続けたサブエージェントがもう一度止まると、会話の記録全体の累計を再び書き足すため、続ける前の分を 2 回数える
3 plugins/ndf/scripts/lib/usage_aggregate.py:188(transcripts) 期間を会話の最初の timestamp だけで決め、採った会話は全体を足す。日をまたぐ会話で対象日の分が落ちるか、期間外の分が混じる
4 plugins/ndf/scripts/lib/usage_aggregate.py:343(step_totals) runtime: claude の work が書く source=supervise の Claude の行を除くため、その使用量が報告の両方の欄から消える(codex の指摘。再現は未確認)

残る 1 件(issues/issue-1953-design.md:395 の「実装で確かめる」2 項目の証跡)は、kiro が同じラウンドのレビュー本文で確かめた記録がある。

どこで見つけたか

  • レビュー: スプリント m1953 #1973 (review) (「差分の外を指す指摘」の節。インラインのスレッドは無い)
  • 修正の worker の報告: ~/.local/state/ndf/sv/sprint-m1953/6-check-state/05-review.out の「codex の本文にだけある 5 件」。対応の対象を未解決のスレッドの数(0 件)で決めたため「今回の対象外とし、取り込み側へ渡す」とした
  • 取り込み側(cross-review の drive)は 5 件を次のラウンドへ渡さず、検査の件数は findings 3 / fixed 3 / unresolved 0 で終わった(run-6-review.log)
  • スプリントの振り返りで、範囲外として扱った指摘に番号が無いことから拾った

なぜ今直さなかったか

指摘が出た時点では修正の対象から外れ、範囲外の起票もされなかった(取りこぼし)。仕組み側の原因は別の課題(cross-review の本文だけの指摘が修正の対象から落ちる)に分ける。

受け入れ条件

  • 1. 同じ message.id が複数行に現れる会話の記録で、帳簿の値がその id の最後の usage になる(token-usage.py と一致する)
  • 2. 同じ agent_id のサブエージェントが 2 回止まっても、帳簿の合計が会話の記録の合計と一致する(2 回数えない)
  • 3. 期間の境界をまたぐ会話で、期間内の応答の分だけが集計に入る
  • 4. 4 の指摘を再現で確かめ、当たるなら runtime: claude の work の使用量が報告のどちらかの欄に 1 回だけ載る。当たらないなら理由を課題に残す
  • 5. 1〜3(と 4 が当たるなら 4)を再現するテストがある

関連

(書き手: 起票者(スプリント m1953 の振り返りで AI が起票)、日付: 2026-10-13。利用者の指示の原文は無い)

起票者の推定

課題の本文は、スプリントの振り返りで AI(起票者)が書いた。利用者の指示の原文は無く、下の条件はすべて起票者が推して足したものである。承認ゲート 1 で利用者が確かめるまで、利用者の決定として扱わない。

# 条件 推した出所 この要求での扱い
S1 同じ message.id の応答は、最後の usage を帳簿の値にする(scripts/token-usage.py と一致させる) 本文の受け入れ条件 1。scripts/token-usage.py:224 の「message.id で重複を除き、最後の usage を取る」 受け入れ条件 1
S2 同じサブエージェントが 2 回止まっても、帳簿の合計を会話の記録の合計と一致させる 本文の受け入れ条件 2。PR 1973 のレビューの codex の指摘 受け入れ条件 2・3
S3 期間の境界をまたぐ会話は、期間内の応答の分だけを集計に入れる 本文の受け入れ条件 3 受け入れ条件 4・5
S4 runtime: claude の work の使用量は、報告のどちらかの欄に 1 回だけ載せる 本文の受け入れ条件 4(再現が未確認と書かれていた) 受け入れ条件 6・7。載せる欄は前提 3
S5 1〜4 を再現するテストを置く 本文の受け入れ条件 5 受け入れ条件 8
S6 本文だけの指摘が修正の対象から落ちる仕組み側の原因は、別の課題で扱う 本文の「なぜ今直さなかったか」と #1980(閉じている) 含まない

目的

  • 使用量の帳簿と、その集計(usage-report.py・supervise の報告)の Claude の値を、Claude Code の会話の記録の値と一致させる。トークン削減の効果を、少なく数えた値・2 回数えた値・期間外の値で測らないようにする

前提

  • 前提 1: 4 件の指摘は、今の develop(d51159151)で再現した(2026-11-05)。手順と結果は下の「再現の記録」にある
  • 前提 2: 既に帳簿へ書かれた行(2 回数えた行を含む)は直さない。値が変わるのは、この変更が配布された後に書かれる行と、その後に行う集計だけである
  • 前提 3: supervise の work(runtime: claude)で起動した claude の使用量は、報告の「CLI(drive・work の中)」の欄に載せる。理由: この起動は external-ai.py と起動の共有処理(launch-cli.sh)を通り、codex・kiro・agy の work と同じ経路で帳簿へ書かれる。「LLM の使用量」の欄は supervise 自身が受け取った claude -p の結果の使用量だけを持つ(runtime を持つ work の結果の使用量は空)
  • 前提 4: 帳簿の行(サブエージェントの行を含む)は、行の時刻(at)で期間に入れる。1 行の中の応答を期間で分けない。期間で分けるのは、会話の記録から足す分(会話の記録だけの呼び出し)だけである
  • 前提 5: サブエージェントの会話の記録は、同じ agent_id で続けても 1 本のファイルに追記される(SendMessage で続けた後の hook の入力の agent_transcript_path は同じファイルを指す)

対象範囲

含む:

  • サブエージェントの会話の記録 1 本から使用量を読む処理(同じ message.id の扱い)
  • SubagentStop の hook が帳簿へ書く値(同じ agent_id の 2 回目以降の停止)
  • usage-report.py の Claude の帳簿の 3 つの値で、会話の記録から足す分の期間の絞り方
  • supervise のステップの帳簿の行の集め直し(step_totals)での、runtime: claude の work の行の扱い
  • 確定仕様 docs/specifications/ndf-usage-ledger-by-runtime.md の、上の 4 つに当たる記述

含まない:

再現の記録

一時ディレクトリに会話の記録と帳簿を作り、今のコードの関数を呼んだ(2026-11-05、d51159151)。

指摘 入力 今の値 期待する値
1 同じ message.id の 2 行(output_tokens が 1 → 50) transcript_usage の output_tokens = 1 50
2 1 回目の停止で応答 1 件(出力 5)、続けた後の 2 回目の停止で応答 2 件(出力 5・7) 帳簿の出力の合計 = 17 12
3 entrypoint=sdk-cli の会話で、応答が JST 11-01 23:50(出力 100)と 11-02 00:10(出力 7) 期間 11-01 で 107、期間 11-02 で 0 11-01 で 100、11-02 で 7
4 NDF_USAGE_SOURCE=supervise・NDF_USAGE_KIND=work で record_output("claude", …) を書いた後の step_totals {}(行が除かれる) Claude の行が 1 回載る

指摘 4 は、supervise 自身の claude -p の行も source=supervise・kind=work で書かれる(supervise_lib/claude.py の claude_kind)。今の帳簿の行の source と kind だけでは 2 つを見分けられない。

ドメインイベント

# イベント 引き金 失敗したとき 順序の前提
E1 Claude Code が会話の記録へ応答の行を書いた。ストリーミングの途中と最後で、同じ message.id の行が複数書かれる 応答の生成 NDF の外。読む側は読めた行だけを数える —
E2 サブエージェントが止まり、SubagentStop の hook が使用量を帳簿へ 1 行書いた サブエージェントの停止 帳簿へ書けなくても hook は落ちない(帳簿の契約の I5)。その回の分は帳簿に残らない E1
E3 同じサブエージェントを SendMessage で続けた 親の会話の SendMessage NDF の外 E2
E4 続けたサブエージェントがもう一度止まり、hook が再び 1 行書いた サブエージェントの停止 E2 と同じ E2・E3。E2 の行が書けなかったときも成り立つ必要がある(受け入れ条件 3)
E5 supervise の work(runtime: claude)が external-ai.py で claude を起動し、起動の共有処理が帳簿へ 1 行書いた work のステップ 帳簿へ書けなくても起動は落ちない —
E6 supervise がステップの終わりに、そのステップの帳簿の行をランタイムごとに集めた ステップの終わり 帳簿が読めなければ集めた値は空 E5
E7 supervise がフェーズレポートを書いた フェーズの終わり — E6
E8 usage-report.py が期間を受け、帳簿の行と会話の記録を読んで Claude の 3 つの値を出した 利用者・振り返りの実行 会話の記録が読めなければ、その分を足さない E1・E2・E4

用語

用語 意味
使用量の帳簿 NDF が起動したエージェントの呼び出し 1 回ごとに、呼び出し元・モデル・使用量を 1 行で追記する jsonl(用語集のとおり)
会話の記録だけの呼び出し 帳簿に行が無く、Claude Code の会話の記録にだけ残る claude -p の会話とサブエージェント(用語集のとおり)
Claude の帳簿の全体 用語集のとおり。受け入れ条件 3・5 で一致を見る値
呼び出し元 帳簿の行の source と kind の組(用語集のとおり)
起動の共有処理 起動の後に帳簿へ 1 行を書く 4 か所(用語集のとおり)

受け入れ条件

  • 1. 同じ message.id の assistant の行が 2 行以上ある会話の記録で、帳簿へ書く使用量の 4 つの値(入力・cache read・cache write・出力)が、その id の最後の行の usage になる。同じ記録を scripts/token-usage.py の scan_file で読んだ合計と一致する
  • 2. 前提: 同じ agent_id のサブエージェントが 2 回止まり、hook が 2 回動いた。結果: その agent_id の帳簿の行の使用量の和が、2 回目の停止の時点の会話の記録の合計と一致する
  • 3. 前提: 受け入れ条件 2 で、1 回目の行が帳簿に無い(書けなかった)。結果: 2 回目の停止の後の帳簿の行の和が、会話の記録の合計と一致する
  • 4. usage-report.py --ledger claude の期間(JST の日付・両端を含む)を指定したとき、会話の記録から足す分(会話の記録だけの claude -p の会話とサブエージェント)には、応答の時刻が期間内の応答の使用量だけが入る。最初の応答が期間より前で、後の応答が期間内の会話も、期間内の分が入る
  • 5. 期間を指定しないときの Claude の帳簿の全体と会話の記録だけの値は、受け入れ条件 1 の数え方を除いて変わらない
  • 6. supervise の work(runtime: claude)で起動した claude の使用量が、フェーズレポートの「CLI(drive・work の中)」の欄に 1 回だけ載り、「LLM の使用量」の欄には載らない
  • 7. supervise 自身の claude -p(runtime を持たない work・判断・PR の呼び出し)の使用量は、「CLI(drive・work の中)」の欄に載らない(今と変わらない)
  • 8. 受け入れ条件 1・2・3・4・6・7 を、それぞれ再現するテストがある。各テストは今の develop(d51159151)のコードで失敗する(7 は退行を防ぐテストで、今のコードでも通る)
  • 9. 確定仕様 docs/specifications/ndf-usage-ledger-by-runtime.md のサブエージェントの取り出し・期間・ステップの集め直しの記述が、受け入れ条件 1〜7 の振る舞いと一致する

非機能の条件

大項目 条件
運用・保守性 hook と起動の共有処理は、帳簿へ書けないとき・会話の記録を読めないときに落ちない(帳簿の契約の I5 を保つ)
移行性 帳簿の行の既存の項目を消さず、意味を変えない。項目を足すときは、足す前の行を読んでも集計が落ちない

影響

対象 影響
公開インタフェース 変わらない。usage-report.py の引数と出力の形、帳簿の行の既存の項目は保つ
データ 既存の帳簿の行は書き換えない。サブエージェントの行の使用量が、同じ agent_id の 2 回目以降は前回からの増分になる(行の項目を足すかは設計が決める)
既存の振る舞い サブエージェントの使用量が増える方向に変わる(最後の usage を取る)。期間を指定した集計の会話の記録の分が変わる。supervise の報告の「CLI(drive・work の中)」に claude が載る

検証手段

項目 手段
テスト uv run --frozen --project . --all-extras pytest plugins/ndf/scripts/tests/test_usage_capture.py plugins/ndf/scripts/tests/test_usage_aggregate.py plugins/ndf/scripts/tests/test_usage_ledger.py -q
テスト(全体) uv run --frozen --project . --all-extras pytest . -q -n 4
静的解析 ruff format --check と ruff check(CI の lint.yml と同じ)
手動確認 配布後に、SendMessage で続けたサブエージェントを 1 回動かし、その agent_id の帳簿の行の和と scripts/token-usage.py の値を比べる(リリース後テスト)

前提とする取り決め

項目 参照先 / 決めたこと
プロジェクト構造 取り出しと書き込みは plugins/ndf/scripts/lib/usage_capture.py、集計は plugins/ndf/scripts/lib/usage_aggregate.py に置く(確定仕様の「置き場」)。scripts/token-usage.py はリポジトリ固有で配布物から参照しない
コーディング規約 AGENTS.md。ライブラリは標準ライブラリとライブラリだけを import する(帳簿の契約の I1)
テスト戦略 関数の単体テストで、一時ディレクトリの会話の記録と帳簿(NDF_USAGE_DIR)を使う。.md の文言を照合するテストは書かない

境界

区分 内容
常に行う 既存の使用量のテストの実行、リンタの適用
確認してから行う 帳簿の行に項目を足すこと(承認ゲート 1 で設計を見る)
行わない 既存の帳簿の行の書き換え、scripts/token-usage.py の変更、codex・kiro・agy・jev の取り出しの変更

未決

項目 誰が決めるか 期限
supervise の work(runtime: claude)の行と supervise 自身の claude -p の行を見分ける方法(呼び出し元の kind を分けるか、別の項目で見分けるか) 設計(design)。承認ゲート 1 で利用者が見る 設計 PR
同じ agent_id の前回の値を、帳簿の既存の行から求めるか、別の控えから求めるか 設計(design) 設計 PR

関連

進行

モード: —

  • 要求と受け入れ条件 — 2026-11-05 15:30
  • 作業場所の用意
  • 設計 — 2026-11-05 15:38
  • 素材の収集と出典の確定
  • ドキュメント再構成 — 2026-11-05 15:45
  • ドキュメントレビュー
  • 計画
  • 実装
  • 構造改善
  • 実装レビュー
  • 完了判定
  • Pull Request
  • 確定仕様化
  • 後片付け
  • 配布
  • 体裁レビュー
  • リリース後テスト
  • 振り返り
  • 棚卸し

設計の承認ゲート 1 での利用者の指摘(直して出し直す)

(書き手: 利用者の答えをオーガナイザーが写した、日付: 2026-11-06。出し直しの設計はこの節を満たすように直す。この節は上書きしない)

利用者の判断(2026-11-06): 直して出し直す。範囲を、実際に起きている 3 つの数え違いに絞る(同じ応答を 2 回数える → 最後の行を採る・続けたサブエージェントの二重計上 → 差だけを書く・期間外の応答を数える → 応答ごとの時刻で絞る)。
外すもの: supervise の外部 AI(runtime: claude の work)の行を supervise 自身の行と分ける部分(受け入れ条件 6・7、決定 4、処理の流れ F4)。実際の帳簿に該当する行は 0 行で、起きた不具合の裏付けが無い。決定 11(前提 5 が外れたときの姿)は未確認のまま残す扱いで足り、決定 6(帳簿の置き場が 2 つある件)も要るか見直す。

  • 追記(2026-11-06、オーガナイザーの実測): 帳簿が 2 か所に分かれ、集計で片方が抜ける。決定 6(置き場が 2 つある件)は外さずに残す。 ledger_dir() は CLAUDE_PLUGIN_DATA があればプラグインのデータ置き場、無ければ ~/.local/state/ndf/usage を使う。hook(サブエージェント)は前者、Bash から動くスクリプト(supervise・cross-review・cross-refactoring・mvv-gate など)は後者に書く。このリポジトリの分で、前者 /persistent/ai/.claude/plugins/data/ndf-ai-plugins/usage/claude/devbasex__ai-plugins.jsonl は 1021 行(source はすべて subagent)、後者 /home/ubuntu/.local/state/ndf/usage/claude/devbasex__ai-plugins.jsonl は 1534 行(supervise 937・cross-refactoring 230・cross-review 195 ほか)。Bash から打つ usage-report.py は後者だけを読み、サブエージェントの 1021 行が丸ごと抜ける。ループの費用の予算にも、conductor がサブエージェントに任せた分が入らない(開発ループ: レーンを増やすと当たる回数・時間の上限を撤廃する(relay の 1 日の起動回数・周回の予算など) #2505 の「費用の予算が少なく数えられている疑い」の中身とみられる)。読む側が両方の置き場を合わせて読むか、書く側の置き場を 1 つにそろえる

人の判断待ち(開発ループ lroot3)

直して出し直す指摘

利用者の判断(2026-11-06): 直して出し直す。範囲を、実際に起きている 3 つの数え違いに絞る(同じ応答を 2 回数える → 最後の行を採る・続けたサブエージェントの二重計上 → 差だけを書く・期間外の応答を数える → 応答ごとの時刻で絞る)。
外すもの: supervise の外部 AI(runtime: claude の work)の行を supervise 自身の行と分ける部分(受け入れ条件 6・7、決定 4、処理の流れ F4)。実際の帳簿に該当する行は 0 行で、起きた不具合の裏付けが無い。決定 11(前提 5 が外れたときの姿)は未確認のまま残す扱いで足り、決定 6(帳簿の置き場が 2 つある件)も要るか見直す。

Activity

  1. added
    bugSomething isn't working
    priority: high実害・安全機構の欠落など、優先して対応する
    on Oct 13, 2026
  2. added 2 commits that reference this issue on Nov 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: ndf-skillNDF の Skill 本体bugSomething isn't workingpriority: high実害・安全機構の欠落など、優先して対応する

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions