作成日: 2026-06-04
ステータス: Draft
対象 issue: #49 Handover の仕組みをつくる
関連文書:
本仕様は、Codex / Claude Code など複数 Brain 間、または同一 Brain のセッション切り替え時に、作業文脈を引き継ぐための Brain-facing インターフェースを定義する。
Handover は単なる会話要約ではない。
目的は、次の Brain が「何を読めばよいか」「どこまで決まっているか」「次に何をすべきか」を迷わず把握できる Markdown artifact を Butler が作成・取得できるようにすることである。
Phase 1 では、Butler 単体で動作する core mode を実装対象とする。
mem0 / Supermemory など外部 memory MCP 連携は enhanced mode として Phase 2 以降に扱う。
LLM を使った開発作業では、長時間同じセッションを使い続けると次の問題が起きる。
butler2 には既に session_log capability があり、会話ログの読み取り、mark 以降の抽出、Codex JSONL / Claude Code JSONL の読取、snapshot 生成の土台を持つ。
ただし session_log は会話ログを扱う低レベル capability であり、Brain が「引き継ぎを作る」「引き継ぎを読む」と考えた時の入口としては粒度が低い。
そのため、Handover capability では session_log / work_record / issue を束ねた高レベルの Brain-facing 操作を提供する。
Phase 1 の対象:
work_session_start(project_root) で初期化された現在の project 内に閉じた Handoverhandover_suspend の Brain-facing tool 定義handover_resume の Brain-facing tool 定義handover_transfer の Brain-facing tool 定義handover_accept の Brain-facing tool 定義handover_prepare の Brain-facing tool 定義handover_read の Brain-facing tool 定義handover_list の Brain-facing tool 定義session_logs/handover/ への Markdown snapshot 保存Phase 1 では次を行わない。
work_finish() からの自動実行handover_accept 時の永続的な受領イベント記録| 用語 | 意味 |
|---|---|
| Brain | Codex / Claude Code など、作業を行う LLM エージェント |
| Handover | 作業文脈を次の Brain / セッションへ引き継ぐこと |
| Handover snapshot | 次の Brain が最初に読む Markdown artifact |
handover_id |
snapshot を一意に識別する ID |
| core mode | Butler 単体で動く初期モード |
| enhanced mode | mem0 / Supermemory など外部 memory MCP を補助的に使う将来モード |
HANDOFF_PROMPT |
次の Brain にそのまま渡せる短い再開指示 |
| current project | work_session_start(project_root) で初期化された現在の Butler project |
| lifecycle command | ユーザーの意図に近い高レベル Handover 操作 |
| primitive tool | lifecycle command の内部で使う低レベル操作 |
| suspend | 同一 Brain のセッション終了前に、再開用 snapshot を残すこと |
| resume | 同一 Brain のセッション再起動後に、前回 snapshot を読んで復帰すること |
| transfer | 現在の Brain から別 Brain に作業を渡すこと |
| accept | 別 Brain から渡された作業を受け取ること |
core mode は Butler 単体で完結する。
情報源:
work_observe() の作業状態session_log の会話ログまたは参照情報notes外部 memory MCP が存在しなくても動作しなければならない。
enhanced mode は Phase 2 以降の拡張候補である。
想定する追加情報源:
enhanced mode でも Handover の正本は Butler の Markdown snapshot とする。
外部 memory MCP は長期記憶、横断検索、個人設定や過去知識の想起を補助する層であり、正本にはしない。
外部 memory MCP が利用できない場合は core mode にフォールバックする。
Handover snapshot は Markdown とする。
次の Brain が読むことを前提に、短く、構造化され、人間が確認・編集できる形にする。
必須セクション:
## METADATA
## CURRENT_STATE
## DISCOVERY
## DECISION
## NEXT_ACTION
## BLOCKED / PENDING
## RISKS
## CODEBASE_ANCHORS
## HANDOFF_PROMPT
セクション名は厳密に扱う。
特に HANDOFF_PROMPT は必須セクション名であり、HANDIFF_PROMPT などの表記揺れを許容しない。
実装時は必須セクション名を定数として定義し、テンプレート側で typo が混入しないようにする。
検討用 artifact では FORMAT_NOTES を含めてもよいが、実運用 snapshot では必須ではない。
長さは Phase 1 では Markdown 200-300 行以下を目安にする。
METADATAsnapshot の機械的な属性を記録する。
最低限含める項目:
projectissuesource_braintarget_brainmodecreated_athandover_idtarget_brain は引き継ぎ先 Brain を表す。
不明な場合は any とする。
handover_prepare の呼び出し時に Brain が明示しない限り、Butler は any を自動設定する。
CURRENT_STATE現在どこまで終わっているかを書く。
Phase 1 では LLM 要約に頼らず、issue title/body、work_observe()、notes から deterministic に埋める。
DISCOVERY調査で分かったことを書く。
session_log の mark 以降や notes から抽出できる事実を箇条書きにする。
推測で増やさない。
DECISION採用した判断と理由を書く。
notes または既存の明示情報にある決定だけを入れる。
Phase 1 時点での基本決定:
handover_suspend / handover_resume / handover_transfer / handover_accept とするhandover_prepare / handover_read / handover_list は primitive tool として定義するsession_log summarize は会話ログ単体の構造化、handover_prepare は issue / work / session を束ねた再開文書生成として分けるNEXT_ACTION次にやることを番号付きリストで書く。
required_action / suggested_action や notes をもとに、次の Brain がすぐ作業に移れる粒度にする。
BLOCKED / PENDING未解決・確認待ちの項目を書く。
各項目には状態ラベルを付ける。
状態ラベル:
推奨採用暫定未確定ブロックRISKS注意点や壊しやすい箇所を書く。
既知のリスクがなければ なし と書く。
CODEBASE_ANCHORS実装再開時に見るべきファイルと役割を書く。
Phase 1 での候補:
butler/mcp_facade.py: Brain-facing MCP tool の定義butler/maids/session_log_worker.py: session_log の deterministic 読み取り・記録処理butler/maids/session_log_summary_worker.py: session_log summarize の Worker 呼び出しと snapshot 保存処理butler/work_record_backend.py: work_observe() など作業状態の backendbutler/issue_backend.py: issue_read() など issue 操作の backendtests/test_session_log_non_llm_maid.py: session_log の既存テストtests/test_mcp_facade.py: MCP facade の tool 公開テストHANDOFF_PROMPT次の Brain にそのまま渡せる 3-8 行程度の再開指示を書く。
Phase 1 の handover_prepare は LLM Worker に依存しない。
deterministic template に既存情報を埋め込んで snapshot を生成する。
方針:
CURRENT_STATE は issue / work_observe() / notes から埋めるDISCOVERY は session_log mark 以降または notes から得られる事実だけを書く。ただし handover_prepare が session_log のどの範囲を読むかは未確定であり、mark 以降は現時点の主候補である。詳細は「18. 未決事項」を参照するDECISION は明示された決定だけを書くNEXT_ACTION は required_action / suggested_action と notes をもとに書くBLOCKED / PENDING は状態ラベル付きで書くRISKS は既知情報だけを書くHANDOFF_PROMPT は短い再開指示として生成するsummary は HANDOFF_PROMPT の冒頭 1-2 文、または notes の先頭から生成するLLM による要約や推論は Phase 2 とする。
Phase 1 の Handover は手動トリガーから始める。
ただし、ユーザーに handover_list、handover_read、work_observe の組み合わせを毎回説明させてはならない。
ユーザーが言う自然な操作は次の 4 つに集約する。
| ユーザーの意図 | lifecycle command | 主な内部 primitive |
|---|---|---|
| 同じ Brain のセッション終了前に記録する | handover_suspend |
handover_prepare |
| 同じ Brain のセッション再起動後に復帰する | handover_resume |
handover_list / handover_read / work_observe |
| 別 Brain へ渡す | handover_transfer |
handover_prepare |
| 別 Brain から受け取る | handover_accept |
handover_resume / handover_read / work_observe |
handover_prepare / handover_read / handover_list は、Butler 内部または明示的な手動操作で使う primitive tool として残す。
普段の Brain-facing な入口は lifecycle command を優先する。
自然文の解釈は Brain が行い、Butler には structured filter を渡す。
Butler の検索範囲は work_session_start(project_root) で初期化された current project に閉じる。
同じ Brain のセッションを終了する時:
今の作業を次のセッションで再開できるようにしてください。
セッションを閉じるので suspend してください。
VSCode を再起動する前に handover を残してください。
同じ Brain のセッションを再開する時:
VSCode を再起動しました。resume してください。
前回のセッションから再開してください。
handover から復帰してください。
別 Brain へ渡す時:
この作業を Claude Code に引き継げるようにしてください。
Codex から Claude Code へ transfer してください。
#49 を別 Brain に渡してください。
別 Brain から受け取る時:
Claude Code から引き継いだ内容を受け取ってください。
Codex からの handover を accept してください。
#49 の引き継ぎを受け取って再開してください。
Handover 履歴から探す時:
#49 の handover 履歴を見せてください。
Claude Code が作った handover を探してください。
昨日の handover から再開してください。
handover_suspend を呼ぶ。session_logs/handover/ に snapshot を保存する。handover_id、snapshot_file、HANDOFF_PROMPT を Brain に返す。handover_resume で復帰できることを伝える。handover_resume を呼ぶ。work_observe() 相当の最新状態を合わせて返す。DECISION / NEXT_ACTION / BLOCKED と現在状態を読んで作業を再開する。handover_transfer(issue_id=49, target_brain="claude_code") を呼ぶ。target_brain=claude_code を METADATA と index に保存し、snapshot を作成する。HANDOFF_PROMPT と snapshot の場所をユーザーに提示する。handover_accept(source_brain="codex", issue_id=49) を呼ぶ。source_brain=codex、target_brain=claude_code または any の候補を優先して探す。required_action に含める。need_input を返し、引き継ぎ元 Brain での handover_transfer を促す。work_observe() 相当の現在状態と snapshot をもとに再開方針を提示する。handover_list(issue_id=49) を呼ぶ。index.json から #49 の候補一覧を返す。created_at、source_brain、target_brain、summary を使って候補をユーザーに提示する。handover_id で handover_read(handover_id=...) を呼ぶ。work_observe() で現在状態を確認して再開する。created_from / created_to の日付範囲に変換する。handover_list(created_from=..., created_to=...) を呼ぶ。index.json だけを検索し、候補一覧を返す。handover_read(handover_id=...) へ進む。issue_title、source_brain、created_at、summary を使って候補を提示し、ユーザーに選ばせる。work_observe() で最新状態を確認して再開する。project_root を確認する。work_session_start(project_root) を呼び、current project を切り替える。handover_list(...) を呼び、対象 project 内の候補を取得する。handover_id で handover_read を呼ぶ。work_session_start(project_root) を呼び直す。Phase 1 では、複数 project を同時に横断検索しない。
別 project を読む場合は、必ず対象 project で work_session_start(project_root) を呼び直す。
Phase 1 では Memory MCP は使用しない。
図中の Memory MCP は enhanced mode の将来拡張を表す。
Phase 1 では複数 project 横断検索を行わない。
handover_suspend は同一 Brain の再起動用入口であり、内部的には handover_prepare を呼ぶ。
handover_resume は「読むべき snapshot を探し、読んだ後に現在状態も確認する」入口である。
ユーザーが handover_read(handover_id=...) のような低レベル指定を覚える必要はない。
handover_transfer は別 Brain に向いた snapshot を作る入口であり、内部的には handover_prepare(target_brain=...) を呼ぶ。
handover_accept は内部的には handover_resume と近い処理を使ってよい。
ただし「別 Brain から責任を受け取る」というユーザー意図を表すため、Brain-facing tool として独立させる。
履歴検索は Phase 1 では index.json のメタデータ一覧に限定する。
本文全文検索や semantic search は enhanced mode の候補とする。
このシーケンスでは、Brain が自然文を structured filter に変換する。
Butler は自然文を解釈せず、current project の index.json を filter 条件で検索する。
Phase 1 では、ひとつの Butler セッションから複数 project を横断検索しない。
別 project の Handover を読む場合は、Brain が対象 project で work_session_start(project_root) を呼び直す。
読み終わった後に元 project で作業を続ける場合も、Brain が元 project で work_session_start(project_root) を呼び直す。
Handover は append-only な履歴として保存するため、再開時には「どの snapshot を読むか」を決める必要がある。
ユーザー向けの再開入口では、Brain にこの選択手順を暗記させない。
handover_resume と handover_accept は、Butler 側で候補検索、単一候補の read、複数候補時の候補返却、現在状態確認までを吸収する。
handover_read(issue_id=49) のように issue が明示されている場合は latest snapshot を読む。
一方で、ユーザーの指示は次のように曖昧な場合がある。
昨日の handover から再開してください。
Claude Code が作った handover を探してください。
前に作った handover を読んでください。
この場合、Brain は最初から issue_id を知っているとは限らない。
そのため、handover_list は issue_id なしで current project 全体の Handover 履歴を検索できなければならない。
Phase 1 では Butler は自然文検索を行わない。
自然文の解釈は Brain の責務とし、Butler は structured filter を受け取って検索する。
| ユーザー指示 | Brain の解釈 | Butler call |
|---|---|---|
#49 の handover 履歴 |
issue_id=49 |
handover_list(issue_id=49) |
Claude Code が作った handover |
client="claude_code" |
handover_list(client="claude_code") |
Codex に渡す handover |
target_brain="codex" |
handover_list(target_brain="codex") |
昨日の handover |
日付範囲 | handover_list(created_from=..., created_to=...) |
#49 の最新 handover |
issue latest | handover_read(issue_id=49) |
再起動したので resume |
current Brain / default issue / latest | handover_resume(client=...) |
Codex から accept |
source_brain=codex + current Brain |
handover_accept(source_brain="codex") |
client は source_brain の filter である。
handover_list では既存互換のため client を残しつつ、handover_accept と同じ意味で使える source_brain alias も受け付ける。
Handover の検索範囲は current project に閉じる。
current project は work_session_start(project_root) で初期化された project である。
別 project の Handover を読みたい場合、Brain は先に対象 project で work_session_start(project_root) を呼び直し、その project の Butler セッションとして handover_list / handover_read を呼ぶ。
Phase 1 では、複数 project を横断した Handover 検索は行わない。
handover_list の結果に応じて、Brain は次のように振る舞う。
| 候補数 | 方針 |
|---|---|
| 0 件 | handover_prepare を促すか、検索条件の変更をユーザーに確認する |
| 1 件 | Brain は候補を提示する。ユーザー指示が「最新を読んで」など明示的な場合を除き、原則として確認してから handover_read(handover_id=...) へ進む |
| 複数件 | Brain が候補一覧をユーザーに提示し、選択を促す |
複数候補がある場合、Butler や Brain が勝手に 1 件を選ばない。
候補提示には issue_id だけでなく issue_title、source_brain、target_brain、created_at、summary を使う。
handover_resume / handover_accept でも候補が複数ある場合は自動選択しない。
ただし候補が 1 件に絞れた場合は、ユーザーに handover_id を再入力させず、その snapshot を読んでよい。
Handover capability には、ユーザーの意図に近い lifecycle command と、内部実装または明示的な手動操作で使う primitive tool がある。
| 種別 | tool | 役割 |
|---|---|---|
| lifecycle | handover_suspend |
同一 Brain のセッション終了前に snapshot を作る |
| lifecycle | handover_resume |
同一 Brain のセッション再起動後に読むべき snapshot を探して再開する |
| lifecycle | handover_transfer |
別 Brain に向けた snapshot を作る |
| lifecycle | handover_accept |
別 Brain から渡された snapshot を受け取る |
| primitive | handover_prepare |
snapshot を作る |
| primitive | handover_read |
指定 snapshot を読む |
| primitive | handover_list |
snapshot 履歴を検索する |
| primitive | handover_list_brains |
接続実績のある Brain 一覧を返す |
Brain は、ユーザーが単に「handover」「再開」「引き継ぎ」と言った場合、文脈に応じて lifecycle command を優先する。
handover_prepare / handover_read / handover_list は、ユーザーが具体的に指定した場合、または lifecycle command の内部処理として使う。
work_session_start(project_root) 済みの current project に閉じるissue_id が省略された場合は default issue を使えるwork_session_start 時に MCP clientInfo.name から自動設定される(session.brain_id)。client パラメータを明示した場合は override として使う。通常は client を省略してよいneed_input と次の推奨操作を返すcandidate_handovers を返し、勝手に選ばないwork_observe() 相当の現在状態を返し、snapshot 作成時からのずれを見えるようにするhandover_read / handover_resume / handover_accept の返値に required_action が含まれる場合、Brain はそれを単なる任意提案として扱わない。
required_action は Butler が再開整合性のために示す次操作であり、Brain は原則としてユーザー確認を挟まず実行する。
例外は次の場合に限る。
required_action が破壊的操作、外部公開、課金、権限昇格などユーザー承認が必要な操作を含むneed_input として返り、Butler が明示的にユーザー入力を求めているこの規範は session や memory に依存せず、MCP instructions と本仕様の両方で定義する。
handover_suspend役割: 同一 Brain のセッション終了前に、次回 handover_resume で復帰できる snapshot を作成する。
内部的には handover_prepare を呼ぶ。
source_brain および target_brain は session.brain_id(clientInfo.name から自動設定)を使う。
client が明示された場合は client を override として優先する。
client も session.brain_id も不明な場合は既存方針どおり any を使う。
resume 時は target_brain の exact match を優先し、該当がなければ any にフォールバックする。
パラメータ:
| 名前 | 必須 | 説明 |
|---|---|---|
issue_id |
任意 | 対象 issue。省略時は default issue を使う |
session_log_path |
任意 | Codex / Claude Code など外部会話ログの path |
client |
任意 | 現在の Brain(通常は省略可。clientInfo.name から自動設定される。明示時は override) |
notes |
任意 | Brain または人間が追加する補足 |
戻り値: handover_prepare と同じ schema を返す。
required_action は原則として handover_resume を指す。
handover_resume役割: 同一 Brain のセッション再起動後に、読むべき snapshot を探して再開する。
パラメータ:
| 名前 | 必須 | 説明 |
|---|---|---|
issue_id |
任意 | 対象 issue。省略時は default issue または project latest を使う |
handover_id |
任意 | 特定 snapshot を指定して再開する |
client |
任意 | 現在の Brain(通常は省略可。session.brain_id を自動使用。明示時は override) |
created_from |
任意 | 履歴候補の開始日時 |
created_to |
任意 | 履歴候補の終了日時 |
処理:
handover_id があれば handover_read(handover_id=...) を優先するissue_id があれば issue 単位の snapshot 候補を収集するissue_id がなく default issue があれば default issue 単位の snapshot 候補を収集するissue_id も default issue もない場合だけ、project latest または project-level snapshot 候補を収集するtarget_brain=current Brain の exact match を優先し、該当がなければ target_brain=any にフォールバックするneed_input または candidate_handovers を返すwork_observe() 相当の現在状態を取得するrequired_action に work_start(issue_id=...) を含めるissue_id または default issue によって issue スコープが決まった場合、候補が 0 件でも project-level snapshot にはフォールバックしない。
ユーザーが特定 issue を指定したのに別スコープの snapshot が返ると、再開対象を誤るリスクがあるためである。
handover_transfer役割: 現在の Brain から別 Brain に作業を渡す snapshot を作成する。
内部的には handover_prepare(target_brain=...) を呼ぶ。
target_brain は必須とする。
target_brain の値が分からない場合は handover_list_brains() でレジストリを確認する。
Brain は取得した一覧を fuzzy match し、1件一致なら自動指定、複数候補ならユーザーに確認する。
パラメータ:
| 名前 | 必須 | 説明 |
|---|---|---|
target_brain |
必須 | 引き継ぎ先 Brain。不明な場合は handover_list_brains() で確認する |
issue_id |
任意 | 対象 issue。省略時は default issue を使う |
session_log_path |
任意 | 外部会話ログの path |
client |
任意 | 現在の Brain(通常は省略可。session.brain_id を自動使用。明示時は override) |
notes |
任意 | 引き継ぎ先への補足 |
戻り値: handover_prepare と同じ schema を返す。
required_action は原則として引き継ぎ先 Brain での handover_accept(source_brain=client, issue_id=...) を指す。
handover_accept役割: 別 Brain から渡された作業を受け取る。
内部的には handover_resume と近い処理を使ってよい。
ただし accept は「別 Brain から責任を受け取る」というユーザー意図を表すため、resume とは別 tool として定義する。
パラメータ:
| 名前 | 必須 | 説明 |
|---|---|---|
source_brain |
任意 | 引き継ぎ元 Brain |
issue_id |
任意 | 対象 issue。省略時は default issue または候補検索 |
handover_id |
任意 | 特定 snapshot を指定して受け取る |
client |
任意 | 現在の Brain(通常は省略可。session.brain_id を自動使用。明示時は override。未設定かつ session.brain_id も不明な場合は target_brain=any の候補だけを優先できる) |
処理:
handover_id があればそれを読むsource_brain があれば source で候補を絞るtarget_brain=current Brain の exact match を優先し、該当がなければ target_brain=any にフォールバックするtarget_brain=any の候補だけを優先し、特定 Brain 宛て snapshot を勝手に受け取らないneed_input を返し、引き継ぎ元 Brain での handover_transfer を required_action に含める。client 明示値が session.brain_id と異なる場合は、その不一致が分かる identity hint を含めるcandidate_handovers を返し、勝手に選ばないwork_observe() 相当の現在状態を取得するwork_start(issue_id=...) を required_action に含めるPhase 1 では、accept した事実を別の永続イベントとして保存することまでは行わない。
将来 Phase 2 で受領履歴を index.json または work record に記録する。
handover_prepare役割: 現在の作業状態から Handover snapshot を作成する。
パラメータ:
| 名前 | 必須 | 説明 |
|---|---|---|
issue_id |
任意 | 対象 issue。省略時は default issue を使う |
session_log_path |
任意 | Codex / Claude Code など外部会話ログの path |
client |
任意 | 現在の Brain(通常は省略可。session.brain_id を自動使用。明示時は override) |
target_brain |
任意 | 引き継ぎ先 Brain。不明時は any |
notes |
任意 | Brain または人間が追加する補足 |
client は snapshot の METADATA.source_brain および戻り値の source_brain に使用する。
target_brain は snapshot の METADATA.target_brain および戻り値の target_brain に使用する。
処理:
issue_id があれば issue 情報を読むissue_id が省略され、work session に default issue があればそれを使うwork_observe() 相当の作業状態を取得するsession_log_path または既存 session_log から会話ログ参照情報を取得するsession_logs/handover/ に保存するindex.json を更新するhandover_id / snapshot_file / handoff_prompt を返す戻り値:
{
"status": "ok",
"summary": "handover snapshot を作成しました",
"data": {
"handover_id": "issue-49-20260603T094500Z-codex",
"snapshot_file": "session_logs/handover/issue-49_20260603T094500Z_codex.md",
"latest_file": "session_logs/handover/issue-49_latest.md",
"handoff_prompt": "次の Brain は...",
"mode": "core",
"issue_id": 49,
"source_brain": "codex",
"target_brain": "claude_code",
"required_action": {
"tool": "handover_read",
"args": {"issue_id": 49}
}
}
}
handover_read役割: 既存の Handover snapshot を取得する。
パラメータ:
| 名前 | 必須 | 説明 |
|---|---|---|
issue_id |
任意 | issue 単位で最新 snapshot を取得する |
handover_id |
任意 | 特定 snapshot を取得する |
handover_id が指定された場合はそれを優先する。
issue_id のみ指定された場合は issue 単位の latest snapshot を返す。
両方省略時は project latest snapshot を返す。
latest snapshot が存在しない場合、handover_read は自動で handover_prepare を実行しない。
need_input を返し、required_action で handover_prepare を促す。
過去の snapshot を遡って探す場合は、先に handover_list を呼び、取得した handover_id を指定して handover_read を呼ぶ。
戻り値:
{
"status": "ok",
"summary": "handover snapshot を取得しました",
"data": {
"handover_id": "issue-49-20260603T094500Z-codex",
"snapshot_file": "session_logs/handover/issue-49_latest.md",
"snapshot": "...markdown...",
"handoff_prompt": "次の Brain は...",
"issue_id": 49,
"required_action": {
"tool": "work_observe",
"args": {}
}
}
}
latest が存在しない場合の戻り値:
{
"status": "need_input",
"summary": "issue #49 の handover snapshot はまだ作成されていません",
"data": {
"issue_id": 49,
"handover_id": null,
"snapshot_file": null,
"required_action": {
"tool": "handover_prepare",
"args": {"issue_id": 49},
"reason": "最新 handover が存在しないため、先に作成が必要です"
}
}
}
handover_list役割: 保存済み Handover snapshot の履歴一覧を取得する。
Handover は 1 件だけではなく、append-only な履歴として保存する。
latest は最新を読むための便利な入口であり、履歴の代替ではない。
パラメータ:
| 名前 | 必須 | 説明 |
|---|---|---|
issue_id |
任意 | issue 単位で絞り込む |
client |
任意 | source_brain で絞り込む |
source_brain |
任意 | client と同義。lifecycle command との名前合わせ用 alias |
target_brain |
任意 | target_brain で絞り込む |
created_from |
任意 | created_at の開始日時(ISO 8601) |
created_to |
任意 | created_at の終了日時(ISO 8601) |
limit |
任意 | 取得件数。省略時は 20 |
戻り値:
{
"status": "ok",
"summary": "handover snapshot の履歴を取得しました",
"data": {
"count": 2,
"handovers": [
{
"handover_id": "issue-49-20260604T101500Z-claude_code",
"snapshot_file": "session_logs/handover/issue-49_20260604T101500Z_claude_code.md",
"issue_id": 49,
"issue_title": "Handoverの仕組みをつくる",
"source_brain": "claude_code",
"target_brain": "codex",
"created_at": "2026-06-04T10:15:00Z",
"is_latest": true,
"summary": "Handover 仕様書レビュー反映後の再開資料"
}
],
"suggested_action": {
"tool": "handover_read",
"args": {"handover_id": "issue-49-20260604T101500Z-claude_code"}
}
}
}
handover_list_brains役割: work_session_start で接続実績のある Brain 一覧を返す。handover_transfer の target_brain を確認するために使う。
パラメータ: なし
処理:
{project_root}/session_logs/brain_registry.json を読む戻り値:
{
"status": "ok",
"summary": "登録済み Brain の一覧を返しました",
"data": {
"brains": [
{"brain_id": "claude-code", "last_seen": "2026-06-10T05:00:00Z"},
{"brain_id": "openai-codex-cli", "last_seen": "2026-06-10T04:50:00Z"}
]
}
}
Brain の選択フロー(Brain 層の責務):
ユーザーが「〇〇に引き継いで」と指示した場合、Brain は以下のフローで target_brain を決定する:
handover_transfer を実行openai-codex-v2 か openai-codex-cli のどちらですか?」)work_session_start を呼んでもらえますか?」brain_registry.json は work_session_start 呼び出し時に自動で upsert される。
Handover snapshot は repo-local の session_logs/handover/ に保存する。
session_logs/ は既存方針どおりローカル運用物として Git 管理対象外にする。
Handover は append-only な履歴として保存する。
latest は履歴中の最新 snapshot を指すための convenience pointer であり、履歴そのものを置き換えない。
ファイル名生成ルール:
issue_id あり:
handover_id: issue-{issue_id}-{YYYYMMDDTHHMMSSZ}-{client}
session_logs/handover/issue-{issue_id}_{YYYYMMDDTHHMMSSZ}_{client}.md
session_logs/handover/issue-{issue_id}_latest.md
issue_id なし:
handover_id: project-{YYYYMMDDTHHMMSSZ}-{client}
session_logs/handover/project_{YYYYMMDDTHHMMSSZ}_{client}.md
session_logs/handover/project_latest.md
index:
session_logs/handover/index.json
Phase 1 では latest.md を物理コピーにするか index.json 参照にするかは実装判断としてよい。
Brain-facing な handover_read の挙動は、どちらの実装でも変えない。
index.json には少なくとも次のメタデータを保存する。
handover_idsnapshot_fileissue_idissue_titlesource_braintarget_braincreated_atis_latestsummaryhandover_list はこの index.json を読み、履歴候補を返す。
issue_id の扱いissue_id は任意とする。
ただし issue がある場合を主経路とする。
動作:
issue_id が指定されていれば issue-{id} 系の snapshot として保存するissue_id が省略され、work session に default issue があればそれを使うissue_id も default issue もなければ project handover として保存するproject handover は、探索・調査中など issue に紐づかない作業の引き継ぎに使う。
| capability | 責務 |
|---|---|
session_log |
会話ログの読み取り・抽出・一次証跡 |
session_log summarize |
会話ログ単体の構造化 snapshot |
work_record |
成果物中心の作業記録 |
issue |
作業対象・背景・議論の外部状態 |
handover |
issue / work / session / optional memory を束ねた再開用 artifact |
| memory MCP | 長期記憶・横断検索・想起の補助 |
handover_prepare は session_log summarize の置き換えではない。
session_log summarize は会話ログ単体を扱い、handover_prepare は issue / work / session を束ねて次の Brain 用の再開文書を作る。
mem0 / Supermemory など外部 memory MCP は、Handover の正本ではない。
外部 memory MCP の役割:
Butler の役割:
handover_prepare / handover_read / handover_list 提供Brain が memory MCP を直接組み合わせる運用にはしない。
手順が Brain ごとに分散し、Codex / Claude Code 間で Handover 品質が揺れるためである。
| 状況 | 方針 |
|---|---|
| 外部 memory MCP がない | core mode で続行 |
session_log が読めない |
issue / work 状態 / notes だけで snapshot を作る |
issue_read が失敗する |
issue 情報なしで続行し、BLOCKED / PENDING に記録 |
issue_id がなく default issue もない |
project handover として保存 |
session_logs/handover/ がない |
初回実行時に作成 |
handover_id が見つからない |
error を返し、可能なら latest snapshot の候補を返す |
| issue latest snapshot がない | need_input を返し、required_action に handover_prepare(issue_id=...) を含める |
| project latest snapshot がない | need_input を返し、required_action に handover_prepare を含める |
handover_resume(issue_id=...) で該当 issue の候補がない |
project-level snapshot へフォールバックせず need_input を返す |
handover_accept で候補がない |
need_input を返し、引き継ぎ元 Brain での handover_transfer を促す |
| 履歴が複数あり選択が必要 | handover_list の結果を提示し、handover_id 指定での handover_read を促す |
Phase 1 の完了条件:
handover_suspend(issue_id=49) で同一 Brain 再開用の Handover snapshot を生成できるhandover_resume(issue_id=49) で最新 snapshot と現在状態を取得できるhandover_transfer(issue_id=49, target_brain="claude_code") で target_brain 付き snapshot を生成できるhandover_accept(source_brain="codex", issue_id=49) で target_brain / source_brain を考慮して snapshot を受け取れるhandover_prepare(issue_id=49) で Handover snapshot を生成できるhandover_read(issue_id=49) で最新 snapshot と HANDOFF_PROMPT を取得できるhandover_list(issue_id=49) で保存済み snapshot の履歴候補を取得できるhandover_list(created_from=..., created_to=...) で current project 内の期間指定履歴を取得できるhandover_list(issue_id=None, client="claude_code") で current project 全体から source_brain による履歴候補を取得できるhandover_read(handover_id=...) で過去の特定 snapshot を取得できるHANDOFF_PROMPT のセクション名が厳密に扱われるsession_logs/handover/ に snapshot が保存されるhandover_list の候補に issue_title が含まれ、ユーザーが候補を識別できるneed_input と required_action: handover_prepare を返すテストがあるhandover_list の候補を返し、Butler が自動選択しないことを確認するテストがあるhandover_resume / handover_accept で候補が複数ある場合に自動選択しないテストがあるhandover_resume / handover_accept で候補が 1 件の場合に snapshot と現在状態を返すテストがあるhandover_accept で候補が 0 件の場合に need_input と handover_transfer 推奨を返すテストがあるhandover_accept で client 省略時に特定 Brain 宛て snapshot を勝手に返さないテストがあるhandover_resume / handover_accept で target_brain exact match 優先、該当なしなら any フォールバックするテストがあるhandover_resume(issue_id=...) で該当 issue の候補がない場合に project-level snapshot へフォールバックしないテストがあるhandover_read / handover_resume / handover_accept の required_action は Brain が原則として確認なく実行する、という仕様と MCP instructions があるhandover_list に混入しないテストがあるtests/test_mcp_facade.py で Brain-facing tool 公開を確認できる| 項目 | 状態 | 備考 |
|---|---|---|
issue_id は任意。ただし issue がある場合を主経路 |
推奨採用 | Phase 1 方針 |
初期 handover_prepare は deterministic template |
推奨採用 | LLM 要約は Phase 2 |
lifecycle command は suspend / resume / transfer / accept |
推奨採用 | ユーザー向け入口 |
Brain identity(source_brain / target_brain)の自動設定 |
確定 | work_session_start 時に MCP clientInfo.name を session.brain_id に保存。client は override 用として残す。Claude Code の実測値は "claude-code" |
Brain レジストリ(brain_registry.json) |
確定 | work_session_start 呼び出し時に brain_id を upsert。handover_list_brains() で参照 |
handover_accept の受領イベント永続記録 |
未確定 | Phase 2 候補 |
| snapshot 最大長 200-300 行以下 | 暫定 | 実運用で調整 |
handover_prepare が session_log のどの範囲を読むか |
未確定 | mark 以降を主候補とする |
| enhanced mode で memory MCP 検索結果をどう混ぜるか | 未確定 | Phase 2 |
latest.md を物理コピーにするか index 参照にするか |
未確定 | Brain-facing 挙動は同じ |
| 本文全文検索や semantic search を入れるか | 未確定 | Phase 2 以降 |
| 複数 project 横断検索を扱うか | 未確定 | Phase 2 以降 |
最初に見るファイル:
butler/mcp_facade.pybutler/maids/session_log_worker.pybutler/maids/session_log_summary_worker.pybutler/work_record_backend.pybutler/issue_backend.pytests/test_mcp_facade.pytests/test_session_log_non_llm_maid.pyPhase 1 では、新規 backend として butler/handover_backend.py を作る案が自然である。
MCP facade は薄く保ち、snapshot 生成、保存、index 更新、latest 解決は backend 側に寄せる。