Skip to content

Latest commit

 

History

History
294 lines (216 loc) · 16.1 KB

File metadata and controls

294 lines (216 loc) · 16.1 KB

claude-code-session-handoff

English · 简体中文 · 한국어 · Español · Français · فارسی

Claude Code は長いセッションを自動的に圧縮します。会話の大部分を削除し、要約に置き換えて、 そのまま続行します。気づくきっかけはたいてい、2時間前に決めたことをモデルが訊き直してくる 瞬間です。

このリポジトリは別のやり方を取ります。セッションが実際どれだけ埋まっているかを測り、 終端が本当に近いときにセッション全体をディスクへ書き出し、新しいセッションで続行します。 その新しいセッションは、何かを始める前に親の記録を完全に読み切ります。 要約で捨てられるものは何もなく、セッションの連鎖は常に見えていて再開できます。

本ツールを一切インストールしない場合でも、知っておく価値のある設定の罠も突き止めています。 クランプされる窓を参照してください。


インストール

Python 3.8+ と Claude Code が必要です。それ以外の依存はありません。

git clone https://github.com/IRDcode/claude-code-session-handoff
cd claude-code-session-handoff
python install.py --dry-run     # まず変更内容をすべて確認
python install.py

その後 Claude Code を再起動し、何が検出されたか確認します:

python ~/.claude/skills/long-session-handoff/scripts/session_weight.py --explain

既定のインストールは Claude Code のコンテキスト管理を変更しません。自動圧縮はそのままで、 ガードは圧縮が発火する前に引き継ぎを済ませるだけです。すべて削除するには:

python install.py --uninstall

settings.json は変更前にバックアップされ、既存の hooks やステータスラインには触れません。 二重にインストールしても何も起きません。

何がインストールされるか

パス 内容
~/.claude/skills/long-session-handoff/ モデルが従う手順と3つのスクリプト
~/.claude/hooks/session-weight-watch.py 検出器。4つのイベントに登録
~/.claude/hooks/statusline-weight.py 毎描画ごとにステータスラインへ占有量を表示
~/.claude/runtime/ 再通知抑制の状態、ログ、計測キャッシュ
~/.claude/handoffs/ 書き出したもの、および親子を結ぶ chains.json

登録される hook イベントは4つ——UserPromptSubmitSessionStartPreCompactPostCompact。これらに既にある hooks は保持されます。

既定の挙動との違い

既定の Claude Code 本ツール導入後
セッションが埋まったとき 自動圧縮が発火し、会話の大部分が破棄され要約に置き換わる その手前で、まず引き継ぎを提案する
次のセッションが知っていること 要約が捉えた範囲——しかも書いたのは既に把握を失いつつあった agent 親自身の記録を完全に読み、件数で照合済み
破棄された履歴 現行セッションからは参照されない ディスクから 05-dropped-context.md に復元
実際どれだけ埋まっている? /context が窓に対する割合を表示 ステータスラインが、実際にセッションを終わらせる壁に対する割合を表示
後から続行セッションを見つける /resume をスクロール chains.json に親・子・移行時の占有量・読み取り検証の有無が記録される

ステータスラインはこうなります:

Opus 5 | ████████░░ 85% 830k/977k | 696t 402tc 6.1h | HANDOFF DUE (5) | no-compact

に対する割合であり、窓に対する割合ではありません。両者は異なり、 ときに5倍も違います。それが次節の要点そのものです。

クランプされる窓

何もインストールしないとしても、この節は読む価値があります。

Claude Code にはセッションが終わる地点が2つあります:

圧縮の発火点    窓   − 応答予約(~20k) − 要約バッファ(~13k)
送信拒否の地点  上限 − 応答予約(~20k) − 余裕(~3k)

自動圧縮が有効なら前者、無効なら後者です。つまり 200,000 の窓はおよそ 167,000 で 圧縮されます。

罠はここです。settings.jsonautoCompactWindow はモデル上限へ黙ってクランプ されます。 上限 200,000 に対して 1,000,000 を要求すれば、得られるのは 200,000—— そしてそのことを UI はどこにも表示しません。100万トークン向けに設定したセッションが 167,000 で3回連続して圧縮され、その間 約830,000 の支払い済みトークンが未使用のまま 放置されます。

これは仮定ではありません。本リポジトリの出発点そのものです。preTokens 167,398 / 167,071 / 166,904 の3回の圧縮に対し、設定ファイルには autoCompactWindow: 1000000 と書かれていました。

