Skip to content

区間の切れ目の再起動と次のコマンドの入力を前景の中継で自動にする(#895) - #921

Merged
takemi-ohama merged 4 commits into
developfrom
feat/issue-895-relay-restart
Sep 23, 2026
Merged

takemi-ohama merged 4 commits into
developfrom
feat/issue-895-relay-restart

Conversation

@takemi-ohama

@takemi-ohama takemi-ohama commented Sep 23, 2026

Copy link
Copy Markdown
Contributor

Summary

/goal /ndf:development-workflow の区間の切れ目で人が行っていた「/exit・起動し直し・次のコマンドの貼り付け」を、端末の前景に常駐する中継(plugins/ndf/scripts/relay.py)が行うようにした。人が入力するのは関門の答えだけになる。中継が使えないとき・止まると決めたときは、ndf-relay: の 1 行を出して今までどおりの運用へ落ちる。

  • relay.py(新設): run(claude を擬似端末の子として起動する。印を受けたら子へ /exit を入力し、プラグインを更新して次の区間を起動する。-p・副命令・パイプ・中継の下の起動は、本物の claude を exec して素通しする)/ stop / mark(Stop hook の本体)/ install(SessionStart hook の本体。安定した場所へ置き直し、bash / zsh の設定へ alias を 1 度だけ足す)
  • hooks/claude.json: Stop に mark、SessionStart に install を 1 件ずつ足した。Codex / agy の hook の定義は変えていない
  • token-guard.sh: 中継の直接の子の conductor では、1 度だけ通す扱いをやめ、上限を超えている限り止め続ける。止めたときの理由の文を ndf-next の形に直した
  • 文書: context-window.mdndf-next のブロックの形を定めた(形の定義はここ 1 か所)。relay.md を新設し、SKILL.mdREADME.md から参照を張った
  • 決定の記録: 実装で決めた 5 件(決定 21〜25)を追記した。未確認の 3 件は実測で決めた。/goal は位置引数で働く / 背景のサブエージェントも background_tasksrunning で載る / 目標の判定は記録の attachment.type: goal_status に書かれる

計画: issues/issue-895-plan.md。設計: issues/issue-895-design.md(設計 PR #908)。

関連する issue: Closes #895

Test plan

  • uv run --project plugins/playwright-kit/skills/playwright-kit-ops --with pytest pytest . -q -n 4 → exit=0(5443 passed。head fd7a2a4 で再実行)。-n auto はメモリの制約のため使わず、-n 4 で実行した
  • python3 scripts/check-skill-frontmatter.py → exit=0
  • python3 scripts/check-doc-staleness.py → exit=0
  • python3 scripts/check-markdown-links.py --root . → exit=0
  • python3 scripts/check-skill-repo-assumptions.py → exit=0
  • claude plugin validate . → exit=0(既知の policy / interface の警告だけ)
  • 単体テスト plugins/ndf/scripts/tests/test_relay.py(72 件)と test_token_guard.py(中継の下の 4 件を追加): AC3〜AC4b・AC6〜AC15・AC19〜AC21・AC23・AC24・AC26。子の claude は擬似端末の上で動く試験用のプログラム(tests/fixtures/relay_fake_claude.py)に差し替えた。install は一時の HOME だけで動かし、本物の ~/.bashrc は変えていない
  • 実装を 1 か所ずつ壊して、該当のテストが落ちることを確かめた(目標の判定の待ち・空回り・1 日の上限・素通しの深さ・背景の処理・停止の印・作業ディレクトリの落とし先・install の足し直し・ロック・写しの比較の 10 か所)
  • AC16・AC22: git diff origin/develop -- plugins/ndf/hooks/codex.json plugins/ndf/dev.agy が空。install を呼ぶのは hooks/claude.json の SessionStart だけ
  • Stop の hook の定義のコマンドを、中継の有無の 2 通りで shell からそのまま動かした(無し: 何もせず exit=0 / 有り: next.json が書かれた)
  • AC17・AC5・AC25 の通しの確かめ(本物の Claude Code 2.1.280・haiku、擬似端末の上。プラグインの更新は利用者の環境を変えないよう、差し替えのラッパーが受けた)。結果は下の「通しの確かめ」

通しの確かめ(2026-09-23 13:13〜13:39 UTC)

項目 合否 見たこと
AC17 合格 人の入力は 1 つ目の区間の最初の入力と AskUserQuestion の答えだけで、3 つ目の区間まで起動した。log.jsonlstart 3 行と ended_by: markend 2 行が残った
AC5 合格 質問の表示から 3 秒後と 20 秒後に next.json が無かった。答えた 1.9 秒後に印が現れ、11.4 秒後に区間 2 へ切り替わった
AC25 合格 NDF_CONTEXT_LIMIT=30000 で、実装: #895 の Agent の起動を 2 回とも止めた(文脈量 39,796 / 40,147)。claude が ndf-next のブロックを出し、区間 2 が起動した
背景の処理(AC24) 合格 背景のサブエージェントが running のあいだは、ブロック付きの応答でも next.json が書かれなかった(80 秒見た)

log.jsonl(AC17 の通し。作業ディレクトリのパスは省いた):

{"event": "start", "at": "2026-09-23T13:18:09.642Z", "section": 1, "pid": 4187, "command": "", "from_session": "", "plugin_version": "10.17.3", "cwd": "…/runA/work"}
{"event": "end", "at": "2026-09-23T13:18:53.891Z", "section": 1, "pid": 4187, "seconds": 34.732, "ended_by": "mark"}
{"event": "start", "at": "2026-09-23T13:18:53.901Z", "section": 2, "pid": 8405, "command": "区間2: README2.md を読んで指示に従ってください", "from_session": "34fbad01-8e00-447b-bd08-a88b131b17db", "plugin_version": "10.17.3", "cwd": "…/runA/work"}
{"event": "end", "at": "2026-09-23T13:19:12.835Z", "section": 2, "pid": 8405, "seconds": 9.726, "ended_by": "mark"}
{"event": "start", "at": "2026-09-23T13:19:12.845Z", "section": 3, "pid": 11983, "command": "区間3: README3.md を読んで指示に従ってください", "from_session": "8aa69353-4dde-4e59-a9fe-d49845b288dd", "plugin_version": "10.17.3", "cwd": "…/runA/work"}
{"event": "end", "at": "2026-09-23T13:19:55.718Z", "section": 3, "pid": 11983, "seconds": 42.873, "ended_by": "no-mark"}

(最後の no-mark は、確かめを終えるときに /exit で閉じた区間 3 のもの)

未確認の項目の実測(設計の「未確認のまま残ること」):

  • 前の区間の画面: 生の出力には全区間が残るが、claude は区間ごとに代替画面を使う。そのため端末の履歴に残るのは Resume this session with: claude --resume <id> と区切りの 1 行だけになる。会話の記録は残る
  • /goal の判定が met: false で止めを拒むと、続いた応答がブロックを出さずに印が消え、中継は待ち続けた(設計の想定どおり)

未検証の項目:

  • macOS での親のたどり(ps の経路)は実機で確かめていない
  • 本物の claude plugin update を挟んだ切り替え(古い版のディレクトリが消える場合)は確かめていない。確かめの中ではラッパーが受けた
  • 利用者の本物の端末(VS Code の統合ターミナル)での見た目は確かめていない

既存の失敗: 無し

範囲外と判断したもの:

  • 新しい会話の最初の応答の中で工程へ入る起動は、会話の記録に assistant の行がまだ無いため、文脈量の hook が止めない(導入済みの 10.17.3 も同じ)。新しい会話の最初の応答は文脈が小さく上限に届かないので、実害は無いと判断した
  • 中継そのものが SIGTERM / SIGHUP で終わるときは、その区間の end の行が書かれない(子の claude は擬似端末が閉じて後から終わる)。設計は end を「区間の claude が実際に終わったとき」に書くと定めており、中継が先に終わる場合の行は求めていない

工程の記録

  • 構造改善: 利用者の指示で省略(cross-refactoring: 変更範囲が狭くても 1 項目ごとに --baseline-test の全体テストを回し、1 項目あたりの待ちが全体テストの時間になる #880 ほか)
  • 実装レビュー: /ndf:cross-review 921(codex + kiro、agy は --exclude agy)が 1 ラウンドで収束(codex=APPROVE / kiro=COMMENT で minor 2 件)。最終スイープで 2 件とも修正した(90054b09: _is_self は読めないファイルを中継と見なさない、relayedlocal にする)。未解決の指摘は 0 件(verify-sweep で確認)
  • CI 修正: 90054b0 の CI で test_run_max_starts_race_one_wins が 1 度落ちた。原因はテストの待ちで、HOME を共有する 2 つの中継のうち相手の stop の行で抜けていた。起動を遅らせる包みで 5/5 再現し、各中継の自分の記録だけを見て両方が決まるまで待つ形に直した(fd7a2a4c、遅延付きで 20/20 通過)。relay.py は変えていない
  • 完了判定: head fd7a2a4 で継続的統合がすべて success。手元でも全体テストと CI と同じ静的検査(build-runtime-plugins --check / frontmatter / repo-assumptions / shell-vars / validate-runtime-plugins / markdown-links / doc-line-limit / instructions-check / pr-body-decisions / claude plugin validate .)がすべて exit=0

影響範囲

配布先 変わるか
Claude Code 変わる。bash / zsh の利用者には SessionStart が alias claude=... を 1 度だけ足す(NDF_RELAY_AUTO=0 で止まる)。-p・副命令・パイプは素通しで今までと同じ
Codex 変わらない(hook を足していない)
Kiro CLI 変わらない
agy 変わらない(hook を足していない)

版を上げる必要があるか

要る。hook と利用者のシェル設定への書き込みを足す機能の追加で、まとまりのマージが終わった後に /ndf:release で上げる。

🤖 Generated with Claude Code

https://claude.ai/code/session_01DhcogXCb1x3eStK4VoDDCy

- relay.py を新設(run / stop / mark / install)。claude を擬似端末の子として起動し、
  Stop hook が写した ndf-next のブロックを受けて /exit・プラグインの更新・次の区間の起動を行う
- 中継が要らない起動(-p・副命令・パイプ・中継の下)は本物の claude を exec して素通しする
- SessionStart で中継を安定した場所へ置き直し、bash / zsh の設定へ alias を 1 度だけ足す
- 文脈量の hook は中継の直接の子の conductor で 1 度の通しをやめ、理由を ndf-next の形にする
- context-window.md に ndf-next のブロックの形を定め、relay.md を新設する
- 実装で決めた 5 件を決定の記録へ追記する

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DhcogXCb1x3eStK4VoDDCy

@takemi-ohama takemi-ohama left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

🤖 cross-review | round 1 | codex | APPROVE

修正必須の指摘はありません。

@takemi-ohama takemi-ohama left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

🤖 cross-review | round 1 | kiro | COMMENT

relay.py と token-guard.sh の設計・テストは決定 1〜25 と AC に密に対応しており、擬似端末上の統合テストも網羅的である。ブロッキングな欠陥は見つからなかった。以下は小さな 2 点の修正提案。

Comment thread plugins/ndf/scripts/token-guard.sh
Comment thread plugins/ndf/scripts/relay.py
- relay.py の _is_self は開けないファイルで False を返す。設計(先頭 4 KB に relay.py を
  含むものだけを飛ばす)に合わせ、実行だけできる正規の claude を飛ばして 127 になるのを防ぐ。
  飛ばし損ねた繰り返しは NDF_RELAY_DEPTH が止める
- token-guard.sh の guard_context で relayed を local の宣言に加える

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DhcogXCb1x3eStK4VoDDCy
@takemi-ohama

Copy link
Copy Markdown
Contributor Author

🔧 /ndf:fix サマリ | commit 90054b0

対応件数: critical=0 / major=0 / minor=0(合計 0 件)
決着: 2 件 / 見送り: 0 件 / 却下: 0 件
CI: NONE

HOME を共有する 2 つの中継では t.rows() が相手の記録も含むため、負けた側の
stop の行を勝った側も拾って待ちを抜けていた。勝った側の子が starts.jsonl へ
書く前に抜けると [1, 1] になる(CI の run 35870357441)。自分の中継の記録だけを
見て、両方が「2 つ目の起動」か「stop」に決まるまで待つ。

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DhcogXCb1x3eStK4VoDDCy
@takemi-ohama
takemi-ohama marked this pull request as ready for review September 23, 2026 14:10
要求・設計・決定の記録・実装計画の 4 本を確定仕様へ畳み、元の 4 本は
issues/old/milestone-26-relay-restart/ へ退避した。中継は副命令 5 つ・状態のファイル 6 つ・
記録の形・install の 7 段を持つ独立した機能のため、新しい仕様書を 1 本作った。

- ndf-relay-segment-restart.md(新設): 用語、構成要素、決定と理由(決定 1〜25 の結論と理由)、
  常に成り立つ条件、副命令の契約、素通しの条件、本物の claude の解決、中継の状態遷移と各段、
  上限と止め方、mark の判定、目標の判定の記録、文脈の上限で切る、install の I1〜I7、
  作業ディレクトリ・印・記録・環境変数、前提にした Claude Code の振る舞い、テスト観点
  (受け入れ条件の言い換え、通しの確かめの結果、実機で確かめていない 3 点)
- ndf-token-waits-and-context-cut.md: 文脈量の判定に「中継の直接の子では 1 度の通しをしない」、
  引き継ぎの 1 行を ndf-next のブロックで出すこと、用語・hook の出力・運用・テスト観点・関連リンク
- docs/specifications/README.md に 1 行、issues/old/README.md に 1 行
- relay.md の末尾の参照を確定仕様のパスへ向けた。元の 4 本を指す参照は退避先の外に無かった

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DhcogXCb1x3eStK4VoDDCy
@takemi-ohama
takemi-ohama merged commit 6cc919f into develop Sep 23, 2026
17 checks passed
@takemi-ohama
takemi-ohama deleted the feat/issue-895-relay-restart branch September 23, 2026 14:19
@takemi-ohama takemi-ohama mentioned this pull request Sep 23, 2026
12 tasks
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