対象 issue: #50 Brain の issue 操作抽象化: gitea 依存を隠す
この文書は検討用メモであり、現時点では正本ではない。
目的は、#50 で行った議論の経緯と判断理由を失わずに、実装可能な変更案として整理することである。
#50 の当初の問題意識は、Brain が issue 操作をする際に Gitea の詳細を知りすぎていることだった。
現状の呼び出しは次のような形になっている。
gitea_get_issue(owner="akira", repo="butler2", issue_id=50)
gitea_add_comment(owner="akira", repo="butler2", issue_id=50, note="...")
この形では、Brain が次を知る必要がある。
akira であることbutler2 であること当初の理想は次のようなものだった。
read_issue(50)
comment_issue(50, "...")
つまり、Brain は「現在のプロジェクトの issue #50」を扱うだけでよく、Butler が project_root や実行コンテキストから backend / owner / repo を解決する。
議論を進める中で、#50 の本質は Gitea を隠すこと自体ではなく、次の目的にあると整理した。
Brain のトークン消費を低レベル作業から切り離し、判断・設計・会話に集中させること。
これは butler2 の最上位目的と同じである。
そのため、issue 操作だけを抽象化しても不十分である。
Brain が実務で繰り返し行う低レベル作業には、issue 操作だけでなく、ソース管理操作も含まれる。
現状、Claude Code や Codex は Butler の git 機能を積極的に使わず、明示的に依頼しない限り直接 git コマンドを実行しがちである。
たとえば「今回の変更をコミットして」と言われた場合、Brain は次のように進みやすい。
git status
git diff
git add ...
git commit -m ...
git log -1
一方、Butler の git_commit はすでに status / add / commit / log の一部をまとめて実行できる。
それでも使われないのは、機能がないからではなく、Brain から見える操作体系が既知の Git 操作体系に負けているためである。
最初の案では、既存の git_* / gitea_* を内部 adapter として残し、Brain 向けに抽象ツールを追加することを考えた。
read_issue
list_issues
comment_issue
close_issue
workspace_status
workspace_diff
workspace_commit
workspace_push
workspace_pull
この案の良い点は、Git / Gitea という vendor 名を隠せることである。
しかし議論の中で、これだけでは不十分だと分かった。
workspace_commit では弱いのかworkspace_commit のような名前は一見抽象的だが、commit という語が残っている。
Claude Code / Codex は Git のコマンド体系を強く知っている。
ツール一覧に抽象ツールがあっても、ユーザーが「コミットして」と言うと、既知の git commit 手順へ引き寄せられる可能性が高い。
また、workspace_push / workspace_pull も同様に Git の語彙を残している。
この時点で、次のように判断した。
Git を便利に包むだけでは、Brain は Git 直接操作へ戻る。
Brain の世界から Git 語彙をできるだけ消す必要がある。
VCS という語も避ける判断次に、localVcs_ / hostVcs_ / LVCS / HVCS のような名前も検討した。
ローカル履歴管理とホスト側管理を分ける発想自体はよい。
しかし、VCS という語も LLM の内部では Git / SVN / Mercurial などの既存バージョン管理に接続されやすい。
今回の目的は、Brain に「Git の別名」を渡すことではない。
Brain が Git を思い出さずに済む、Butler 独自の作業体系を見せることである。
そのため、Brain-facing な名前では VCS も避ける。
内部設計では、次のような名前を使ってよい。
GitAdapter
LocalVcsAdapter
GiteaIssueAdapter
ただし Brain に公開するツール名や Instructions では、これらの backend 名を前面に出さない。
議論の結果、次の方向に寄せる。
Git を抽象化するのではなく、Butler 独自の「作業記録システム」仕様を作り、その現行 backend として Git をラップする。
Brain から見ると、Butler は Git の便利ラッパーではない。
Butler は「作業を観測し、記録し、共有し、issue と結び、締める」ための作業記録システムである。
概念名の候補:
Butler Work Record System
BWR
Brain-facing な操作名は work_* 系に寄せる。
ユーザーから Brain への指示も、できるだけ Git 語彙ではなく Butler の作業記録語彙へ寄せる。
| 従来の言い方 | 推奨する言い方 | Butler 呼び出し |
|---|---|---|
| 今回の変更をコミットして | 今回の作業を記録して | work_record |
| 未コミットの変更を見て | 未記録の作業内容を確認して | work_observe / work_diff |
| 差分を見て | 未記録の差分を見て | work_diff |
| git status 見て | 作業場の状態を確認して | work_observe / work_status |
| git log 見て | 作業記録の履歴を見て | work_history |
| push して | 記録済みの作業を共有先に反映して | work_publish |
| pull して | 共有先から最新状態を取り込んで | work_update |
| ブランチ作って | 新しい作業系列を作って | work_new_line |
| ブランチ切り替えて | 作業系列を切り替えて | work_switch_line |
| merge して | 別の作業系列を取り込んで | work_merge_line |
実際の指示例:
今回の作業を記録して。記録メッセージは「work record 仕様の初版を追加」。
未記録の作業内容を確認して、問題なければ作業記録を残して。
記録済みの作業を共有先に反映して。
共有先から最新状態を取り込んでから、未記録の作業内容を確認して。
単に work_status / work_diff / work_record を並べるだけでは、Brain が「次に何を呼ぶべきか」を判断し続ける必要がある。
そのため、Brain には個別操作の集合ではなく、Butler 上の作業ライフサイクルとして見せる。
主導線は次の 6 ステップにする。
work_session_start(project_root)
work_start(issue_id?)
work_observe()
work_record(message, issue_ids?)
work_publish()
work_finish()
補助的に、以下の低レベル観測ツールを残してよい。
work_status
work_diff
work_history
ただし、Brain に最初に使わせる入口は work_observe() に寄せる。
work_observe() の役割work_observe() はこの設計の中核である。
Brain は作業中に「まず状態を見る」ために git status や git diff を実行しがちである。
これを work_observe() 1 回で置き換える。
work_observe() は単なる status + diff ではなく、Brain の次アクションを誘導する司令塔として設計する。
{
"work_state": "dirty",
"default_issue": 50,
"changed_files": ["src/mcp_facade.py"],
"diff_summary": "work_observe ツールを追加",
"unpublished_records": 0,
"next_recommended": {
"tool": "work_record",
"reason": "未記録の変更があります",
"required_args": ["message"]
}
}
work_state は clean_with_active_issue とし、work_finish を推奨する。
{
"work_state": "clean_with_active_issue",
"default_issue": 50,
"changed_files": [],
"diff_summary": null,
"unpublished_records": 0,
"next_recommended": {
"tool": "work_finish",
"reason": "作業は記録・公開済みです。締め処理を行ってください",
"required_args": []
}
}
work_state は clean_idle とする。
「ただ状態確認しただけ」の場合にも毎回 issue 開始を強制しないよう、next_recommended は null にし、選択肢を available_next で示す。
{
"work_state": "clean_idle",
"default_issue": null,
"changed_files": [],
"diff_summary": null,
"unpublished_records": 0,
"next_recommended": null,
"available_next": ["work_start"]
}
next_recommended: null は「今すぐ何かしなければならない状態ではない」ことを示す。
Brain が次の作業を開始したい場合は available_next の中から選ぶ。
{
"work_state": "recorded_unpublished",
"default_issue": 50,
"changed_files": [],
"unpublished_records": 2,
"next_recommended": {
"tool": "work_publish",
"reason": "未公開の記録があります",
"required_args": []
}
}
{
"work_state": "uninitialized",
"default_issue": null,
"changed_files": null,
"next_recommended": {
"tool": "work_session_start",
"reason": "セッションが初期化されていません",
"required_args": ["project_root"]
}
}
required_args には必須引数のみ列挙し、issue 紐付け方針は policy_hint で補足する。
{
"work_state": "dirty",
"default_issue": null,
"changed_files": ["src/mcp_facade.py"],
"diff_summary": "work_observe ツールを追加",
"unpublished_records": 0,
"next_recommended": {
"tool": "work_record",
"reason": "未記録の変更があります",
"required_args": ["message"],
"policy_hint": "issue_ids に紐付け先を指定するか、issue_ids=[] で紐付けなしにしてください"
}
}
重要な点:
next_recommended は文字列ではなく構造化フィールドにするrequired_args を返し、次に Brain が何を埋めるべきか分かるようにするwork_state は機械判定しやすい enum 的な値にする想定する work_state:
uninitialized
clean_with_active_issue
clean_idle
dirty
recorded_unpublished
dirty_and_unpublished
blocked
unknown
work_record() の役割work_record() は、現在の作業を Butler の作業記録として保存する操作である。
Phase 1 では message を必須にする。
work_record(message="docs: 作業記録インターフェース案を追加")
issue_ids は任意で受ける。
work_record(message="fix: 本題の修正")
work_record(message="fix: 派生バグも修正", issue_ids=[50, 123])
work_record(message="chore: コード整理", issue_ids=[])
issue_ids の意味:
work_start(issue_id) の default issue を使うissue_ids=[] は issue 紐付けなしを明示する現行 Git backend での実装イメージ:
git status --porcelain
git add -A
git commit -m <message>
git log -1
ただし Brain-facing の description では、Git の詳細を書かない。
悪い説明:
git add -A と git commit を実行します。
良い説明:
現在の作業内容を、後から参照できる永続的な作業記録として保存します。
将来は message 省略時に、Butler が差分から記録メッセージ候補を生成する案がある。
work_record()
ただし、Butler 自身が diff を要約するには AI コンポーネントが必要になる。
Phase 1 では候補なしの need_input か、単に message 必須にとどめる。
work_session_start() の役割work_session_start(project_root) は、Butler セッションを初期化する操作である。
work_session_start(project_root="/home/akira/develop/butler2")
この呼び出しにより、Butler が project_root から backend / owner / repo を自動解決し、以降の work_* / issue_* 呼び出しにそのコンテキストが引き継がれる。
以降のすべての呼び出しからパラメータが消える:
work_session_start(project_root="/home/akira/develop/butler2")
# 以降はパラメータなし
work_observe()
issue_read(18)
work_record("fix: バグ修正")
既存の Butler session_start との関係:
Butler にすでに session_start の仕組みがある場合は、そこへの統合を検討する。
work_session_start は session_start のラッパーまたは同一操作として実装してよい。
work_observe() がセッション未初期化時に next_recommended: "work_session_start" を返すことで、Brain が適切な起動順序へ誘導される(セクション 9 参照)。
project_root などのセッションコンテキストは MCP connection 単位で保持する。
MCP の接続は 1 Brain = 1 接続を前提としており、接続ごとに独立してコンテキストを持てば、複数プロジェクト・複数 Brain・並行セッションが混線しない。
session_id を返して呼び出し側に管理させる方式は Brain 側の実装が複雑になるため採用しない仕様への明記事項: 「project_root および default_issue は MCP connection スコープに保存される。接続が切れるとリセットされる。」
FastMCP が connection-local state を素直にサポートしているか確認が必要。
FastMCP の実装によっては、接続ごとの状態保持が難しい場合がある。
その場合の代替案として、Brain には見せない「内部 session key」方式を用意する。
代替案(内部 session key):
- work_session_start が内部でセッションキーを生成・保存する
- Brain には session_id を返さない(Brain は意識しなくてよい)
- 各 work_* / issue_* ツールは FastMCP の Context や
接続/リクエスト単位で取得できる識別子でセッションを特定する
実装時の注意: 「接続元 IP + timestamp」のような不安定な識別子は使わない。
FastMCP に接続単位またはリクエスト単位で取れる識別子(Context オブジェクト等)がないかを先に調べる。
どちらの実装になるかは FastMCP の検証結果で決める。Brain-facing API は変わらない。
work_start() の役割work_start(issue_id) は、作業セッションの default issue を設定する。
重要なのは、これはロックではないという点である。
work_start(issue_id=50)
この呼び出しは「この作業は基本的に issue #50 に関係する」という既定値を Butler セッションに記録するだけである。
実際の開発では、作業中に派生 issue が生まれたり、複数 issue に関係する記録が発生する。
そのため work_start が 1 issue に作業を縛ってはいけない。
設計原則:
work_record は issue_ids 省略時だけ default issue を使うissue_create / issue_read などの issue 操作は default issue に関係なく呼べるissue_ids=[] で紐付けなしを明示できるwork_publish() の役割work_publish() は、記録済みの作業を共有先へ反映する。
現行 Git backend での実装イメージ:
git status --porcelain
git branch --show-current
git push
ただし、Brain-facing では push という語を前面に出さない。
説明例:
記録済みの作業を共有先へ反映します。
未記録の作業が残っている場合は、先に work_record を促します。
work_finish() の役割work_finish() は作業を締めるための抽象操作である。
Brain は作業終了時に次を毎回考えがちである。
これを Butler がチェックリストとして持つ。
Phase 1 では自動実行ではなく、提案と確認にとどめる。
想定する挙動:
work_record を促すwork_publish を促すPhase 2 では、自動実行オプションを検討してよい。
Issue 側も Gitea を隠す。
Phase 1 の Brain-facing 候補:
issue_read(issue_id, last_n_comments?)
issue_create(...)
issue_comment(issue_id, body)
補助候補:
issue_list
issue_update
issue_close
issue_read は last_n_comments をサポートする。
これは #50 初期コメントで出ていた「最後だけ読む」「最後から指定した数件だけ読む」という要望に対応し、トークン削減にも直結する。
issue_read(50)
issue_read(50, last_n_comments=3)
内部実装では既存の gitea_get_issue / gitea_get_issue_comments を使ってよい。
ただし Brain には owner / repo / backend を要求しない。
抽象ツールを追加するだけでは不十分である。
Brain が参照できるツール一覧に git_* / gitea_* が残っていると、LLM は既知のコマンド体系へ引き寄せられる可能性が高い。
したがって、Brain 向け MCP と Worker 向け MCP の公開面を分ける。
mcp_facade.py
├─ brain_tools.py ← work_* / issue_* のみ公開
└─ worker_tools.py ← git_* / gitea_* を含む内部向けツール公開
Brain が見えるツール:
work_session_start
work_start
work_observe
work_record
work_publish
work_finish
issue_read
issue_create
issue_comment
Brain が見えないツール:
git_commit
git_push
git_pull
gitea_get_issue
gitea_add_comment
gitea_update_issue
gitea_close_issue
位置づけ:
本体: Brain tools には work_* / issue_* だけを出す
保険: MCP Instructions で work_observe から始めるよう誘導する
継続誘導: work_observe / work_finish の戻り値で next_recommended を返す
MCP Instructions に「git_* / gitea_* を直接呼ばない」と書くことは有効だが、それだけでは弱い。
本命は、Brain 用セッションから低レベルツールを見えなくすることである。
MCP サーバーは接続時に Instructions を Brain へ渡せる。
これを使って、Brain がどの LLM であっても Butler の作業体系に入れるようにする。
Instructions 例:
Butler は作業記録システムです。
セッション開始時は work_session_start(project_root) を呼んでください。
作業開始時や状態確認時は、まず work_observe() を呼んでください。
work_observe() は現在の作業状態と次に推奨する操作を返します。
作業内容の保存には work_record() を使ってください。
記録済み作業の共有には work_publish() を使ってください。
issue 操作には issue_read / issue_create / issue_comment を使ってください。
低レベル backend 操作は Brain 向けには公開されません。
また、各 tool description にも役割を明記する。
work_session_start の description 例:
Butler セッションを初期化します。最初に一度だけ呼んでください。
project_root を指定することで、以降の work_* / issue_* 呼び出しで
owner / repo / backend の指定が不要になります。
work_observe の description 例:
セッション開始時、または次に何をすべきか確認したい時に呼ぶツールです。
現在の作業状態、未記録変更、未公開記録、default issue、次に推奨する操作を返します。
Phase 1 では、deterministic に実装できる範囲を優先する。
work_session_start(project_root)
work_start(issue_id?)
work_observe()
work_record(message, issue_ids?)
work_publish()
work_finish()
issue_read(issue_id, last_n_comments?)
issue_create(...)
issue_comment(issue_id, body)
git_*
gitea_*
work_record() のメッセージ自動生成work_finish() の自動実行すべての work_* / issue_* ツールは次の共通形式で返す。
{
"status": "ok | error",
"summary": "人間が読める結果の要約",
"data": { ... }
}
エラー時は status: "error" とし、error_code に意味コードを入れる。
{
"status": "error",
"error_code": "conflict | auth_failed | not_found | policy_blocked | backend_error",
"summary": "何が起きたかの説明",
"hint": "Brain が次に取るべき対処の候補"
}
exit code 1 などの backend 詳細は Brain に見せない。
hint により Brain が次の操作を判断できるようにする。
Phase 2 では、より Butler らしく自律的な支援を検討する。
work_record() のメッセージ自動生成work_finish(auto=True) のような自動実行オプションwork_new_line / work_switch_line / work_merge_line#50 の元 issue に記載があった将来ユースケース。
issue_read で取得した issue を Maid へ渡す委任フローとして設計する予定現時点の実装優先順位は次の通り。
work_session_start(project_root) を追加し、project_root から backend / owner / repo を解決するwork_observe() を追加し、next_recommended を構造化して返すwork_record(message, issue_ids?) を追加するwork_start(issue_id?) で default issue を設定するissue_read / issue_comment を Gitea を隠した名前で追加するwork_publish() / work_finish() を追加するwork_session_start → work_observe の導線にするこの順序なら、まず Brain が git status / git commit へ戻る最大の導線を work_observe / work_record で置き換えられる。
#50 の完了条件は、当初の Gitea 抽象化から次のように拡張する。
git_* / gitea_* が出ていないwork_* / issue_* が出ているwork_session_start(project_root) でセッションが初期化できるwork_observe() が作業状態と next_recommended を構造化して返すwork_observe() がセッション未初期化時に next_recommended: "work_session_start" を返すwork_record(message, issue_ids?) で現在の作業を記録できるwork_start(issue_id?) の default issue が work_record に反映されるissue_ids=[] により issue 紐付けなしの記録が可能issue_read(issue_id, last_n_comments?) により最後 N 件コメント取得ができるwork_session_start → work_observe の導線があるBrain 向け利用ガイド・公開 API 例では、git_* / gitea_* の直接呼び出し例を除去し、work_* / issue_* の抽象インターフェースに統一する。
internal / Worker 向けのリファレンスや adapter 実装仕様では、git_* / gitea_* の説明を引き続き許容する。これらは実装者向けの情報であり、Brain に見せる文書ではないため。
#50 は、単なる「Gitea 依存を隠す」issue ではなくなった。
現在の設計テーマは次である。
Brain に Git/Gitea の代替ツールを渡すのではなく、Brain の世界から Git/Gitea という概念を消し、Butler 独自の「作業記録」「共有」「課題管理」操作として扱えるようにする。
そのための中核が、次の主導線(6ステップ)である。
work_session_start → work_start → work_observe → work_record → work_publish → work_finish
特に work_observe() は、Brain が直接 git status / git diff へ向かう導線を置き換える司令塔である。
ここが Butler 作業記録システムの入口になる。
git_* / gitea_* は捨てない。
ただし Brain からは見えない internal / worker adapter として扱う。
これにより、現行 backend として Git / Gitea を使いつつ、Brain から見える世界は Butler 独自の作業記録システムへ切り替わる。