--explain が自分がどちらの状況かを示します:

WINDOW
  client reported  1,000,000
  ceiling          1,000,000   (source DISABLE_COMPACT+CLAUDE_CODE_MAX_CONTEXT_TOKENS)
  resolved         1,000,000   (source settings)

WALL -- the token count past which no more work happens here
  1,000,000 ceiling - 20,000 reply reserve - 3,000 margin
  = 977,000   then SENDING IS REFUSED (no summary; a handoff is the only exit)

settings CLAMPED to … と出たら、窓の設定が押し下げられています。

クランプを回避できる構成は1つだけ——DISABLE_COMPACT=1CLAUDE_CODE_MAX_CONTEXT_TOKENS の併用です。インストーラが設定できますが、 先に確認し代償を伝えます。手動の /compact も無効になるからです:

python install.py --disable-compact --window 1000000

これを設定すると、セッションは要約では終わらず、送信拒否で終わります。 これは本物のトレードオフです。拒否は耐えられます——引き継いで続けるだけです。 黙って破壊された履歴は耐えられません。ただし提案をすべて無視して壁まで進めば、 そのセッションは新しいターンを受け付けなくなります。それは事前に知っておくべきです。 ガードは 85% で発火し、約147,000トークンの余地を残すので、実際にはそこまで行きません。

/compact を残したいならこのフラグは使わないでください。引き継ぎ自体は問題なく動きます。

互換性

ここには Claude Code にパッチを当てたりラップするコードはありません。文書化された2つの インターフェース——hook の stdin/stdout 契約とステータスラインのペイロード——を読み、 クライアントがすでに書いている JSONL の記録を解析するだけです。内部実装に依存した ツールなら壊れるような更新を、これが乗り越えられる理由がそこにあります。

正確な数値が必要なところでは、断定ではなく証拠から取ります。三層です:

  1. 窓は context_window_size——クライアントがステータスライン描画ごとに自分について 報告する値です。
  2. そのセッションが圧縮された経験を持つなら、発火点は記録に残る当時の preTokens から 取ります。これは計算値ではなく観測された発火点そのものです。score() はこちらを 優先し、その場合 corrected from observed preTokens と明示します。
  3. どちらも無いときだけ予約の算術に戻ります。--explain が全入力を示すので、ズレは 黙って進行せず目に見えます。

古い Claude Code。 4つの hook イベントとステータスラインは多くのリリースで安定して います。あなたの版に無いイベントがあれば、その hook は発火せず残りはそのまま動きます—— 検出器は追加であって置き換えではありません。--disable-compact だけは具体的な設定名に 依存します。効かなかった場合は --explain が教えてくれます。

OS。 純粋な Python、依存なし、コンパイル部分なし。パスは常に os.path を通り、 CLAUDE_CONFIG_DIR はどこでも尊重され、インストーラはあなたのシェルで実際に動く インタプリタ名を選びます(ハードコードしません)。プラットフォーム依存は stdout を UTF-8 に固定する箇所だけで、Windows には必要、他では無害です。

自分の環境で検証してください:

python tests/test_session_weight.py    # 算術、ゲート、2つの罠
python tests/test_compat.py            # 構文の下限、エントリポイント、hook 出力

test_compat.py はこのマシンにある他のすべての Python を見つけ、それぞれでスイートを 再実行します。バージョン差は後日の驚きではなく、失敗として今現れます。

いつ発火するか

7つの信号を測ります。移行するか否かを決めるのはコンテキストだけで、 残りはどれだけ急ぐかにしか影響しません。

信号 しきい値
コンテキスト vs 壁 ≥ 85% → 引き継ぎ、≥ 95% → 確認せず実行
assistant のターン数 ≥ 900
ツール呼び出し ≥ 600
実作業時間 ≥ 4 時間
自動圧縮が既に発生 1回以上

壁の 62% を下回る間は、他の信号がどうであれ提案しません。 このゲートがあるのは、残りの信号がすべてコンテキスト圧の代理指標—— コンテキストを直接測れなかった時代の発明——だからです。本ツールを作ったセッションでの実測: 4.3時間の作業と2回の先行圧縮でスコアは「今すぐ引き継ぎ」に達したのに、 コンテキストは 977,000 中 147,527——15% でした。そこで移行すれば 829,473 トークンを 何の見返りもなく捨てることになります。

