免責事項: planner-shellはあくまで使い捨てのテスト環境を作成する目的のもので、本番環境での使用は想定していません。AIが生成したコマンドを実行するため、環境を破壊する可能性もあります。自己責任でご利用ください。
開発背景はこちらの記事に記載しています。
planner-shell "install superset and load sample data" —— それだけで、インストールからサンプルデータのロードまで全自動
ChatGPTがコマンドを提案するレベルを超えて、実際にLinuxコマンドを実行し、状態を維持し、失敗を自己修復する—— これが planner-shellです。
LangChainを基盤とし、Planner × Executor の分離アーキテクチャ、永続的PTYシェル、ハイブリッドPlan Cache(SQLite/SingleStore)、そして 自動修復機能を備えたAIエージェントです。
この記事では、READMEの情報を元に解説します。
核となるコンセプト: 計画 × 記憶 × 実行
planner-shellは3つの要素で構成されています:
-
Planner(計画エージェント):
Tavily検索で最新情報を得て、LLMに実行プランを生成させます。 -
Cache(記憶):
生成したプランを保存し、ベクトル検索とBM25全文検索を組み合わせたハイブリッド検索によって将来の同一または類似タスクで再利用可能にします。これにより、LLMトークンとTavilyトークンの両方を節約できます。 -
Executor(実行エージェント):
永続的PTYシェルでプランを1つずつ実行し、結果を観察し、エラーになれば自律的に修正します。
✅ 主な特徴(機能一覧)
コアアーキテクチャ
-
永続的PTYシェル: 単一のbashセッションを維持。
cd、export、環境変数が全て次のコマンドに引き継がれる - 3相実行: Planner(Tavily検索 + LLM計画作成)、Auditor(セキュリティ監査)、Executor(シェル実行)を完全分離
- 人間の介入: 各コマンド実行前に承認・編集・スキップ・却下が可能
- スマートキャッシュ: SQLite(またはSingleStore)バックエンド、30日TTL、Hybrid検索
- URL/Markdownモード: 外部ドキュメントから直接手順を実行(Tavily検索無効)
-
自動承認モード:
AUTO_APPROVE=1で完全非対話実行(使用は要注意) - 自動修復: コマンド失敗時にAIがエラーを分析し、Tavilyで修正案を検索して再計画
- 定期メンテナンス: キャッシュの期限切れエントリを起動時に自動クリーンアップ
- スレッド管理: バックグラウンドエージェントスレッドを協調的にクリーンアップ
高度な機能
- Hybrid検索: BM25(FTS5)とOpenAI埋め込みベクトル検索を融合
- 出力要約: 長いコマンド出力を先頭/末尾10行で truncate(LLMトークン節約)
-
非対話フラグ自動付与:
systemctl→--no-pager、apt→-y - 端末状態復元: PTY操作後に terminal echo/canonical mode を確実に回復
-
DPkgロックタイムアウト: aptコマンドに
-o DPkg::Lock::Timeout=60を注入(同時実行対応)
プロンプトカスタマイズ
- 外部プロンプトファイル: markdownファイルでplanner、executor、distillエージェントのプロンプトをカスタマイズ
-
必須変数: 各プロンプトには特定のプレースホルダが必要(例は
prompts/ディレクトリ参照) - バリデーション: 起動時に不足変数を検出しエラーメッセージ表示
-
ディレクトリ上書き:
PROMPT_DIR環境変数でカスタムプロンプトディレクトリ指定 - デフォルトフォールバック: カスタムプロンプトが見つからない場合は組み込みデフォルト使用
🚀 クイックスタート
planner-shellをインストールします
git clone https://github.com/bobo3977/planner-shell.git && cd planner-shell && chmod +x setup.sh && ./setup.sh
環境変数ファイルを編集します
vi .env
.envファイルの記載項目(最低限 OPENAI_API_KEY があれば動きます)
# 任意: Tavily検索(無くても動作するがWeb検索機能が使えない)
# TAVILY_API_KEY=your_tavily_key_here
# 必須: どちらか1つ(OpenAI or OpenRouter)
OPENAI_API_KEY=your_openai_key_here
# または
# OPENROUTER_API_KEY=your_openrouter_key_here
# 任意: LLMモデル選択
# OPENAI_MODEL=gpt-5-nano # デフォルト
# OPENROUTER_MODEL=openai/gpt-5-nano # デフォルト
# 任意: 埋め込めモデル選択
# EMBEDDING_MODEL="text-embedding-3-small" # デフォルト
# 任意: 埋め込みの無効化 (ベクトル検索はスキップ)
# DISABLE_EMBEDDINGS=0 # 1で無効化
# 任意: 自動承認モード(automation/CI用)
# AUTO_APPROVE=1 # true/yes も可
# 任意: SingleStoreキャッシュバックエンドを使用する場合
# PLAN_CACHE_BACKEND=singlestore # デフォルト: sqlite
# SINGLESTORE_HOST=<SINGLESTORE_HOSTNAME>
# SINGLESTORE_PORT=3306(Standard Workspace) or 3333(Starter Workspace)
# SINGLESTORE_USER=<USER_NAME>
# SINGLESTORE_PASSWORD=<PASSWORD>
# SINGLESTORE_DATABASE=<DATABASE_NAME>
# PLAN_CACHE_TTL_DAYS=30 # デフォルト: 30
Tavily APIは必須ではなく、なければLLMの内部知識オンリーで動作します。ただ、Web検索なしだとハルシネーションが起きやすいので、後述のURLやマークダウンを参照するモードでのみ使用するのがよさそうです。
トークン料金を節約するためにgpt-5-nanoという小さなモデルをデフォルトにしていますが、賢いモデルを選択するとより快適に使えます。
alias設定を反映させます
source ~/.bashrc
📖 使い方
対話モード
引数なしで実行:
planner-shell
タスク、URL、またはマークダウンファイルのパスを入力:
🤖 Enter your task (or 'exit'): Install Apache Superset and load sample data
動作は3相です:
-
計画フェーズ(Planner): 詳細な実行計画を作成
- Tavily APIキーがあれば検索(URL/Markdownモードを除く)
- Tavilyなし: LLMの内部知識のみ(機能制限)
- 生成したプランは30日TTLでキャッシュ
- 類似キャッシュがあれば提示(Hybrid検索、URL/Markdown入力時はスキップ)
-
監査フェーズ(Auditor): セキュリティ監査
- 生成されたプランを危険なコマンドでスキャン
-
rm -rf、ディスク破壊、特権昇格などのパターンを検出 - 警告を表示し、ユーザーに注意を促す
-
実行フェーズ(Executor): コマンドをステップバイステップで実行
- 各コマンドは実行前に承認が必要
- 出力はリアルタイムでストリーミング
- AIが結果を分析して次に進む
- 失敗時は自動修復(Tavily検索で修正案を探索)
承認オプション
コマンド提案時に:
-
Y/Enter- そのまま実行 -
N- 却下しAIにフィードバック -
edit- vim/nanoで編集 -
skip- スキップ(現在のコマンドを実行しない、成功扱い) -
finish- 残りのステップを完了済みとしてマークし、計画実行を早期終了 -
quit- 即時終了してシェルに戻る
特殊コマンド
タスク入力プロンプトでこれらのコマンドが使える:
-
exit/quit- エージェント終了 -
clear/cc- 全キャッシュクリア -
cleanup/expired- 期限切れ(30日以上前)のキャッシュ削除 -
refine/distill/distil- 実行ログからクリーンな計画を抽出- 前回のタスク実行後に自動表示されるヒントを参照
🌐 URL/Markdownモード
外部ドキュメントから手順を直接実行:
🤖 Enter your task: https://example.com/install-guide
# または
🤖 Enter your task: ./setup.md
処理内容:
- URLをフェッチまたはマークダウンファイルを読込
- 提供されたコンテンツのみを使用して計画作成(Tavily検索は無効)
- 記載された手順をステップバイステップで実行
- 公式ドキュメントの手順をそのまま automation したい場合に最適
自動承認モード(非対話)
自動化・CI/CD向け:
# URLまたはマークダウン指定
AUTO_APPROVE=1 planner-shell https://example.com/guide
AUTO_APPROVE=1 planner-shell ./setup.md
# 対話タスクに自動承認
AUTO_APPROVE=1 planner-shell
- 全てのコマンドが自動承認(
Y連打equivalent) - プロンプト表示なし
自動承認モードはヒューマン・イン・ザ・ループを完全に排除するので危険です。使用するときは十分ご注意ください。
⌨️ Ctrl+C 操作
- シングルCtrl+C: 現在のコマンドを中断(PTYにSIGINT送信)、計画は継続
- ダブルCtrl+C(2秒以内):実行全体を中止してプロンプトに戻る
🏗️ アーキテクチャ詳細
永続的PTYシェル
Pythonの pty モジュールで単一のbashプロセスを管理。これにより:
-
状態永続化:
cdしたディレクトリ、exportした環境変数、シェル変数が全て次のコマンドに継承 -
対話的コマンド対応:
sudo、ssh、passwdなどTTYrequiredなコマンドも正常動作 - シグナル転送: Ctrl+C (SIGINT) が子プロセスに正しく配送される
- リアルタイム出力: コマンド出力が生成と同時に端末にストリーミング
シェルは PersistentShell クラスで実装され、クラッシュ時には自動再起動する。
3エージェントシステム
Planner Agent (PlannerAgent):
- 詳細な番号付き実行計画を作成
- Tavily APIキーがあれば最新情報を検索(URL/Markdownモードを除く)
- Tavilyなし: LLMの内部知識のみ(機能低下)
- キャッシュルックアップの順序: ハッシュ完全一致 → Hybrid検索 → 新規生成
- URL/markdown入力時: hybrid search をスキップし完全一致のみ
- システムプロンプトで厳命: 具体的なコマンドのみ、OS認識
Executor Agent (ExecutorAgent):
- 事前構築計画を受信
-
PersistentShellToolを用いてコマンドを1つずつ実行 - 各コマンド後に出力を分析
- 失敗時はエラーメッセージを元に別コマンドを生成して再試行
- 無限ループ防止のため最大反復回数制限
Auditor (ルールベース監査モジュール):
- 生成された実行計画を事前にスキャン
-
config/dangerous_patterns.txtに定義された危険なコマンドパターンを正規表現で検出 -
rm -rf、ディスク破壊、権限変更、フォークボムなどを検出 - 警告メッセージを表示し、ユーザーに注意を促す
SQLite バックエンド(デフォルト)
SQLitePlanCache は3つのルックアップ戦略:
- 完全一致 (O(1)): 正規化タスクテキストのSHA-256ハッシュ
-
Hybrid検索: (URL/markdown入力時はスキップ)
- FTS5 BM25: タスク説明の全文検索
-
ベクトル: OpenAI
text-embedding-3-smallの埋め込みをJSONで保存 - ベクトル類似度: Pythonによるコサイン類似度計算
- 重み付きブレンド (α=0.6, ベクトル重視、
ALPHAで調整可能)
-
TTL: 30日満了(
ttl_daysで調整可能)
キャッシュは自動:
- データベース破損を検知しバックアップ後再構築
- FTS5仮想テーブルと同期トリガーを維持
- URL/markdownベースタスクは埋め込み生成をスキップ(
skip_embedding)
SingleStore バックエンド(オプション)
SingleStorePlanCache は同機能をクラウドネイティブ分散SQLで提供:
-
保存:
task_hash,task_text,plan,timestamp,embedding(Vector型) カラム - 全文検索: SingleStore BM25 関数(FULLTEXT index)
- ベクトル検索: ベクトル埋め込みのドット積
- Hybridスコア: RRF(Reciprocal Rank Fusion:逆順位融合)
- TTL: 調整可能(デフォルト30日)
- 高可用性: SingleStore の分散アーキテクチャの恩恵
設定: PLAN_CACHE_BACKEND=singlestore を設定し、環境変数で接続情報を提供(設定セクション参照)。
注意: SingleStoreバックエンドには singlestoredb Python パッケージと稼働中のSingleStoreデータベースが必要。
⚙️ 設定
環境変数
プロジェクトルートに .env ファイルを作成し、以下の環境変数を設定します。
必須API Key
どちらか必須(両方設定されている場合はOPENROUTERを優先)
OPENROUTER_API_KEY="your-key-here"
OPENAI_API_KEY="your-key-here"
オプションAPI Key
# Optional: Tavily search (agent works without it, but with reduced capabilities)
TAVILY_API_KEY="your_tavily_key_here"
LLM Settings
# Model selection (defaults shown)
OPENROUTER_MODEL="openai/gpt-5-nano"
OPENAI_MODEL="gpt-5-nano"
LLM_TEMPERATURE=0 # 0 = deterministic, higher = more creative
Embedding Settings
# Embedding model for vector search (default: text-embedding-3-small)
EMBEDDING_MODEL="text-embedding-3-small"
# Disable embeddings entirely (skip vector search)
DISABLE_EMBEDDINGS=0 # set to 1 to disable
Cache Settings
# Backend: "sqlite" (default) or "singlestore"
PLAN_CACHE_BACKEND=sqlite
# SQLite database path (default: .plan_cache.db)
PLAN_CACHE_DB_PATH=.plan_cache.db
# Hybrid search tuning
PLAN_CACHE_ALPHA=0.6 # 0 = pure FTS, 1 = pure vector
PLAN_CACHE_MAX_CANDIDATES=3 # Max candidates to return
PLAN_CACHE_SCORE_THRESHOLD=0.0 # Minimum score threshold
# Cache TTL (days)
PLAN_CACHE_TTL_DAYS=30
SingleStore Settings (when PLAN_CACHE_BACKEND=singlestore)
SINGLESTORE_HOST=localhost
SINGLESTORE_PORT=3306
SINGLESTORE_USER=agent
SINGLESTORE_PASSWORD=
SINGLESTORE_DATABASE=inst_agent
Agent Behavior
# Maximum consecutive failures before stopping (default: 10)
MAX_CONSECUTIVE_FAILURES=10
# Maximum Tavily searches per command execution (default: 10)
MAX_SEARCHES_PER_COMMAND=10
# Auto-approve all commands (no interactive prompts)
AUTO_APPROVE=0 # set to 1 to enable
Timeouts (seconds)
PLAN_TIMEOUT=120 # Plan generation timeout
EXECUTE_TIMEOUT=1800 # Plan execution timeout (30 minutes)
Shell Settings
# Maximum output bytes before truncation (default: 10 MB)
MAX_OUTPUT_BYTES=10485760
# Periodic cleanup interval in seconds (default: 1 hour)
CLEANUP_INTERVAL=3600
コマンドライン引数
# 対話モード(引数なし)
planner-shell
# タスク指定
planner-shell "Install Redis"
# URL指定
planner-shell https://example.com/guide
# markdown指定
planner-shell ./setup.md
AUTO_APPROVE=1が設定されていれば自動承認、なければ確認プロンプトが出る。
AUTO_APPROVE=1 planner-shell "Install Redis"
refine / distill / distil — 実行ログからクリーンな計画を抽出
refine コマンドは、前回実行したタスクの実行ログ(全コマンドとその成功率)を分析し、失敗したコマンドや重複を除去して、クリーンな実行計画を再生成します。
主な使用場面:
- 試行錯誤の過程(retry、手動介入)を含む実行ログから、成功パスのみを抽出
- 生成されたプランを定期的にアップデートしてチームで共有
実行例:
# 1. まず任意のタスクを実行(例: Nginxインストール)
🤖 Enter your task: Install Nginx with SSL
# 2. 実行完了後、自動表示されるメッセージ:
# "Type 'refine' to distil a clean plan from the successful path."
# 3. refine を実行
🤖 Enter your task: refine
動作フロー:
- 前回の実行ログを取得(全ステップ数、成功/失敗数)
- LLM に送信し、以下のルールで plan を再構成:
- 失敗したコマンドは除外
- 同一目的での retry は最終成功コマンドのみを残す
- 冪等性を高めるために existence-check(
[ -f ... ]、systemctl is-activeなど)を追加 - 非対話フラグ(
-y、DEBIAN_FRONTEND=noninteractiveなど)を自動付与
- 元のプランとの diff を表示
- 編集(
edit)してから保存するオプションも可能 - 保存時は元のキャッシュエントリを 上書き
セキュリティ・設計上の特徴:
-
distill_plan()メソッドはPlannerAgentに実装(main.py:2800) - 実行ログは
ExecutionStepオブジェクト(command, exit_code, output, succeeded)のリストとして保持 - セッション状態
_last_execution_logは実行直後に自動設定されるため、refineは即座に利用可能 - 保存時は
_save_distilled_to_cache()で URL/Markdown モードも考慮してキャッシュを更新
注意:
-
refineは 前回のタスク実行後にのみ使用可能。何も実行していないとエラー - トークン節約のため、LLMには実行コマンドとその結果の組み合わせだけを送信していて、ログ全体を送信しているわけではない
edit <index|task> / ed <index|task> — キャッシュの計画を編集
キャッシュに保存されている実行計画を編集できます。vim/nano などのエディタで計画内容を修正し、更新した計画をキャッシュに保存し直せます。
使用例:
# インデックス番号で編集
🤖 Enter your task: edit 3
# タスクテキスト(部分一致)で検索して編集
🤖 Enter your task: edit nginx
動作フロー:
- 指定されたインデックスまたはタスクテキストでキャッシュから計画を取得
- 現在の計画を表示
-
$EDITOR(デフォルト: vim)で編集画面を開く - 編集後に保存すると、更新された計画をキャッシュに再保存
-
task_textは編集後の計画の最初の行(最大200文字)に更新 - URL/Markdownモードの計画も編集可能
-
- 編集後の計画を即座に実行可能
セキュリティ対策:
-
$EDITORのパスは安全な文字のみ許可(英数字・スラッシュ・ドット・ハイフン・アンダースコア) - エディタ起動は
shell=Falseで実行(シェルインジェクション防止) - 編集後の計画は元の plan の update として保存(同一タスクの上書き)
編集後のオプション:
編集が完了すると、計画のプレビューが表示され、そのまま実行するか、さらに編集するか選択できます。編集後の計画は自動的にキャッシュに保存されるため、次回から修正済み計画が利用されます。
注意:
- 編集元の計画がURL/Markdownに基づく場合、編集後も同じURL/fileが関連付けられます
- 編集内容をキャッシュに保存しない選択も可能(編集画面で何も変更しなかった場合)
📦 Ollama サポート
planner-shell はローカル LLM として Ollama を利用できます。README の設定例は次のとおりです。
# Ollama (ローカル LLM) 用設定
OLLAMA_BASE_URL=http://localhost:11434 # Ollama がリッスンしている URL
OLLAMA_MODEL=llama2 # 使用したいモデル名(例: llama2, mistral)
OLLAMA_BASE_URL と OLLAMA_MODEL を .env に記載すれば、OpenAI 系の API キーが無くてもローカルでモデルを呼び出すことが可能です。インターネット接続が制限された環境でも planner-shell をフル活用できます。
🏖️ Sandbox モード
安全にコマンドを実行するための コンテナ型サンドボックス が組み込まれています。README の「Containerized Execution (Sandbox Mode)」に記載されているように、次のようにオプションを付与して実行します。
planner-shell "install superset" --sandbox docker # Docker コンテナで実行
planner-shell "install superset" --sandbox podman # Podman (rootless) で実行
SANDBOX_TYPE 環境変数でデフォルトのバックエンドを設定でき、SANDBOX_IMAGE でベースイメージを指定できます(例: ubuntu:24.04)。サンドボックスはホストシステムへの影響を最小限に抑え、破壊的なコマンドも安全にテストできます。
⚡ y! トリガー(自動承認モード)
対話プロンプトで y! と入力すると、以降の全コマンドが自動承認 (AUTO_APPROVE=1) になります。README の「y! trigger」セクションでも触れられていますが、実際の挙動は次の通りです。
- プラン生成後、コマンド承認プロンプトが表示される。
-
y!と入力 → 自動承認モードが有効化され、次のコマンドからは手動でYを押す必要がなくなる。 - 必要に応じて
AUTO_APPROVE=0に戻すことはできませんが、セッションを終了すればリセットされます。
この機能は CI/CD パイプラインや大量の自動テストで便利ですが、ヒューマン・イン・ザ・ループが完全に無くなるため、危険なコマンドの実行に注意が必要です。
🔬 節約機能
- キャッシュウォームアップ: 「Nginxインストール」のような反復タスクのプラン作成は初回のみLLM呼び出し、2回目はキャッシュヒット(即時実行)
- Hybrid検索: ハッシュによるキャッシュミス時に類似計画を提案、APIコスト削減(URL/markdown時はスキップ)
- Embedding API: ベクトル検索はOpenAI API 呼び出しが必要。埋め込み作成はタスク入力文字とプランの件名のみなのでコスト小。API利用不可時はFTSのみにフォールバック
- スレッドタイムアウト: エージェントスレッドはデフォルト30分(executor)でタイムアウト
-
定期クリーンアップ: 期限切れエントリは起動時自動削除。
cleanupコマンドで即時解放可能
注意点
planner-shellは設計のコンセプト上、冪等性の担保(毎回同じ処理を実行することの保証)が弱いです。
キャッシュされた実行プラン自体は毎回同じものを使いますが、Excecutorエージェントによる実行時には、プランを参照しつつ毎回コマンドをつどつど生成します。そのため、プランと完全に同じコマンドが実行されるわけではありません。
LLMに対するプロンプトで冪等性を担保するように強く指示していますが、限界があります。
冪等性の担保が重要な場合には、Ansibleなどを使うのが望ましいでしょう。