地味に見えて重要な計測上の2点:

  • 実作業時間は10分未満の間隔の合計で、最後から最初を引いた値ではありません。 開いたまま一晩置いたセッションはスパン44時間・作業11時間と出ます。 スパンで採点すると、ただ放置されただけのセッションで引き継ぎが発火します。
  • 圧縮回数は記録内の型付き行から数えます。マーカー文字列の検索は決して使いません。 一度検索すれば、その文字列は自分のツール出力に現れ、カウントが自己増殖します。

提案は各段階につき最大1回——さらに200ターン、または壁のもう10分の1を超えたとき—— かつ15分の下限があります。サブエージェント内では決して発火しません。

引き継ぎそのもの

測る  →  訊く  →  書き出す  →  続行セッションを作る  →  それが親を読む

書き出しは5つのファイルを作ります。ユーザーの全メッセージを逐語で(ターン途中に送られた —— 失われやすい —— ものも含む)、実質的な assistant メッセージすべて、 ツール出力を切り詰めた完全な記録、件数の索引、そして以前の圧縮が捨てた内容です。

続いて続行セッションが作られ、あなたが開く前にヘッドレスで起こされて書き出しを読みます。 索引と一致する件数を返さなければなりません。一致しなければ読みは部分的で、引き継ぎは未完了です。 この読み取りは誰も待っていないセッションで起きるので、移行の重い部分に待ち時間はかかりません。

その後 id と名前が渡されます:

claude --resume 7157caa1-11ce-4f29-a46a-09913d483fb0

または /resume で名前を検索します。名前には親の主題語と (cont. 2) が入っています。

自分で検証する

上記のすべては自分の環境で確認できます。スクリプトが出すのは数字で、慰めではありません:

# 自分のセッションはどこで終わり、それはなぜか
session_weight.py --explain

# 現在の占有量を、全信号つきで
session_weight.py --session-id <uuid>

# 機械可読
session_weight.py --session-id <uuid> --json

設定変更が効いたかを確認するときは、ファイルを信じず記録を読みます。 トークン合計が旧しきい値を越えた最初のターンを見つけ、その後に新しい圧縮行がないことを確かめます。

制約

測るためのツールは、自分が測っていないことについて正直であるべきなので、率直に書きます:

  • 各予約(~20k / ~13k / ~3k)は観測された挙動から導いたものです。将来の版で変わる可能性が あります。防御は三層です:クライアント自身が報告する窓、記録に残る実際の preTokens (観測された発火点。score() はこちらを優先します)、そして最後にこの予約の算術—— 推測なのは第三層だけです。
  • 送信拒否の壁は計算と裏付けによるもので、意図的に到達させてはいません。 ガードはそこへ行かせない設計です。
  • Windows で Python 3.11 / 3.12 / 3.14 を用いてテストし、3.8 の文法に対しても検査済みです。 Linux と macOS も動くはずですが——コンソールのエンコーディング以外に プラットフォーム依存のコードはありません——どちらも通しでは実行していません。
  • 5ファイルの書き出しとスコアラーはテストスイートが検証しています。ヘッドレスでの起動は あなたの claude 実行ファイルが起動できるかに依存します。起動できない場合でも書き出しは 成功し、次に何をすべきかをツールが伝えます。
  • プロンプトキャッシュ:引き継ぎは新しいセッションを始めるので、キャッシュは冷えた状態から 始まります。壁に近いセッションにとっては有利な交換ですが、コストではあります。

貢献

不具合報告を歓迎します。とくに「自分の環境では数字が違う」——--explain の出力を添えて ください。Claude Code の更新でこの算術が変わった場合、その報告がいちばん早く直せます。

PR を出す前に両方のスイートを実行してください:

python tests/test_session_weight.py
python tests/test_compat.py

セキュリティ

SECURITY.md に、何を読み、何を書き、ネットワークへ何を送るのか(何も 送りません)を明記しています。セッションのファイルに触れるものを入れる前に一読を。

ライセンスとクレジット

MIT —— LICENSE を参照。商用を含め、自由に使用・改変・再配布できます。 唯一の条件は著作権表示とライセンス本文を一緒に保つことで、フォークや再パッケージされた コピーでも出所が分かるようになっています。

このアプローチや調査結果——とくに窓がクランプされる件の診断——を利用した場合は、リンクを いただけると嬉しいです。CITATION.cff を置いてあるので、GitHub の 「Cite this repository」ボタンが正しい引用情報を返します。

作者:IRDkiya