diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 00000000..e830ac74 --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,140 @@ +# Insomnia アーキテクチャ + +## プロジェクトの目的 + +複数の LLM エージェント(Pod)が独立プロセスとして並行動作し、自律的に分業・統括できるエンジン。シングルエージェントの LLM リグとの差別化は、タスクを別 Pod に委譲し結果を集約する**オーケストレーション**を LLM 自身に行わせる点にある。 + +## 設計原則 + +### 層の分離と方向性 + +各クレートは下位層に依存し、上位層を知らない。上位層は下位層の API を丸ごと隠蔽せず、制約が必要な部分だけをラップして提供し、それ以外は下位層を直接使わせる。 + +### 宣言した層が解決する + +ある層が構成を宣言として受け取ったなら、その解決もその層の責務。マニフェストに `provider.kind = "anthropic"` と書いた以上、`ProviderConfig` → `LlmClient` の変換は insomnia が行う。逆に llm-worker が `LlmClient` trait だけを受け取るのは正しい — llm-worker はプロバイダの選択を宣言として受け取っていないから。 + +### 概念の追加は不在が問題になってから + +概念を先に作らない。「これがないと今の問題が解けない」と言えるまで追加しない。拡張ポイントはドキュメントに記録するが、実装はしない。 + +### 最小の構造化で最大の自由度 + +insomnia は環境再現・コンテナ管理・VCS 統合などを自身の責務としない。Pod が動くホストの fs 上で活動する主体を提供し、それにコンテキストを与えてスポーンさせられる仕組みを付与する。構築する環境はユーザー次第。 + +## Pod + +独立したエージェントの実行単位。llm-worker の Worker をラップし、マニフェストによる宣言的構成とディレクトリスコープを加える。 + +- 1 Pod = 1 プロセス = 1 セッション +- マニフェスト(TOML)から完結構築できる +- scope で書き込み可能なパスを制限(読み取りは自由) +- 独立した socket サーバーを持ち、Client (TUI / GUI) が接続して操作する + +### 実行ループ + +``` +Client → Method::Run { input } + → Worker: user message 追加 → LLM リクエスト + → LLM 応答: text → Client に Event::TextDelta で配信 + → LLM 応答: tool_use → Interceptor (pre_tool_call) → Tool 実行 → Interceptor (post_tool_call) → Hook + → tool result を履歴に追加 → 次の LLM リクエスト + → ターン終了: Hook (on_turn_end) → Client に Event::RunEnd +``` + +### Interceptor と Hook の分離 + +- **Interceptor**: Worker の内部制御フロー。context / history の直接操作が可能。Pod の内部機構(compaction トリガー、notification 注入)が使う。外部に公開しない +- **Hook**: Pod の公開 API。read-only のサマリ情報のみを受け取り、制御フロー判断(continue / skip / abort / pause)を返す。将来スクリプト言語に安全に公開可能 +- 実行順序: Interceptor → Hook。Interceptor が context を整えた結果を Hook が観測する + +### コンテキスト管理 + +- **Prune**: 古い tool result の content を除去(summary は残す)。`pre_llm_request` で毎回判定 +- **Compact**: 履歴全体を要約して圧縮。`input_tokens` が閾値を超えたとき、`PreRequestAction::Yield` で Worker を一旦中断し、Pod 側で要約 → 新セッションとして再開 +- サーキットブレーカー: compact が3回連続失敗したら無効化 + +## Protocol + +Pod の制御・監視に使う JSONL ベースのメッセージプロトコル。トランスポートに依存しない。 + +- **Method** (Client → Pod): `Run` / `Notify` / `Resume` / `Cancel` / `Shutdown` / `GetHistory` +- **Event** (Pod → Client, broadcast): `TurnStart` / `TurnEnd` / `TextDelta` / `ToolCallStart` / `ToolCallArgsDelta` / `ToolCallDone` / `ToolResult` / `Usage` / `RunEnd` / `Error` / `History` / `Notification` / `Shutdown` +- リクエストとレスポンスの紐付けはしない。Pod の状態遷移(イベント)を見れば何が起きているか分かる +- イベントは全リスナーに broadcast +- 操作の競合は先勝ち(run 中に別の run → AlreadyRunning エラー) + +## マニフェストとファクトリ + +### PodManifest + +Pod の宣言的構成。TOML で記述。 + +```toml +[pod] +name = "agent" +pwd = "/abs/path" + +[model] +scheme = "anthropic" +model_id = "claude-sonnet-4-20250514" + +[worker] +instruction = "$insomnia/default" +max_tokens = 4096 + +[[scope.allow]] +target = "/abs/path" +permission = "write" +``` + +### PodFactory: カスケード設定 + +マニフェストを手書きせず、4 層のカスケードで `PodManifest` を組み立てる: + +1. **ビルトインデフォルト** — `manifest::defaults` の定数値 +2. **ユーザー manifest** — `$XDG_CONFIG_HOME/insomnia/manifest.toml` +3. **プロジェクト manifest** — `.insomnia/manifest.toml`(cwd から上方向に探索) +4. **プログラマティック overlay** — CLI / GUI / spawn 時のインライン指定 + +マージ規則: スカラーは上層が置換、Map はキー単位マージ、`scope.allow` / `scope.deny` は union。全パスは絶対パスのみ。 + +### Instruction とプロンプト資産 + +`worker.instruction` はファイル参照。3 層の prefix addressing でプロンプト資産を解決: + +- `$insomnia/...` — バイナリ同梱(`resources/prompts/`、`include_dir!` で埋め込み) +- `$user/...` — `$XDG_CONFIG_HOME/insomnia/prompts/` +- `$workspace/...` — `/.insomnia/prompts/` + +テンプレートは minijinja で評価。`{% include "$insomnia/common/tool-usage" %}` のようにプロンプト間で参照可能(prefix なしの include は現在のファイルからの相対解決)。 + +レンダリング結果の末尾に scope summary と AGENTS.md(あれば)がコード側で固定付加される。ユーザーテンプレートからはこれらに触れない。 + +## Scope + +Pod が操作できるファイルパスの制御。 + +- `allow` ルールで読み取り・書き込みを許可、`deny` ルールで制限 +- effective permission = allow - deny +- `recursive = false` で直下のみに制限可能(summary に `[non-recursive]` マーカー) +- scope 排他: Pod 間の write 衝突は scope lock file (`$XDG_RUNTIME_DIR/insomnia/scope.lock`) で検出。scope 分譲(spawn 時に譲渡、終了時に返却)の記録にも使う + +## セッション永続化 + +- append-only JSONL ログ。1 エントリ = 1 行 +- SHA-256 チェーンでエントリの整合性を保証 +- ログ再生で Worker の状態を完全復元(スナップショット不要) +- Compact 時に新セッションを開始し、旧セッションへのリンクを保持 + +## 組み込みツール + +| ツール | 概要 | +| ------ | ------------------------------------------ | +| Read | ファイル内容の読み取り | +| Write | ファイルの新規作成・上書き | +| Edit | 既存ファイルの部分編集(事前 Read が必要) | +| Glob | ファイル名パターンマッチ | +| Grep | ファイル内容の正規表現検索 | + +すべて scope の permission チェックを経由。`ScopedFs` が書き込み制限を、`Tracker` がセッション内のコンテンツハッシュ追跡を行う。 diff --git a/docs/compaction.md b/docs/compaction.md new file mode 100644 index 00000000..250475f4 --- /dev/null +++ b/docs/compaction.md @@ -0,0 +1,259 @@ +# コンテキスト圧縮 (Compaction) + +長時間実行エージェントのコンテキストウィンドウ管理。 +Prune(段階的な出力除去)と Compact(履歴の要約置換)の2層で対処する。 + +--- + +## 全体像 + +``` +[各リクエストの合間] +LLM リクエスト + ↓ +PruneHook (pre_llm_request) + → 古い ToolResult の content を除去(summary は残す) + → リクエストコンテキストのみ操作、history 本体は不変 + ↓ +CompactInterceptor (pre_llm_request) ← safety net + → input_tokens > request_threshold なら Yield + → Worker が WorkerResult::Yielded で正常終了 + ↓ +Pod::handle_worker_result + → persist_turn(旧セッションに記録) + → compact() → resume() + +[ターンの合間 — Pod::run 完了後] +Controller::try_post_run_compact ← proactive + → input_tokens > post_run_threshold なら compact() (best-effort) +``` + +--- + +## Prune + +古い ToolResult の `content` を `None` に置換し、`summary` だけ残す。 + +### 設計判断 + +- **条件付き実行**: 推定トークン節約量が `min_savings` を超えた場合のみ。KV キャッシュの無駄な無効化を避ける +- **リクエストコンテキストのみ操作**: history 本体は変更しない。Prune 状態を Pod が保持し、LLM リクエスト構築時に反映する +- **冪等**: `content: None` のアイテムはスキップ + +### ToolOutput の構造 + +```rust +pub struct ToolOutput { + pub summary: String, // 常に残る。「何をしたか」の最低限の情報 + pub content: Option, // Prune 対象。None に置換するだけ +} +``` + +`Tool::execute()` は `Result` を返す。 +`From` で自動変換可能(小さい出力は summary のみ、大きい出力は summary + content)。 +巨大な出力はフレームワークの責務外。ツール側がファイルに退避し、content に見取り図を置く。 + +--- + +## Compact + +### 用語 + +- **run = turn**: 同じ概念。1 ユーザープロンプト → 完了までの単位 +- **リクエスト**: 1 turn 内で投げる個別の LLM 呼び出し。ツール使用で 1 turn に複数リクエストが発生する +- **リクエストの合間**: 1 turn 内、次の LLM リクエストを投げる前の地点 +- **ターンの合間**: turn が完了して次の turn を待つ状態 + +### トリガー(2段階の閾値) + +1. **ターンの合間 (Controller)**: `try_post_run_compact()` で `input_tokens > post_run_threshold` → best-effort +2. **リクエストの合間 (CompactInterceptor)**: `pre_llm_request` で `input_tokens > request_threshold` → `PreRequestAction::Yield` + +**ターンの合間が proactive (小さい閾値)**: +turn が完了した地点はタスクの自然な区切り。ここで先を見越して早めに compact する。 +マニフェストの `compact_threshold` が対応。 + +**リクエストの合間は safety net (大きい閾値)**: +turn 内部でリクエストの合間にチェックするのは「暴走的に膨張した場合のみ止める」用途。 +マニフェストの `compact_request_threshold` が対応。通常は発動しない。 + +**両閾値は manifest で個別指定する**。過去の設計では 9/8 倍で自動導出していたが、 +比率に根拠がなかったため廃止。両方が `Option` で、片方だけの設定も可能 +(そちら側のチェックだけが有効になる)。 + +### Yield の仕組み + +`PreRequestAction::Yield` は Worker のターンループを正常終了させる汎用プリミティブ。 +Worker は Compact を知らない。「Interceptor が yield と言ったので抜ける」だけ。 +Pod が Yielded を検知して compact → resume する。 + +``` +PreRequestAction::Yield → WorkerResult::Yielded → Pod が compact → resume +PreRequestAction::Cancel → WorkerError::Aborted → エラー +``` + +### 安全機構 + +- **サーキットブレーカー**: 3回連続 compact 失敗で無効化 (`CompactState::disabled`) +- **Thrash 検出**: compact 直後に再び閾値超過 → `PodError::CompactThrash` +- **Yield 前の永続化**: `persist_turn` を compact の前に実行。失敗しても旧セッションにデータが残る + +### セッション管理 + +compact は fork と同じ構造。旧セッションを保全し、新 SessionId で圧縮後のセッションを開始。 + +``` +旧セッション (abc-123): + [...entries...] → Outcome::Yielded (interrupted=true) ← そのまま残る + +新セッション (def-456): + [SessionStart { compacted_from: (abc-123, entryN.hash), history: [要約 + 直近] }] → ... +``` + +`SessionStart` に `forked_from` / `compacted_from` フィールドで出自を追跡可能。 + +--- + +## compact 後の history 構造 + +全て system message(`Item::Message { role: System }`)として注入。 + +``` +[system prompt] ← 不変 +[system: 構造化要約] ← compact worker の出力 +[system: auto-read ファイル群] ← read_required の結果 +[system: リファレンス一覧] ← reference の結果 +[直近 N トークン分の生の会話] ← pruned 状態で保持 +``` + +### 直近の保護: トークンベース + +ターン単位ではなくトークン数ベースで直近の会話を保護する。 + +ターン単位の問題: 自走エージェントは1ターン内で多数のリクエストを回す。 +1ターンが膨大に長くなり、保護量がターン長に依存してしまう。 +トークン数ベースなら、ターンの長さに関わらず一定量の直近コンテキストが保持される。 + +```toml +[compaction] +compact_threshold = 80000 # ターンの合間 (proactive) +compact_request_threshold = 90000 # リクエストの合間 (safety net) +retained_tokens = 8000 # 直近保護トークン数 (Prune 済みで計測) +auto_read_budget = 8000 # compact worker の mark_read_required 合計上限 +compact_worker_max_input_tokens = 50000 # compact worker 自身の累計入力トークン上限 +``` + +### Auto-Read とリファレンス + +2段階のファイル参照: + +- **Read** (`mark_read_required`): 全文/範囲指定でコンテキストに注入 +- **Reference** (`add_reference`): 「読んだことがある」とだけ伝える。必要なら再読要求 + +auto-read の system message: +``` +[Auto-read file: src/main.rs:42-142] +fn main() { + let config = Config::load(); + ... +} +``` + +リファレンスの system message: +``` +[Referenced files — read before compaction, contents not included] +- src/config.rs (read during task setup) +- tests/integration_test.rs (read during test implementation) +Use read_file to access current contents if needed. +``` + +auto-read も通常の history 内 system message なので、将来の Prune/Compact 対象になる。 + +--- + +## compact worker + +要約生成とファイル選定を行う使い捨て Worker。ツールなし・1リクエストの現行実装から、 +ツール付きマルチターンに改善する。 + +### ツール + +``` +read_file(path, offset?, limit?) — ファイルを読んで判断する +mark_read_required(path, offset?, limit?) — auto-read 対象として指定 +add_reference(path) — リファレンスとして追加 +write_summary(text) — 構造化要約を出力/上書き(最後の呼び出しが採用) +``` + +### フロー + +1. Pod が `tools::Tracker::recent_files(5)` で最近触られたファイルを抽出(デフォルトリファレンス) +2. compact worker にプロンプトとして渡す: + - pruned history(summary only、arguments/reasoning 除去) + - デフォルトリファレンスの一覧 +3. compact worker が自律的に: + - read_file で各ファイルを読み、必要性を判断 + - mark_read_required / add_reference で指定 + - write_summary で構造化要約を出力(呼び直し可) +4. ターン終了時に write_summary 未呼び出し or read_required 空(かつファイル操作履歴がある場合)→ 追加プロンプトで促す + +### 構造化要約の要件 + +**目的**: auto-read でコードは別途載せるので、要約は意思決定・コンテキスト・方向性の記録に特化する。 + +**含めるべき内容:** +1. 何を、なぜやったか — 意思決定の記録。具体的な型名・関数名で言及 +2. ユーザーの指示・フィードバックの原文 — ニュアンス保持。重要なもののみ +3. 発生した問題と解決策 — 同じ轍を踏まない +4. 今どこにいて次に何をするか — compact 前後の一貫性 + +**含めないもの:** +- コードの全文(auto-read が担う) +- 変更の diff(git がある) +- 中間のやりとりの詳細(最終結論だけ) + +### 要約フォーマット(5セクション、1000-2000 トークン目安) + +``` +## Completed Tasks +### (タスク名) +- 完了した作業(具体的な型名・ファイル名で) +- 注意点 / 発覚した事実 + +## Active Task +### (タスク名) +- 目標 +- 現状(何が済んで何が未着手か) +- 次のステップ + +## Key Decisions +- (判断内容) — (理由) + +## User Directives +- 「(ユーザー発言の原文)」 — 重要な指示・フィードバックのみ + +## Current Work +(直前に何をしていたか。2-3行) +``` + +セッション中に複数タスクが切り替わっている前提で設計。 +完了タスクは簡潔に(注意点・事実のみ)、進行中タスクは十分な詳細で。 + +--- + +## 設計の背景 + +### Claude Code からの知見 + +Claude Code の compaction 実装(`docs/ref/claude-code-compaction.md`)を参考に、以下を取り入れた: + +- **条件付き Prune** (`min_savings`): KV キャッシュ無効化コストとのトレードオフ。`clear_at_least` パターン +- **サーキットブレーカー**: 連続失敗の無限ループ防止 +- **2段階のファイル参照** (Read vs Referenced): Claude Code は compact 後に「Referenced file」(内容省略)と「Read」(内容保持)を区別する。サイズと必要性で判断 +- **要約の具体性**: 型名・関数名・ユーザー発言原文を保持。抽象的な要約は役に立たない + +### 設計原則との対応 + +- Prune は Worker 層の拡張。新しい trait は不要(設計原則3) +- Compact は Pod 層の制御。Worker は Yield を知るが Compact は知らない +- CompactInterceptor は Decorator パターンで HookInterceptor をラップ。既存の Hook 機構を壊さない diff --git a/docs/plan/llm_providers.md b/docs/plan/llm_providers.md new file mode 100644 index 00000000..5ec405e9 --- /dev/null +++ b/docs/plan/llm_providers.md @@ -0,0 +1,94 @@ +# LLM プロバイダサポート方針 + +## Context + +INSOMNIA が利用する LLM プロバイダとその認証方式を決める。従量 API 課金の心理的負担を避け、定額サブスク枠(ChatGPT Codex / Ollama Cloud)を活かせる構成にする。 + +詳細な現状調査は `docs/ref/llm-provider-landscape.md` と `docs/ref/llm-pricing-2026-04.md` を参照。 + +## 決定事項 + +### 第一級サポート(専用アダプタ) + +| プロバイダ | scheme | 認証 | 用途 | +|---|---|---|---| +| **Ollama** | scheme/anthropic 流用(v0.14+ `/v1/messages`) | なし(ダミー) | ローカル + `:cloud` サフィックスでクラウド中継。`localhost:11434` で統一 | +| **Codex OAuth 経路** | scheme/openai_responses | `~/.codex/auth.json` | ChatGPT の定額枠を利用 | +| **Anthropic API** | scheme/anthropic | API key | 従量課金経路のみ | + +Ollama は独自 scheme を作らず `scheme/anthropic` を base_url 差し替えで流用。`/v1/chat/completions` は stream+tools バグ (#9092) のため使わない。`cache_control` / `tool_choice` / `metadata` / `count_tokens` は Ollama 非対応のため送らない。 + +### 二次サポート(OpenAI 互換共通枠) + +`provider_kind: openai_compatible` + `base_url` + API key + `available_models[]` の汎用アダプタ 1 本で以下を収容: + +- OpenRouter +- xAI (Grok) +- Groq +- Together / Fireworks / DeepInfra +- BLACKBOX +- 任意の OpenAI 互換エンドポイント + +### 非サポート + +- **Claude Pro/Max OAuth 経路** — Anthropic が 2026-01-09 にサーバ側でブロック、2026-02-19 に ToS で第三者ツール経由を明文禁止。リスクが第一級機能に見合わない +- **`claude -p` CLI fork** — 同様に採用しない。実装しない + +## 根拠 + +- **Codex OAuth は Codex CLI 互換**: Codex CLI は Apache-2.0、openai/codex #8338 で OpenAI 社員が fork 自由と明言、service terms に名指し禁止なし +- **Anthropic API は従量だが代替なし**: Pro/Max OAuth 封鎖後、Claude 系を使うには API key 経路のみ +- **Ollama は `:cloud` で透過**: `ollama signin` で Ed25519 鍵登録後、`localhost:11434` 経由でクラウドモデルが使える。ローカルデーモンが署名付き中継 +- **OpenAI 互換は汎用アダプタ 1 本**: ルーター系は後追いで数を増やしやすい宣言型設計、実装コスト最小 + +## 実装原則 + +- 認証ストアを読むアダプタ(`~/.codex/auth.json` 等)は **llm-worker 直下に置かず上位層に配置**。llm-worker は低レベル基盤に留める方針(`feedback_llm_worker_scope.md`)と整合 +- モデル列挙は **auto_discover と宣言型の両輪**。Ollama は `/api/tags` で自動、OpenAI 互換枠は `available_models[]` を宣言 +- UI のプロバイダ選択肢も第一級 → 二次の優先順位で並べる +- **`ollama launch insomnia` 対応を視野に**、env 注入(`ANTHROPIC_BASE_URL` / `OPENAI_BASE_URL` 等)で起動設定を受け入れる作り + +## 機能方針 + +### プロバイダ側の高次ツールは使用しない + +`web_search` / `code_interpreter` / `computer_use` / Live Search 等はプロバイダ依存を避けるため不採用。insomnia の自前 Tool 層で統一。fallback や routing もワーカー側で管理するため OpenRouter の `transforms` / `provider routing` も使わない。 + +### 必須 capability + +- **tool calling**: parallel tool calls 含めて必須 +- **reasoning**: 必須。内部表現は共通型 `ReasoningControl { effort, budget_tokens }` に正規化し、scheme アダプタで各社形式(`reasoning.effort` / `thinking.budget_tokens` / `reasoning_format`)に投影。DeepSeek の `reasoning_content` 別フィールドは Thinking block として正規化 + +### Capability は 5 軸 + +`ModelCapability` として以下を持つ: + +1. tool calling(parallel 可否含む) +2. structured output (`json_object` / `json_schema` / Grammar) +3. reasoning(effort / budget_tokens / 出力形式) +4. vision +5. prompt caching(下記) + +### Prompt caching は 2 値 + +``` +prompt_caching: CacheStrategy, // Explicit { max_breakpoints } / Auto +``` + +- **Explicit**: Anthropic。scheme が `cache_control` マーカー挿入 +- **Auto**: それ以外全部。scheme は何もしない(サーバ側自動 prefix と「何もしない」は呼び出し側から同一) +- 「安定 prefix を先頭に寄せる」正規化は両方共通で適用 + +### Streaming + +現状の `BlockStart / BlockDelta / BlockStop / BlockAbort` + `DeltaContent::{Text, Thinking, InputJson}` 設計を維持。変更不要。 + +ToolCall 引数が delta で来ないプロバイダ(Gemini)は scheme アダプタで「BlockStart → InputJson(全体 1 回) → BlockStop」の**擬似ストリーム化**で吸収。 + +Ollama は Ollama 側の OpenAI 互換エンドポイント (`/v1/chat/completions`) に stream+tool バグ (#9092) があるため、**Anthropic Messages API 互換エンドポイント (`/v1/messages`) に寄せる** (`scheme/anthropic` を `base_url` 差し替えで流用、`cache_control` / `tool_choice` は送らない)。 + +## Scope 外 + +- 各アダプタのクレート構成・データ型・API 境界は別 plan/ticket で切り出す +- プロバイダ選択 UI とモデル一覧管理の設計も別扱い +- OpenAI Responses API の stateful (`previous_response_id`) 対応は将来対応、`BlockMetadata` の拡張が必要 diff --git a/docs/plan/memory.md b/docs/plan/memory.md new file mode 100644 index 00000000..c83e0669 --- /dev/null +++ b/docs/plan/memory.md @@ -0,0 +1,119 @@ +# メモリ機構の方針 + +## Context + +INSOMNIA がユーザーのプロジェクトに対して提供するメモリ機構。プロジェクトの暗黙知蓄積と同じ失敗を繰り返さないための記憶が目的。エージェントに連続するアイデンティティや自己意識を持たせる方向は対象外。 + +リサーチは `docs/ref/memory-systems.md`。前提として、**レポジトリがファイルシステム上にある**ケースで設計する(越境・バックエンド抽象は Scope 外)。 + +Workflow(`/workflow-name` で呼び出される制約付き作業フロー)は別 plan に切り出した。`docs/plan/workflow.md` 参照。 + +## 決定事項 + +### 記録対象の 5 種 + +| 種別 | パス | 備考 | +| ---------------- | ---------------------------- | -------------------------------------------------------------------------------------------- | +| Always-on サマリ | `memory/summary.md` | 1-5k tokens 目安 | +| Lessons | `memory/lessons/.md` | | +| Decisions | `memory/decisions/.md` | `status: open \| resolved \| superseded` で未決議論も保持、supersede は `superseded_by` 参照 | +| Requests | `memory/requests/.md` | ユーザー submit の構造化要約 | +| Knowledge | `memory/knowledge/.md` | `#name` で注入。ノウハウ / 用語 / 運用方針 / ルール / 事実など型を設けず Markdown 自由記述 | + +- `` は UUIDv7(時系列順に並ぶ)、`` は slug(小文字英数とハイフン) +- **1 件 1 ファイル**。append-only な複数エントリログファイルは作らない +- Phase 1 の中間ストアとして `memory/_staging/.json` を使う(Phase 2 完了で cleanup、後述) +- Raw session log は既存 `llm-worker-persistence` で保持する。memory 対象外、参照経路のみ + +### Knowledge の呼び出し制御 + +agentskills.io の `SKILL.md` 形式は採用しない。Knowledge は `#knowledge-name` でユーザー / LLM から注入参照する。名前空間は**フラット**、name は slug。 + +| フラグ | 意味 | デフォルト | +| ---------------- | ------------------------------------------------------- | ---------- | +| `auto_invoke` | description が LLM context に載り、LLM が自発的に呼べる | **OFF** | +| `user_invocable` | ユーザーが `#` で明示的に呼べる | **ON** | + +`auto_invoke` の ON 化は人間の判断、または consolidation が頻繁に `user_invoke` されているものを検出して offer する。自律的に新規エンティティを生成はしない。Workflow も同じフラグ仕様(`workflow.md` 参照)。 + +### 書き込み先の制約 + +`memory/workflow/` への自動書き込みは、consolidation Worker の structured output schema に `workflow` カテゴリを含めないことで担保する(後述、および `workflow.md`)。OS ファイル権限や Scope 上の特別な保護機構は設けず、`memory/` 配下を write allow する以上の細工はしない。衝突は git で解決する前提。 + +### 自動化(consolidation)メカニズム + +**2 フェーズ構成**。Phase 1 は頻繁に発火して活動を raw event として抽出、Phase 2 は蓄積時のみ発火して永続化形式に統合する。参考: Codex Memories の Phase 1/2 構造。 + +#### Phase 1: 活動抽出 + +- **Trigger**: activity tokens の累積閾値。tool call カウントは不採用(ツールカスタマイズ非依存・大小重みづけのため) +- **実行主体**: 既存 compact と同じ Worker spawn 機構を再利用。Pod は立てない +- **入力**: 前回 Phase 1 以降の session log 範囲 +- **出力**: JSON schema で**活動ログ**の候補配列を返す。Knowledge 等の派生物は Phase 2 が活動ログから導出するので、Phase 1 では純粋な「起きたこと」に絞る + - `decisions`: 判断したこと(選択肢 + 選んだ + 根拠) + - `discussions`: 議論したこと(トピック + 論点) + - `attempts`: 試したこと(試行 + 結果 + 成否) + - `requests`: ユーザー submit の構造化要約(意図 / 対象 / 要約) + - **抽出対象がなければ空配列を返してよい**(Hermes の "Nothing to save." と同系。頻繁発火を許容する前提) +- **書き込み先**: `memory/_staging/.json` +- **モデル**: `memory.extract_model`。軽量だが文脈理解できる中堅クラス(Haiku / 4o-mini / Flash 相当)を想定 + +#### Phase 2: 永続化への統合 + +- **Trigger**: staging の累積ファイル数 or bytes が閾値超過、または compact 発火時(必ず flush) +- **実行主体**: Phase 1 を終えた pod が `memory/_staging/.lock` の advisory file lock を `LOCK_EX | LOCK_NB` で取得試行。取れたら consolidation Worker を spawn。取れなければ skip(他 pod が処理中) +- **Lock**: POSIX `flock(2)` / `fcntl` ベース。プロセスが生きている限り保持、落ちれば OS が自動解放。stale 検出・heartbeat 不要。reasoning 処理が数分〜十数分かかる前提 +- **入力**: staging 全件(活動ログ)+ 既存 `memory/*`(summary / lessons / decisions / requests / knowledge) +- **処理**: sub-Worker に memory read/write tool を渡し、agentic に以下を自律判断: + - 新規 lessons / decisions / requests を 1 件 1 ファイルで追加 + - 活動ログから派生する Knowledge(用語定義 / 運用方針 / ルール / 事実 / ノウハウ)を新規作成 or 既存 patch + - summary を必要に応じて rewrite +- **書き込み先**: summary / lessons / decisions / requests / knowledge の 5 種 +- **Workflow への書き込み禁止**: Phase 2 の write tool schema に workflow を含めないことで構造的に担保(`workflow.md` 参照) +- **完了処理**: staging cleanup + lock release。Phase 2 完了時に staging に新着があれば次を発火(Coalesce) +- **モデル**: `memory.consolidation_model`。reasoning 系 + +#### Phase 2 agent への原則 + +`memory/` 配下は人間も git 経由で編集する。Phase 2 prompt で以下を明示: + +- 既存内容を尊重し、**差分追加を優先**する。全文 rewrite は `summary.md` のみ、かつ人間編集と整合する範囲で +- 削除は supersede 記録(`status: superseded` + `superseded_by: `)で表現、直接削除しない + +#### Offer 経路 + +consolidation は自律生成しない。以下は Client に `Event::Notification` で提案し、人間承認で反映: + +- Knowledge の `auto_invoke` ON 化 +- Workflow 関連の offer(新規作成 / 改善 / `auto_invoke` ON 化)は `workflow.md` 参照 + +#### Compact との関係 + +基本分離(memory は独立トリガー、compact は `input_tokens` 既存閾値のまま)。ただし **compact 発火時は Phase 2 を必ず同時 flush**(compact で失われる raw を漏らさないため)。 + +### ファイル形式 + +- frontmatter + Markdown 本文 +- Knowledge の frontmatter: `name`, `description`, `auto_invoke`, `user_invocable` +- Lessons / Decisions / Requests の frontmatter: `id`, `created_at`, および種別固有フィールド +- Decisions は `status: open | resolved | superseded`、supersede 時は `superseded_by: ` を併記 +- Phase 1 staging は `memory/_staging/.json`(JSON、1 件 1 ファイル、Phase 2 完了で削除) +- Workflow の frontmatter は `workflow.md` 参照 + +## Scope 外 + +- ネットワーク越境での memory 同期 — `network-peering.md` で扱う範疇。本設計はプロジェクトスコープ固定 + +### 将来検討(運用で必要性が見えたら追加) + +- Vector index / FTS5 等の検索索引 — 初期は grep で足りる想定。ファイル数増加で検索が重くなったら検討 +- `auto_invoke` offer の自動判定ロジック — 初期は人間が手動で切り替え +- 過去 session を cross-session で検索する UI +- Phase 2 を担う常駐 daemon 化 — オンデマンド + lock 方式で始める。必要性が出たら upgrade path として daemon 化 +- Deterministic promotion(OpenClaw 型 scoring + ゲート)— 初期は Phase 2 agent の LLM 判断に委ねる。運用実績で出力を評価してから、成熟カテゴリから scoring 導入 +- Shallow request の自動除外判定 — 初期は Phase 1 prompt で「些細な質問は返さなくてよい」と指示する程度。精緻な filter は後 +- Retention / GC — Phase 2 agent に直接削除禁止(supersede のみ)を課す一方、明示クリーンアップ経路は未定義。別途 LLM 駆動 GC を用意する前提で、初期は無制限蓄積 + summary rewrite で凌ぐ + +### 別 plan / ticket で扱う + +- 具体クレート構成・API 境界 diff --git a/docs/plan/network-peering.md b/docs/plan/network-peering.md new file mode 100644 index 00000000..625569c9 --- /dev/null +++ b/docs/plan/network-peering.md @@ -0,0 +1,284 @@ +# ネットワーク越しの Pod 協働(将来設計ノート) + +本ドキュメントは将来の設計方針を記録するものであり、実装チケットではない。 + +--- + +## 動機 + +ローカルの workspace が Pod 間の発見・通知・scope 会計を提供するが、 +これは 1 台のマシンに閉じている。複数マシンにまたがるプロジェクト +(モノレポの異なる部分を別マシンで触る、CI マシンの Pod と開発マシンの +Pod が連携する等)では、マシン間のメッセージングが必要になる。 + +## 設計原則 + +- **中央サーバーを作らない**。各マシンが主権を持ち、P2P でやり取りする +- **ローカル workspace を歪めない**。ネットワーク層は workspace の上に + 乗る「他の workspace への郵便」であり、workspace 自体を分散化しない +- **ファイルシステムはマシンローカル**。cross-host の scope 分譲は + 行わない。協働はメッセージベースのタスク委譲で完結する +- **SSH を transport に使う**。開発者の手元に既にあるインフラで、 + 鍵管理・暗号化・認証が追加投資なしで手に入る + +## アドレッシング + +Pod のネットワークアドレスは `insomnia.pod-name@host` の形式を**論理的な +宛先表記**として使う。実際の SSH 接続がこの文字列そのままで行えるかは +transport 方式に依存する(後述)。 + +- `insomnia` = SSH ユーザー名(固定) +- `host` = 相手マシンのホスト名 or IP +- `pod-name` = 送信先の Pod 名(相手マシン上のローカル workspace 内で一意) + +推奨構文は **`insomnia@host:pod-name`**(git 方式)。 +詳細は後述の「アドレッシング構文」を参照。 + +## アドレッシング構文 + +論理的な宛先表記として **`insomnia@host:pod-name`** を推奨する。 +git の `git@github.com:user/repo` と同じ構文で: + +- SSH ユーザーは `insomnia` 固定(動的ユーザー名が不要) +- `:` 以降がルーティング情報(Pod 名) +- クライアント側が `insomnia@host:pod-name` をパースし、 + `ssh insomnia@host "insomnia-route pod-name"` に変換する + +git がこの方式で `git-upload-pack user/repo` にルーティングしている +のと同じ仕組み。OS レベルの設定(NSS モジュール等)が一切不要で、 +ユーザー 1 つ + ForceCommand(またはシェルスクリプト)だけで動く。 + +## SSH transport の選択肢 + +### A. 単一ユーザー + コマンド引数 + +``` +ssh insomnia@host send pod-name "message" +``` + +- 相手マシンにシステムユーザー `insomnia` を 1 つ作る +- `authorized_keys` に接続元 Pod の公開鍵を登録 +- ForceCommand または shell スクリプトが第一引数 (`send`) と + 第二引数 (`pod-name`) を解釈してローカル workspace のレジストリから + Pod の socket を引き、メッセージをルーティング +- **導入コスト最低**。ユーザー 1 つ + スクリプト 1 つで動く +- 宛先が引数に入るので `insomnia.pod-name@host` の見た目にはならない + +### B. 鍵ベースルーティング(gitolite 方式) + +``` +ssh insomnia@host # 使った鍵でどの Pod 宛か判別 +``` + +- `~insomnia/.ssh/authorized_keys` に Pod ごとのエントリ: + ``` + command="insomnia-route pod-a",no-port-forwarding,... ssh-ed25519 AAAA... pod-a@remote + command="insomnia-route pod-b",no-port-forwarding,... ssh-ed25519 AAAA... pod-b@remote + ``` +- SSH 接続時に使われた鍵が `command=` で指定されたルーティング先を決定 +- gitolite / Gitea / Gogs で実証済みのパターン +- 接続元は `ssh insomnia@host` だけ。**鍵が宛先を決める** +- クライアント側 SSH config で alias を作れば見た目を整えられる: + ``` + Host pod-a.host-b + HostName host-b + User insomnia + IdentityFile ~/.config/insomnia/keys/pod-a + ``` +- 鍵の登録が相互に必要(Pod A が Pod B に送るなら、B のマシンの + authorized_keys に A の公開鍵 + route 先を登録) + +### C. 動的ユーザー名 + +``` +ssh insomnia.pod-name@host +``` + +- `insomnia.pod-name` を OS レベルで有効なユーザー名として解決する: + - NSS (Name Service Switch) モジュールを書くか `libnss-extrausers` を利用 + - PAM モジュールで認証をフック + - `sshd_config` で `Match User insomnia.*` → `ForceCommand` でルーティング +- **最も直感的なアドレッシング**だが OS レベルの設定が必要 +- insomnia をインストールするだけでは動かない(管理者権限での設定が要る) +- コンテナ環境ではやりやすい(ユーザー管理を自由にできる) + +### 推奨 + +**MVP は A(単一ユーザー + コマンド引数)** から始める。設定が最小で +動くものが作れる。将来 B(鍵ベースルーティング)に進化させると +セキュリティが強化される(Pod 単位での接続制御が可能)。 +C は UX は最高だが導入コストが高く、必要が明確になるまで見送る。 + +## ファイルシステムの境界 + +Pod はローカルのファイルシステムだけを操作する。ネットワーク越しの +Pod 協働では: + +- **scope はマシンローカルに閉じる**。host_a の `/src` と host_b の + `/src` は別物。cross-host の scope 分譲は行わない +- **協働はタスク委譲**。「このリポジトリでこの変更をして結果を教えて」 + というメッセージで、相手の Pod がローカルに実行する +- **sshfs / NFS 等のネットワークファイルシステムは使わない**。 + 透過的なリモートファイルアクセスは壊れやすく、scope モデルと相性が悪い + +## ローカル workspace との関係 + +| 層 | スコープ | 役割 | +|---|---|---| +| Workspace | 1 台のマシン | Pod 発見 / scope 会計 / 通知バス | +| Network peers | マシン間 | メッセージング / タスク委譲 / 結果通知 | + +- Network peers は workspace の**上に**乗る。workspace を置き換えない +- ローカルの Pod 間通信は引き続き workspace の Unix socket バスを使う +- リモートの Pod への通信だけ SSH transport を経由する +- Pod から見ると「ローカル peer に送る」と「リモート peer に送る」は + 同じ API で、transport 層が切り替わるだけ(が理想) + +## broadcast の扱い + +ローカル workspace は Unix socket で 1 本の bus を持てたが、 +ネットワーク越しには共有 bus が無い。 + +- 各マシン(または各 Pod)が **known-peers リスト** を持つ +- broadcast = known-peers を iterate して個別送信 +- 規模が数十 Pod なら十分実用的 +- 将来的に gossip protocol で peer 発見を自動化できるが、 + MVP では手動登録(`insomnia peer add pod-a@host-b`)で十分 + +## 受信側のルーティング + +SSH 接続を受けた側が、宛先 Pod のローカル socket にルーティングする +仕組みが必要。 + +``` +[SSH 接続] → insomnia-route + ↓ + workspace registry を参照 + ↓ + /run/insomnia/.../pod-name.sock に転送 + ↓ + Pod が受信・処理 +``` + +`insomnia-route` は: +1. workspace のレジストリを読んで pod-name の socket path を引く +2. socket に接続してメッセージを中継 +3. 応答を SSH 接続に返す + +workspace が複数ある場合のルーティング(どの workspace の +pod-name か)は追加の設計が必要。Pod 名がマシン上で globally +unique であれば workspace を指定しなくて済む。 + +## Daemon-less リモート Pod 生成(SSH-only モデル) + +リモートホスト上の Pod 生成は **daemon 無しで SSH だけで成立する**。 +remote 側に必要なのは `pod` バイナリと SSH アクセスのみ。 + +### 前提 + +- insomnia は環境再現(git clone, コンテナ構築等)を自身の責務としない。 + 作業対象のファイルがリモートに既にあるか、ユーザーが任意の手段で + 用意する前提(git clone, rsync, 手動配置、CI の checkout 等) +- insomnia が転送するのは**セッション(会話履歴)と manifest overlay** + だけ。コードベースの同期は外部に委ねる +- コンテナ内で動かすか bare metal で動かすかも insomnia は問わない。 + `pod` バイナリが動くホストの fs 上で活動する主体がある、 + それだけが前提 + +### フロー + +``` +host_a (spawner) host_b (remote) + Pod A (pod binary + ssh のみ) + │ + ├── ssh: session データを転送 ────────→ ファイル書き込み + ├── ssh: overlay TOML を転送 ─────────→ ファイル書き込み + ├── ssh: `pod --overlay ... &` ───────→ Pod プロセス起動、socket 作成 + ├── ssh -L: socket を tunnel ─────────→ Pod B の unix socket + │ + └── localhost:tunnel に接続 ──────────→ Method::Run / Event stream + (以降はローカル Pod と同じ protocol) +``` + +### コマンドイメージ + +```bash +# 1. session + overlay を転送 +ssh insomnia@host-b "mkdir -p ~/workspaces/task-123/store" +tar cz session/ | ssh insomnia@host-b "tar xz -C ~/workspaces/task-123/store" +echo "$OVERLAY" | ssh insomnia@host-b "cat > ~/workspaces/task-123/overlay.toml" + +# 2. Pod を起動(detach) +ssh insomnia@host-b "pod --store ~/workspaces/task-123/store \ + --overlay ~/workspaces/task-123/overlay.toml &" + +# 3. socket を tunnel で引っ張る +ssh -L /tmp/pod-b.sock:/run/insomnia/task-123/pod.sock insomnia@host-b + +# 4. あとは /tmp/pod-b.sock にローカルと同じ protocol で繋ぐ +``` + +spawner の `SpawnPod` ツールがこの一連を内部で実行する。LLM から +見たら「ツールを呼んだら Pod ができた」だけ。 + +### なぜこれで足りるか + +- **protocol は変わらない**: SSH tunnel の向こう側は普通の Pod socket。 + ローカルの Pod と同じ `Method` / `Event` でやり取りする +- **scope は host ごとに独立**: cross-host の scope 分譲はそもそも + 成立しないので、workspace の scope 会計は remote には関係しない +- **通知**: SSH tunnel が繋がっている限り `Event` stream がそのまま + 流れる。tunnel が切れたら再接続する +- **環境構築は insomnia の責務外**: git clone するか rsync するかは + Pod の instruction で指示するか、事前に用意されている前提 + +### daemon が必要になるケース + +SSH-only モデルの制約が、daemon 導入の動機になる: + +- **Pod 一覧の取得**: remote の runtime_dir を SSH 越しに `ls` する + 必要がある(daemon がいればレジストリ API で済む) +- **Pod の生存監視**: tunnel が切れたら再接続するまで状態不明 + (daemon がいれば health check を引き受ける) +- **複数の spawner が同じ remote Pod に繋ぐ**: tunnel の共有が面倒 + (daemon がいれば multiplexing できる) +- **workspace サービス(registry / 通知バス)の remote 提供**: + SSH-only モデルではリモート側に workspace サービスが無い + +これらは **MVP では問題にならず**、daemon は「便利にしたくなった +ときの upgrade path」として位置づける。 + +### リモート側のディレクトリ構成 + +``` +/home/insomnia/ ← insomnia システムユーザーの home +├── workspaces/ +│ ├── / ← workspace ごとのルート +│ │ ├── repo/ ← ユーザーが用意した作業ファイル群 +│ │ └── store/ ← session store(spawner から転送) +│ └── ... +└── .ssh/ + └── authorized_keys ← 接続元 Pod の公開鍵 +``` + +- `insomnia` システムユーザーが SSH 接続先 + ファイル所有者 +- `repo/` 配下の準備は insomnia の責務外(git clone, rsync 等は + ユーザーや instruction が指示) +- `store/` は spawner がセッションデータを書き込む場所 + +## セキュリティの考慮 + +- SSH の鍵認証がベースライン。パスワード認証は使わない +- Pod 単位の鍵ペアにより、接続制御の粒度を Pod レベルにできる(B 方式) +- `authorized_keys` の `command=` で実行可能な操作を制限できる + (`no-port-forwarding`, `no-pty` 等) +- 将来的に Pod 間の trust relationship を定義する仕組みが要るが、 + それは本ドキュメントの範囲外 + +## 未解決の論点 + +- SSH transport の具体的な message protocol(JSON-RPC? 独自? protocol crate の拡張?) +- 非同期メッセージの扱い(相手が offline のとき queue するか、fail するか) +- peer 登録の自動化(workspace 内の Pod が自動で peer list を共有する等) +- workspace が複数ある環境での pod-name 解決 +- Pod の migration(あるマシンから別のマシンへ Pod を移す)の可能性と scope の扱い diff --git a/docs/plan/sandbox.md b/docs/plan/sandbox.md new file mode 100644 index 00000000..76a4df2c --- /dev/null +++ b/docs/plan/sandbox.md @@ -0,0 +1,11 @@ +完全なサンドボックスを求めるなら、Dockerコンテナ内で動かすのが一番 + +--- + +コード実行環境なら↓ +Denoが出してたサンドボックス -> https://deno.com/deploy/sandbox +Firecracker microVM で作ってるっぽい。 + +--- + +エージェントをサンドボックスに転送する場合、ディレクトリ全体をマウントして動かすか?Podもその内部で動作させるか? diff --git a/docs/plan/workflow.md b/docs/plan/workflow.md new file mode 100644 index 00000000..2d177266 --- /dev/null +++ b/docs/plan/workflow.md @@ -0,0 +1,60 @@ +# Workflow の方針 + +## Context + +Workflow は制約付きの強制的な作業フロー。`/workflow-name` で明示的に呼び出し、依存 Knowledge を context に inject してから実行する。Knowledge(`#knowledge-name`)は `docs/plan/memory.md` 側で定義。 + +## 決定事項 + +### 呼び出しと依存 + +- 呼び出し: `/workflow-name` +- 名前空間はフラット、name は slug(小文字英数とハイフン) +- frontmatter `requires: [knowledge-name, ...]` で依存 Knowledge を name 参照 +- 実行時は依存 Knowledge 本文を context に inject してから Workflow 本文を実行 + +### 呼び出し制御フラグ + +| フラグ | 意味 | デフォルト | +| ---------------- | ------------------------------------------------------- | ---------- | +| `auto_invoke` | description が LLM context に載り、LLM が自発的に呼べる | **OFF** | +| `user_invocable` | ユーザーが `/` で明示的に呼べる | **ON** | + +`auto_invoke` の ON 化は人間の判断、または consolidation からの offer 経由のみ。同じ制御は Knowledge 側(`memory.md`)でも採用。 + +### 格納先とファイル形式 + +- `memory/workflow/.md` +- frontmatter + Markdown 本文 +- frontmatter フィールド: `name`, `description`, `auto_invoke`, `user_invocable`, `requires` + +### 生成・更新ポリシー + +Workflow は**人間が書く**、または consolidation が offer して人間が承認する。自動書き込みは禁止: + +- consolidation Phase 2(`memory.md` 参照)の write tool schema に `workflow` カテゴリを含めないことで構造的に担保 +- 新規作成 / 手順追加・更新は `Event::Notification` で提案し、人間承認で反映 + +### Offer 契機 + +consolidation が以下を検出した場合、Client に Notification を投げる: + +- 再利用価値ある手続きの Workflow 化(新規作成) +- 既存 Workflow への改善提案(手順追加・更新) +- `auto_invoke` ON 化(頻繁に `user_invoke` されているものを検出) + +## Scope 外 + +### 恒久除外(本設計方針として採用しない) + +- Workflow の自律生成(offer までで留める。LLM が勝手に新規 Workflow を生成する経路は設けない) + +### 将来検討(運用で必要性が見えたら追加) + +- DSL 化や step 粒度の制約 — 初期は Markdown 本文そのまま実行 +- Workflow 実行中の中断・再開・トランザクション管理 +- 品質検証フロー: external author empirical-prompt-tuning(`docs/ref/memory-systems.md` §6)相当の**新規 subagent 試走 + 構造化報告**を Workflow に適用。判定対象は本文の不明瞭点・裁量補完・要件達成率。Knowledge 単体の検証は設けず、`requires` 経由で Workflow から使われる前提で間接回収。SKILL 的用途(Workflow 経由しない `#knowledge`)は人間レビューに委ねる + +## 関連 + +- `memory.md`: Knowledge 定義、consolidation Phase 1/2、Offer の配送経路 diff --git a/docs/pod-factory.md b/docs/pod-factory.md new file mode 100644 index 00000000..ed5ab6c3 --- /dev/null +++ b/docs/pod-factory.md @@ -0,0 +1,253 @@ +# Pod Factory: カスケード設定とプロンプト資産 + +`PodFactory` は、複数の層に分かれた `manifest.toml` とプログラマティック +overlay をマージして、検証済みの `PodManifest` と `PromptLoader` を生成する +ビルダー。これにより Pod 起動ごとに TOML を手書きする必要がなくなる。 + +--- + +## カスケード層 + +優先順位が低い順(上位ほど下位を上書き): + +| 優先度 | 層 | 位置 | 典型的な内容 | +|---|---|---|---| +| 1 | ビルトインのデフォルト | `manifest::defaults` モジュールの `pub const` 群を `PodManifestConfig::builtin_defaults()` が cascade 層として注入 | `tool_output.default_max_bytes = 16KB` など | +| 2 | ユーザー manifest | `$XDG_CONFIG_HOME/insomnia/manifest.toml`(未設定時は `~/.config/insomnia/manifest.toml`) | プロバイダ指定、デフォルトモデル、常用ツール設定 | +| 3 | プロジェクト manifest | 起動ディレクトリから上方向に探索した最初の `/.insomnia/manifest.toml` | scope、compaction、プロジェクト固有の instruction | +| 4 | プログラマティック overlay | CLI / GUI / 別 Pod からの spawn 等 | `pod.name`、`pod.pwd` のような Pod 固有値 | + +デフォルト値はすべて `crates/manifest/src/defaults.rs` の `pub const` として集約 +されており、serde `#[default = "..."]` 経路(`PodManifest` の直接 deserialize) +と `TryFrom` 経路(cascade 解決)の両方が同じ constants を +参照する。デフォルトを変更するときは `defaults.rs` の 1 行を書き換えるだけで +全経路に反映される。 + +どの層も TOML スキーマは `PodManifest` と同じ(全フィールド省略可)。 + +## マージセマンティクス + +| フィールド種別 | 規則 | +|---|---| +| スカラー(`String`, `u32`, `bool` 等) | 上層に値があれば丸ごと置換 | +| `Option` | 上層が `Some` なら置換、`None` なら据え置き | +| マップ(`tool_output.per_tool` 等) | キー単位でマージ、同一キーは上層優先 | +| `scope.allow` / `scope.deny` | **union**(各層から全部足す)。上位層は `deny` で下位層の `allow` を必ず削れる | + +各層をマージした結果(`PodManifestConfig`)を `TryFrom +for PodManifest` が必須フィールド検証と絶対パス検証をかけて `PodManifest` +に変換する。 + +## パス解決 + +manifest 中のパス(`provider.api_key_file` / `scope.*.target` / +`compaction.provider.api_key_file`)は相対記述を許容する。相対パスは +**各層のベース基準**で層ごとに絶対化され、そのあとで cascade merge に +かかる。層をまたいだ相対の意味ブレ(user 層の `./keys` が project 層の +どこを指すのか曖昧)を避けるための設計。 + +| 層 | ベース | +|---|---| +| user manifest (`~/.config/insomnia/manifest.toml`) | そのファイルの親ディレクトリ | +| project manifest (`/.insomnia/manifest.toml`) | **プロジェクトルート**(`.insomnia/` の親)。`target = "."` がワークスペース全体を指すように | +| overlay(inline TOML・programmatic) | プロセスの `current_dir()` | + +Pod の作業ディレクトリは manifest に含まれない。プロセス起動時の +`std::env::current_dir()` がそのまま Pod の pwd となるため、別の作業 +ディレクトリで Pod を走らせたい場合は `cd` してから `pod` を起動する +(または `SpawnPod` が子に対して行っているように、親プロセス側で +`Command::current_dir` を明示する)。 + +cascade merge 後の `TryFrom` では `ensure_absolute` +が不変条件チェックとしてだけ働く。相対パスが残っていれば上流の +resolve 段を取りこぼしている証拠なので `ResolveError::RelativePath` を +返す。 + +## 未知フィールドと型エラー + +- **未知フィールド**: `tracing::warn!` を出して無視。将来バージョンアップで読めない + 旧設定が出るとユーザー体験が悪いため、`#[serde(deny_unknown_fields)]` は使わない。 +- **型ミスマッチ**: `max_tokens = "100"` のような型エラーは hard error として + resolve 失敗させる。ファイルパスと位置情報をエラーメッセージに含める。 + +--- + +## manifest.toml 例 + +### ユーザー層(最小) + +`$XDG_CONFIG_HOME/insomnia/manifest.toml`: + +```toml +[model] +scheme = "anthropic" +model_id = "claude-sonnet-4-20250514" +auth = { kind = "api_key", file = "/home/you/.config/insomnia/keys/anthropic" } +``` + +### プロジェクト層(最小) + +`/.insomnia/manifest.toml`: + +```toml +[[scope.allow]] +target = "/abs/path/to/project" +permission = "write" + +[[scope.deny]] +target = "/abs/path/to/project/secrets" +permission = "read" + +[compaction] +compact_threshold = 80000 +``` + +### 全オプション例 + +```toml +[pod] +name = "reviewer" + +[model] +scheme = "anthropic" +model_id = "claude-sonnet-4-20250514" +base_url = "https://api.anthropic.com" +auth = { kind = "api_key", file = "/home/you/.config/insomnia/keys/anthropic" } + +[worker] +instruction = "$user/reviewer" +max_tokens = 4096 +max_turns = 50 +temperature = 0.3 + +[worker.tool_output] +default_max_bytes = 16384 + +[worker.tool_output.per_tool] +Read = 32768 +Grep = 4096 + +[[scope.allow]] +target = "/abs/path/to/project" +permission = "write" + +[[scope.allow]] +target = "/abs/path/to/docs" +permission = "read" +recursive = false + +[[scope.deny]] +target = "/abs/path/to/project/secrets" +permission = "write" + +[compaction] +prune_protected_turns = 3 +prune_min_savings = 4096 +compact_threshold = 80000 +compact_retained_turns = 2 + +[compaction.provider] +kind = "gemini" +model = "gemini-2.0-flash" +api_key_file = "/home/you/.config/insomnia/keys/gemini" +``` + +--- + +## instruction とプロンプト資産 + +### `worker.instruction` フィールド + +Pod のシステムプロンプトの**本体**として使うプロンプト資産への参照。 +import-map 形式のプレフィックスで指定する: + +| プレフィックス | 解決先 | +|---|---| +| `$insomnia` | バイナリ同梱の `resources/prompts/`(`include_dir!`) | +| `$user` | `$XDG_CONFIG_HOME/insomnia/prompts/` | +| `$workspace` | `/.insomnia/prompts/` | + +- `.md` 拡張子は省略する(例: `$insomnia/default` → `resources/prompts/default.md`) +- 省略時のデフォルト値は `$insomnia/default`(`defaults::DEFAULT_INSTRUCTION`) +- 指定した prefix の dir に該当ファイルが無ければ **hard error**(fallthrough しない) + +### ビルトインプロンプト + +`resources/prompts/` 以下に同梱: + +| 名前 | 用途 | +|---|---| +| `default` | デフォルトの instruction 本体。workspace / tool-usage をインクルード | +| `common/workspace` | cwd・日付の注入 | +| `common/tool-usage` | ツール使用の共通ガイダンス | + +### `{% include %}` の相対解決 + +テンプレート内で `{% include "name" %}` のようにプレフィックス無しで書いた場合、 +**include を書いたファイル自身のプレフィックスとディレクトリ**からの相対で解決する: + +- `$insomnia/default.md` 内の `{% include "common/workspace" %}` → `$insomnia/common/workspace` +- `$user/custom.md` 内の `{% include "$insomnia/common/tool-usage" %}` → 明示的プレフィックスが優先 + +### システムプロンプトの最終構造 + +`instruction` テンプレートのレンダリング結果に、Rust 側で以下の**固定セクション**を付加する。 +ユーザーテンプレートからは触れない領域: + +``` + + +--- +## Working boundaries + + + +--- ← AGENTS.md が不在なら省略 +## Project instructions (AGENTS.md) + + ← AGENTS.md が不在なら省略 +``` + +- scope セクションは**必ず**出力される +- AGENTS.md セクションは不在時に区切り `---` ごと省略 + +--- + +## `pod` CLI + +``` +pod [--user-manifest ] [--project ] [--overlay ] + [-s/--store ] +``` + +| フラグ | 説明 | +|---|---| +| `--user-manifest` | ユーザー manifest のパス。省略時は XDG から自動解決 | +| `--project` | プロジェクト manifest 探索の起点。省略時は cwd から上方向に `.insomnia/` を探索 | +| `--overlay` | 最上層の overlay を inline TOML 文字列で渡す(例: `--overlay 'worker.instruction = "$user/foo"'`) | +| `-s, --store` | セッション永続化ディレクトリ(デフォルト: `~/.insomnia/sessions/`) | + +Pod の作業ディレクトリは `pod` 起動時の cwd が直接使われる。別ディレクトリで +動かしたい場合は `cd && pod ...` のように外側で `cd` してから起動する。 + +引数無しで起動すると、cwd + XDG の自動解決だけで動く最小構成になる +(overlay 無し、プロジェクトに `.insomnia/manifest.toml` があればそれを使う)。 + +--- + +## プログラマティック API + +```rust +use pod::{Pod, PodFactory}; + +let (manifest, loader) = PodFactory::new() + .with_user_manifest_auto()? // XDG から自動読み込み、不在 OK + .with_project_manifest_auto()? // cwd から上方向に .insomnia/ を探索、不在 OK + .with_overlay_toml(overlay)? // programmatic な最上層 overlay + .resolve()?; // -> (PodManifest, PromptLoader) + +let pod = Pod::from_manifest(manifest, store, loader).await?; +``` + +`Pod::from_manifest_toml(toml, store)` は単層 manifest を TOML 文字列で直接投げる +便利関数(テスト・デバッグ向け)。builtins-only のプロンプトローダで動く。 diff --git a/docs/ref/claude-code-compaction.md b/docs/ref/claude-code-compaction.md new file mode 100644 index 00000000..b4cf5793 --- /dev/null +++ b/docs/ref/claude-code-compaction.md @@ -0,0 +1,244 @@ +# Claude Code コンテキスト管理リファレンス + +調査日: 2026-04-12 + +## 概要 + +Claude Code は3層構造のコンテキスト管理を行う。 +安価な局所操作から順に適用し、必要に応じてより重い操作にエスカレーションする。 + +``` +Tier 1: MicroCompaction(ローカル、API コスト 0) + ↓ それでもコンテキストが大きい場合 +Tier 2: AutoCompact(API ベース要約、自動発動) + ↓ ユーザーが明示的に要求 +Tier 3: Full Compact(/compact コマンド) +``` + +--- + +## Tier 1: MicroCompaction + +**API 呼び出しなし**のローカル操作。個別のアイテムを軽量に刈り込む。 + +### 対象 + +| 対象 | 操作 | +|------|------| +| 古いツール結果 | プレースホルダに置換(`"stored on disk, retrievable by path"`) | +| base64 画像 | 古いメッセージから除去 | +| 50K 文字超のツール出力 | ディスクに退避、パスで参照 | +| thinking ブロック | 直近ターン以外は除去 | + +### 条件付き実行(cache-aware) + +ツール結果クリア機能 `clear_tool_uses` には `clear_at_least` パラメータがある。 + +**「十分なトークンを削れる場合にだけ実行」** という判断を行い、 +キャッシュ無効化コスト > 節約トークン数 となるケースを避ける。 + +これは Insomnia における条件付き Prune の直接的な先行事例。 + +### キャッシュへの影響 + +- ツール結果をクリアすると**プレフィクスが変わり、KV キャッシュが無効化される** +- Claude Code はこれを認識した上で、`clear_at_least` 閾値で損益を管理 +- **14種のキャッシュ無効化ベクター**を `promptCacheBreakDetection.ts` で追跡 +- system prompt を cached/uncached セクションに分離(`SYSTEM_PROMPT_DYNAMIC_BOUNDARY`) +- "sticky latch" パターンでモード切替によるキャッシュ破壊を防止 + +--- + +## Tier 2: AutoCompact + +**API ベースの要約生成**。コンテキスト使用量が閾値を超えると自動発動。 + +### トリガー + +- コンテキスト使用量が**ウィンドウの約 83.5%** に到達 + - 200K ウィンドウの場合、約 167K トークン + - `CLAUDE_AUTOCOMPACT_PCT_OVERRIDE` 環境変数で調整可能(1-100) +- 約 33K トークンのバッファを確保(要約処理 + 応答生成用) + +### 要約生成 + +- **最大 20,000 トークン**の構造化要約を生成 +- 要約中は**ツール無効化**(`tools: []`)で副作用を防止 +- `` タグで chain-of-thought 推論を行い、最終要約から推論部分を除去 + +### 要約の構造(9セクション) + +``` +1. Primary Request and Intent(元の要求と意図) +2. Key Technical Concepts(重要な技術概念) +3. Files and Code Sections(ファイルとコードセクション) +4. Errors and Fixes(エラーと修正) +5. Problem Solving(問題解決の過程) +6. All User Messages(ツール結果以外の全ユーザーメッセージ) +7. Pending Tasks(未完了タスク) +8. Current Work(現在の作業) +9. Optional Next Steps(次のステップ案) +``` + +加えて「直近の会話からの直接引用」を要求し、作業の継続性を保証する。 + +### サーキットブレーカー + +- 3回連続で AutoCompact が失敗すると、セッション残りで compaction を無効化 + - 定数: `MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES = 3` +- コンテキスト再充填が3回連続で即座に発生(thrash loop)した場合もエラーで停止 + +### サーバーサイド Compaction API + +Anthropic は `compact-2026-01-12` beta でサーバーサイド compaction を提供: + +``` +1. input_tokens が閾値超過 +2. Claude が要約を生成(ツール無効) +3. 要約が `compaction` ブロックとして assistant メッセージに挿入 +4. 次のリクエストで compaction ブロック以前のメッセージが自動ドロップ +``` + +`pause_after_compaction` オプション: compaction 後に `stop_reason: "compaction"` で一旦返し、 +クライアントがファイル・プラン・メモリ等を再注入してから継続できる。 + +--- + +## Tier 3: Full Compact(`/compact`) + +ユーザーが明示的に発動する完全圧縮。 + +### 圧縮後に再注入されるもの + +| 再注入対象 | 上限 | +|------------|------| +| 要約 | 約 10K トークン | +| 直近アクセスファイル | 最大5ファイル、各 5,000 トークン | +| アクティブプラン | 全量 | +| CLAUDE.md / MEMORY.md | 全量 | +| 関連スキルスキーマ | 最大 25K トークン | +| フック結果 | 全量 | +| 直近メッセージ | 約 20 メッセージ | + +圧縮後の作業バジェットは約 50,000 トークンにリセットされる。 + +--- + +## Anthropic Prompt Caching の仕様 + +Claude Code のコンテキスト管理を理解するために必要な、プロバイダ側のキャッシュ仕様。 + +### プレフィクスベース + +- リクエストの先頭から `cache_control` ブレークポイントまでの内容がキャッシュ対象 +- **1バイトでも変わるとそこから先は全てミス** +- キャッシュは因果的(causal): アイテム N の KV が変わるとN 以降すべて再計算 + +### A を送った後に A+B を送った場合 + +- **A のキャッシュは保持される**(独立した TTL) +- A+B のリクエストで A はキャッシュから読まれ、B だけ新規処理 +- **複数のキャッシュエントリが共存**する + +### TTL + +| タイプ | 時間 | 書き込みコスト | +|--------|------|----------------| +| ephemeral(デフォルト) | 5分(ヒットで更新) | 基本入力の 1.25倍 | +| 拡張 | 1時間 | 基本入力の 2倍 | + +読み取り: 基本入力の **0.1倍**(90% 割引)。1回のキャッシュヒットで書き込みコストを回収。 + +### 最小キャッシュ可能トークン + +| モデル | 最小トークン | +|--------|-------------| +| Opus 4.6 / 4.5 | 4,096 | +| Sonnet 4.6 | 2,048 | +| Sonnet 4.5 / 4 / 3.7, Opus 4.1 / 4 | 1,024 | +| Haiku 4.5 / 3 | 4,096 | + +### ブレークポイント + +- 最大 **4個** の明示的ブレークポイント +- 自動キャッシング: 最後のキャッシュ可能ブロックにブレークポイントを自動配置 +- **20ブロック lookback**: ブレークポイントから20ブロック以上離れたキャッシュは検索されない + +### キャッシュ階層と無効化 + +``` +tools → system → messages +``` + +上位の変更は下位すべてを無効化: +- tools 変更 → tools + system + messages 全て無効 +- system 変更 → system + messages 無効 +- messages 変更 → messages のみ無効 + +--- + +## OpenAI / Gemini との比較 + +| | Anthropic | OpenAI | Gemini | +|---|---|---|---| +| 制御 | 明示的 `cache_control` | 完全自動 | 明示 + 暗黙 | +| 書き込みコスト | +25% / +100% | **無料** | 標準入力レート | +| 読み取り割引 | 90% | 50-90% | 90% | +| TTL | 5分(更新あり)/ 1h | 5-10分 / 最大1h / 24h | デフォルト1h / 任意 | +| ヒット保証 | 確定的 | 確率的 | 明示=確定 / 暗黙=確率的 | +| 最小トークン | 1,024-4,096 | 1,024 | 1,024-4,096 | + +### 共通原則 + +- **全プロバイダでプレフィクスベース** +- A を送った後に A+B を送ると、A のキャッシュは保持され再利用される +- プレフィクスの途中を変更するとそこから先は再計算 +- これが Prune のキャッシュコストの根本原因 + +--- + +## OpenCode の Prune 関連 Issue + +OpenCode(sst/opencode)でも Prune とキャッシュの問題は未解決。 + +| Issue | 内容 | +|-------|------| +| [#3917](https://github.com/sst/opencode/issues/3917) | Prune のドキュメント要求。**キャッシュ無効化が caveat として明記** | +| [#21208](https://github.com/sst/opencode/issues/21208) | 固定閾値(40k/20k)が 1M コンテキストモデルに対して不適切 | +| [#20826](https://github.com/sst/opencode/issues/20826) | Prune で tool output 消去 → 孤立した tool_call で API エラー | +| [#19081](https://github.com/sst/opencode/issues/19081) | thinking 除去で KV キャッシュが毎ターン無効化 | +| [#4416](https://github.com/sst/opencode/issues/4416) | キャッシュトークン二重カウントで premature compaction | +| [#2945](https://github.com/sst/opencode/issues/2945) | Compaction でワーキングコンテキスト全喪失 | +| [#6535](https://github.com/sst/opencode/issues/6535) | サブエージェントが compaction 後にシステムプロンプトを喪失 | + +**「cache-aware な prune は未解決問題」** がコミュニティの共通認識。 +サードパーティの Dynamic Context Pruning プラグインも「prune はキャッシュプレフィクスを壊す」と認めている。 + +--- + +## Insomnia 設計への示唆 + +### 1. Prune は条件付きで実行すべき + +Claude Code の `clear_at_least` パターン: +- 「削れるトークン数がキャッシュ再計算コストを上回る場合にだけ prune」 +- 無条件 prune はキャッシュを無駄に壊す + +### 2. Compact は必須 + +- AutoCompact(83.5% 閾値)+ サーキットブレーカー(3回失敗で停止)は堅実な設計 +- 要約後の再注入(ファイル、プラン、メモリ)で作業継続性を確保 +- Anthropic のサーバーサイド compaction API は将来的に利用可能 + +### 3. キャッシュ階層を意識した設計 + +- system prompt は静的部分と動的部分を分離 +- ツール定義の変更はキャッシュ全体を無効化する → 動的ツール登録/削除はキャッシュコストがある +- messages 部分の変更は messages キャッシュのみ影響 + +### 4. 既知の落とし穴 + +- Prune で ToolCall の対応する ToolResult を消すと API エラー(OpenCode #20826) +- キャッシュトークンの二重カウントで premature compaction(OpenCode #4416) +- Compaction 後のコンテキスト喪失(計画、サブエージェント、スキーマ) +- Thrash loop(compaction 直後に再び閾値超過)の検出と停止が必要 diff --git a/docs/ref/llm-pricing-2026-04.md b/docs/ref/llm-pricing-2026-04.md new file mode 100644 index 00000000..726fe466 --- /dev/null +++ b/docs/ref/llm-pricing-2026-04.md @@ -0,0 +1,100 @@ +# LLM 料金サマリ + +調査日: 2026-04-19。料金は頻繁に改定されるため一次ソースで再確認。 + +## 定額サブスク + +| サービス | 月額 | 含まれる上限 | 実用性メモ | +|---|---|---|---| +| Claude Pro | $20 | Sonnet 中心、5h rolling + 週次 | Opus 4.7 はほぼ使えない | +| Claude Max 5x | $100 | Pro×5、Sonnet 常用可 | Sonnet をガッツリ書くなら最低ライン | +| Claude Max 20x | $200 | Pro×20、Opus 4.7 解禁 | Opus をエージェント用途で回すならここ | +| ChatGPT Plus | $20 | Codex 5h で GPT-5.4 が 20–100 msg | 長時間 agent loop では枯渇 | +| ChatGPT Pro | $100 | Codex 5h で 100–500 msg、**2026-05-31 まで 2x プロモ**で 600–3000 | Max 20x 相当の常用級 | +| Copilot Free | $0 | 50 premium/月、Haiku 4.5 / GPT-5 mini | お試し | +| Copilot Pro | $10 | 300 premium/月、GPT-5 mini/4.1/4o は乗数 0x(無制限扱い) | コスパ◎ | +| Copilot Pro+ | $39 | 1,500 premium/月、Opus 4.7 含む全モデル | 定額で Opus 4.7 最安 | +| Cursor Pro | $20 | $20 クレジット、fast 500/月、slow 無制限 | 2025/6 に usage-based へ移行 | +| Cursor Pro+ | $60 | Pro×3 クレジット | 詳細は公式ダッシュボード | +| Cursor Ultra | $200 | Pro×20 クレジット、Privacy Mode | Max 20x 競合 | +| Zed Pro | $10 | トークン課金、BYOK 可 | 実質「安い BYOK フロント」 | +| Windsurf Pro | $20(2026/3 値上げ) | クレジット制、Claude 系のみ BYOK 可 | Teams/Ent は BYOK 不可 | +| Ollama Pro | $20(年 $200) | 5h + 7d、Free×50、3 並列 | `:cloud` モデル (`gpt-oss:120b`, `deepseek-v3.1:671b` 等) | +| Ollama Max | $100 | Pro×5、10 並列 | ローカル + クラウド併用 | +| BLACKBOX Pro/Plus/Max | $10 / $20 / $40 | FUP 非公開 | Unlimited の実態不明 | +| xAI SuperGrok | $30 | Grok 4 + 2M ctx(Web/アプリ) | **API 枠なし** | +| xAI SuperGrok Heavy | $300 | Grok 4 Heavy 独占(Web/アプリ) | **API 枠なし** | +| X Premium+ | $40 | Grok Web 利用 | **API 枠なし** | + +## 無料枠・従量(少額) + +- **OpenRouter**: プリペイド方式、手数料 5.5%(暗号通貨 5%)。**BYOK は月 100 万 req 無料**、超過後は元コストの 5%。`:free` モデルは 50 req/日、$10 以上入金で 1000 req/日 +- **Google AI Studio (Gemini 2.5 Pro)**: 5 RPM / **100 RPD** / TPM 250k 共有。2025/12 に大幅減枠 +- **GitHub Models (GPT-4o 等)**: 10 RPM / 50 RPD / 8k in, 4k out / 2 並列 +- **Cerebras Free**: 30 RPM / **1M tokens/日**(Llama 3.3 70B, Qwen3, gpt-oss-120B) +- **Groq Free**: サインアップのみ(CC 不要)。Llama 3.3 70B は 30 RPM / 12k TPM / 1k RPD / 100k TPD、モデル別に上限異なる。**Developer tier**(要 CC)で Free の約 10× + Batch/Flex 解放 + daily cap 撤廃。月額プランは Enterprise のみ +- **DeepSeek API**: V3 $0.14/$0.28, R1 $0.55/$2.19 /1M tok。オフピーク 50–75% 割引、キャッシュ 90% 割引 +- **Moonshot Kimi**: Adagio 無料プランあり。K2.5 API は $0.60/$2.50 /1M tok +- **Together / Fireworks / DeepInfra**: サインアップクレジットのみ、恒久無料枠なし + +## 従量 API の単価比較(コーディング向け) + +| プロバイダ | モデル | 入/出 ($/1M tok) | Ctx | 備考 | +|---|---|---|---|---| +| Anthropic | Haiku 4.5 | $1.00 / $5.00 | 200k | 軽量・高速 | +| Anthropic | Sonnet 4.6 | $3.00 / $15.00 | 200k | 精度上位級 | +| Anthropic | Opus 4.7 | $5.00 / $25.00 | 200k | エージェント最強(旧 $15/$75 から値下げ) | +| xAI | grok-code-fast-1 | $0.20 / $1.50 | 256k | SWE-bench 70.8%、142 tok/s | +| xAI | grok-4-1-fast | $0.20 / $0.50 | 2M | 長文向け | +| xAI | grok-4 | $3.00 / $15.00 | 256k | 旗艦(旧) | +| Groq | GPT-OSS 120B | $0.15 / $0.60 | 128k | ~500 tok/s(LPU) | +| Groq | Kimi K2-0905 | $1.00 / $3.00 | 256k | open モデル最強級 | +| Groq | Llama 3.3 70B | $0.59 / $0.79 | 131k | 汎用 | +| Groq | Qwen3 32B | $0.29 / $0.59 | 131k | 汎用 | +| DeepSeek | V3 | $0.14 / $0.28 | 128k | オフピーク 50–75% 引、cache 90% 引 | +| DeepSeek | R1 | $0.55 / $2.19 | 128k | 推論系最安 | +| Moonshot | Kimi K2.5 API | $0.60 / $2.50 | — | 無料 Adagio プランあり | + +プロンプトキャッシュ: Anthropic / xAI / Groq すべて対応(50–90% 引)。長い system prompt を使うエージェントでは実効単価が大きく下がる。 + +## 従量回避派の推奨構成 + +### A. Claude Max 単騎 ($100) +Sonnet 4.6 中心で Claude Code + Web を併用。物足りなければ Max 20x ($200) に上げて Opus 4.7 を解禁。 + +### B. Copilot Pro+ + 無料枠併用 ($39) +1,500 premium/月 + GPT-5 mini/4.1/4o が乗数 0x 無制限。溢れを Gemini 2.5 Pro (100 RPD) / Cerebras (1M tok/日) に逃がす。Opus 4.7 は乗数 7.5x = 実質 200 回/月なので温存必須。 + +### C. OpenRouter 少額 + Ollama Cloud + 無料枠 ($20–30) +OpenRouter に $10 入金で `:free` が 1000 req/日に拡張。Ollama Pro ($20) で `gpt-oss:120b` / `deepseek-v3.1:671b`。補助に GitHub Models / Groq / Cerebras。BYOK 持ちなら月 100 万 req 無料の BYOK トンネルも。 + +### D. ChatGPT Pro Codex プロモ ($100、~2026-05-31) +Codex 2x プロモで Max 20x 相当の実行量。プロモ終了後(6月以降)は通常枠 100–500/5h に戻る点は要確認。 + +### E. ハイブリッド最安 ($30) +Zed Pro ($10) + Ollama Pro ($20) + 無料枠フル動員(Gemini / Cerebras / GitHub Models / Groq)。Zed は Sonnet をトークン課金、高頻度タスクはローカル/Ollama Cloud へ流す。 + +## 注記(要確認) + +- Claude Code の週次上限の具体値: Anthropic 公式ヘルプに正確な数値記載なし。Max 20x で Opus 4.7 が「概ね 24–40 時間/週」との二次情報あり +- Codex Pro 5x/20x の詳細と プロモ終了後の挙動 +- BLACKBOX Unlimited の実スループット(公式非開示) +- Cursor Pro+ / Ultra のクレジット実効回数(モデル API コスト依存で一律換算不能) + +## 一次ソース + +- https://claude.com/pricing +- https://developers.openai.com/codex/pricing +- https://github.com/features/copilot/plans +- https://docs.github.com/en/copilot/concepts/billing/copilot-requests +- https://ollama.com/pricing +- https://openrouter.ai/pricing +- https://openrouter.ai/announcements/1-million-free-byok-requests-per-month +- https://ai.google.dev/gemini-api/docs/rate-limits +- https://docs.github.com/github-models/prototyping-with-ai-models +- https://console.groq.com/docs/rate-limits +- https://api-docs.deepseek.com/quick_start/pricing +- https://docs.x.ai/developers/models +- https://grok.com/plans +- https://groq.com/pricing +- https://console.groq.com/docs/rate-limits diff --git a/docs/ref/llm-provider-landscape.md b/docs/ref/llm-provider-landscape.md new file mode 100644 index 00000000..2dcb13ed --- /dev/null +++ b/docs/ref/llm-provider-landscape.md @@ -0,0 +1,242 @@ +# LLM プロバイダ統合の外部事例 + +調査日: 2026-04-19。認証経路・`ollama launch` 等の時事的項目は陳腐化が早い。数値・URLは一次ソースで再確認すること。 + +## 各ハーネスのプロバイダ対応方式 + +### Zed +- ネイティブ: Anthropic / OpenAI / Google / Ollama / DeepSeek / Mistral / OpenRouter / Vercel AI Gateway +- `openai_compatible` スロットで任意プロバイダ追加 +- OpenRouter は**宣言型**: `available_models[]` に `max_tokens` / `supports_tools` 等の capability を書く。自動 discovery ではない +- Ollama は `auto_discover: bool` でローカル tag 自動列挙 +- https://zed.dev/docs/ai/llm-providers + +### OpenCode (sst/opencode) +- Vercel AI SDK + Models.dev で 75+ プロバイダ +- 認証は `~/.local/share/opencode/auth.json` に統一保存(OAuth / APIキー / その他の3種別) +- **2026-03-19 に Anthropic OAuth 対応を削除**(PR #18186)。詳細は後述 +- ChatGPT のブラウザ OAuth (`/connect`) は存続 +- https://opencode.ai/docs/providers/ + +### OpenClaw +- Peter Steinberger らの個人向け self-hosted AI(OpenCode と別物) +- 30+ プロバイダ対応 +- Anthropic 独自 OAuth 実装は縮小、現在は `claude -p` (Claude Code CLI) の subprocess 再利用を推奨 +- OpenRouter は「OpenAI 互換プロキシ」扱いで固有機能 (serviceTier, prompt-cache hints) は転送しない +- https://docs.openclaw.ai/concepts/model-providers + +### Aider / Cline / Roo / Continue.dev +- Aider は LiteLLM 経由、他は直叩き +- BLACKBOX 等マイナー系は OpenAI 互換枠で収容するのが一般パターン + +## 認証経路の現状 + +### Anthropic (Claude Pro / Max) ── 封鎖済み +- 2026-01-09: Anthropic がサーバ側で Pro/Max OAuth トークンに `This credential is only authorized for use with Claude Code and cannot be used for other API requests` の制限導入 +- 2026-02-19: Claude Code の Legal & Compliance ページに "Authentication and credential use" セクション追加。「OAuth 認証は Claude Code 等の通常利用のため専用」「Agent SDK を含む第三者製ツールで Free/Pro/Max 資格情報を経由するのは許可しない」と明記 +- 2026-03-19: OpenCode が PR #18186 で以下を削除 + - `packages/opencode/src/session/prompt/anthropic-20250930.txt`(Claude Code 風システムプロンプト) + - `opencode-anthropic-auth@0.0.13` ビルトインプラグイン + - `claude-code-20250219` beta ヘッダ +- 代替検討: `claude -p` (Claude Code の headless mode) を subprocess で呼ぶ方式。ACP ではなく素朴な CLI fork。Anthropic ToS 的には採用しない(明確な裁定なし) +- https://code.claude.com/docs/en/legal-and-compliance +- https://github.com/sst/opencode/pull/18186 + +### OpenAI (ChatGPT Plus / Pro via Codex CLI) ── 互換経路 +- Codex CLI は Apache-2.0。openai/codex Discussion #8338 で OpenAI 社員が fork・改変自由と明言 +- ChatGPT OAuth を他ツールから使う行為を service terms で名指し禁止する条項は未確認 +- OpenCode の `/connect` で ChatGPT ブラウザ認証が通る +- コミュニティ評価: 「Anthropic は walled garden、OpenAI はむしろ取り込みに来た」 +- https://github.com/openai/codex/discussions/8338 +- https://developers.openai.com/codex/auth + +### CLI fork 方式 (`claude -p`) +- `claude --print` / `claude -p` は Claude Code の非対話(headless)モード。プロンプトを stdin/引数で受け stdout に返す +- **ACP ではなく素朴な subprocess 呼び出し** +- OpenClaw と OpenCode コミュニティフォーク (`griffinmartin/opencode-claude-auth`) が採用 +- OAuth 経路ではないため 2026-01-09 のブロックは回避できるが、Anthropic ToS の「第三者ツールでの資格情報経由」禁止条項に抵触する可能性(明確な裁定なし) + +## Ollama の統合機構 + +### `ollama launch ` +- v0.14.0 (2026-01) 以降、Ollama サーバ本体に **Anthropic Messages API 互換レイヤー**が組み込まれた +- 別プロセスのプロキシは起動しない。Ollama コア (`localhost:11434`) が `/v1/messages` 相当を喋る(streaming / system prompt / tool calling / extended thinking / vision 対応) +- `ollama launch claude` はサブコマンドバイナリを fork し、子プロセスに env を注入 + - `ANTHROPIC_BASE_URL=http://localhost:11434` + - `ANTHROPIC_AUTH_TOKEN=ollama` + - `ANTHROPIC_API_KEY=`(空) +- 設定ファイル書き換えやシェル rc 操作はしない +- `ANTHROPIC_BASE_URL` はハードコードで `OLLAMA_HOST` を見ない(issue #13936) +- サブコマンド `claude` / `codex` / `opencode` / `droid` / `clawdbot` は「ツール毎の env 注入テンプレート」の集合体 + - 例: `opencode` は `OPENCODE_CONFIG_CONTENT` に JSON を流し込み OpenCode 側で deep-merge + - `codex` は OpenAI 互換 (`/v1/chat/completions`) を向けるので Anthropic 系ではなく OpenAI 系 env を設定 +- https://ollama.com/blog/launch +- https://ollama.com/blog/claude +- https://github.com/ollama/ollama/issues/13936 + +### Ollama クラウド +- `ollama signin` は OAuth ではなく **Ed25519 鍵ペア登録**。`~/.ollama/id_ed25519`(秘密鍵)と `.pub`(公開鍵)をローカル保存、ブラウザで `ollama.com/connect` から公開鍵をアカウントに紐付け +- ルーティング: `server/routes.go` の `GenerateHandler` / `ChatHandler` がモデル名をパースし、`:cloud` サフィックスなら `server/cloud_proxy.go` 経由で `ollama.com:443` に中継 +- 署名: タイムスタンプ付きチャレンジを Ed25519 秘密鍵で署名し `Authorization` ヘッダに載せる +- レスポンスは 32KB バッファで chunk 中継 +- **クライアントは `localhost:11434` のままでよい**。ローカル Ollama デーモンが透過プロキシ役を果たす +- `ollama.com/api` を直叩きしたいときのみ `OLLAMA_API_KEY` を別途発行 +- https://github.com/ollama/ollama/blob/main/server/cloud_proxy.go + +## OpenAI 互換プロバイダ事例 + +「共通 OpenAI 互換枠」で収容する代表的プロバイダ。`base_url` + API key 差し替えで動く。 + +### xAI (Grok) +- `base_url`: `https://api.x.ai/v1`(OpenAI SDK 互換、Anthropic SDK 互換レイヤーも提供) +- 主要モデル: + - `grok-code-fast-1` — 256K ctx、$0.20/$1.50 /1M tok、SWE-bench 70.8%、142 tok/s(Sonnet の 1/10 以下の単価) + - `grok-4-1-fast` — 2M ctx、$0.20/$0.50 /1M tok(長文向け差別化) + - `grok-4` — 256K ctx、$3.00/$15.00 /1M tok +- プロンプトキャッシュあり(50–75% 引)、Batch API 50% 引 +- 認証は API key のみ、OAuth なし +- サブスク(X Premium+ / SuperGrok / SuperGrok Heavy)は UI 専用で **API 枠を含まない** +- Zed / OpenCode / Cline / Cursor に第一級サポートあり +- https://docs.x.ai/developers/models + +### Groq +- `base_url`: `https://api.groq.com/openai/v1`(OpenAI SDK 互換) +- 特徴: LPU で TTFT <100ms、モデル別 TPS 500–1000(`gpt-oss:120b` で 500 tok/s 等) +- 主要モデル: + - GPT-OSS 120B — $0.15/$0.60 /1M tok、128K ctx + - Kimi K2-0905 — $1.00/$3.00、256K ctx(open モデル最強クラス) + - Llama 3.3 70B Versatile — $0.59/$0.79、131K ctx + - Qwen3 32B — $0.29/$0.59、131K ctx +- tool_use / JSON mode / streaming すべて OpenAI 互換 +- プロンプトキャッシュ 50% 引、Batch API 50% 引 +- **Qwen3-Coder-480B は未提供**。Coder 用途は Together / Cerebras 等で補完する前提 +- Cerebras 比較: GPT-OSS 120B で Cerebras ~3000 tok/s vs Groq ~476 tok/s。throughput は Cerebras、TTFT は Groq +- 月額サブスクは Enterprise のみ、通常は従量 +- https://groq.com/pricing +- https://console.groq.com/docs/api-reference + +### BLACKBOX AI +- `base_url`: `https://cloud.blackbox.ai/api` (OpenAI 互換 REST)、Bearer `bb_*` トークン +- 主要ハーネスに専用サポートなし。共通枠で登録するのが実用 +- Unlimited プランは FUP 非公開で実態不明 +- https://docs.blackbox.ai/api-reference/authentication + +### OpenRouter +- `base_url`: `https://openrouter.ai/api/v1` +- 多数のプロバイダを単一 API で束ねる(料金は passthrough + プリペイド手数料 5.5%) +- BYOK: 月 100 万 req 無料、超過で元コストの 5% +- `:free` モデル 50 req/日($10 入金で 1000 req/日) +- https://openrouter.ai/docs + +## プロバイダ独自拡張 + +各プロバイダが OpenAI 互換エンドポイントに載せている独自機能。詳細は `llm-pricing-2026-04.md` と合わせて参照。 + +### OpenAI 本家 +- `/v1/responses` stateful API、`previous_response_id`、`reasoning.effort` (minimal/low/medium/high/xhigh)、`reasoning.summary: "auto"`、`store=false` / ZDR 組織で強制 +- 高次ツール: `web_search` / `code_interpreter` / `computer_use` +- `max_tokens` → `max_completion_tokens` (o系) → `max_output_tokens` (Responses) のリネーム + +### xAI (Grok) +- OpenAI + Anthropic 両 SDK 互換(**Anthropic 互換は deprecated**、native 推奨) +- `reasoning_effort: low/high` は Grok-3 mini 系のみ、Grok-4 系は常時 reasoning +- **Deferred Chat Completions** で `request_id` による非同期取得 +- Live Search + +### Groq +- `service_tier: on_demand / flex / auto`(flex は 10x rate、失敗許容、paid 限定) +- **prompt caching は自動・追加料金なし・コード変更なし** +- `reasoning_format` で reasoning の提示方式制御(JSON 出力との両立) + +### DeepSeek +- `deepseek-reasoner` はレスポンスに **`reasoning_content` 別フィールド** +- **reasoner では function calling 非対応** +- 入力に `reasoning_content` を含めると 400 +- 自動 prefix cache (64 tok 単位、hit で 90% 割引) + +### Cerebras / Together AI +- JSON schema / tool calling / multi-turn tool calling / streaming + structured を全モデル対応 + +### Fireworks +- **Grammar mode (BNF)** が独自、任意出力形式を制約可能 + +### OpenRouter +- `provider` でルーティング/フォールバック、`transforms` で自動中抜き +- **`reasoning` 統一インターフェース**で各社差を吸収 + +### Ollama +- `/v1/chat/completions` は **"experimental"** 明記 +- **stream + tools で単一ブロック返却のバグ** (#9092) +- context size は Modelfile の `num_ctx` で固定(API 側で設定不可) +- native `/api/chat` の方が機能フル +- v0.14+ で `/v1/messages` (Anthropic 互換) も追加 + +### Azure OpenAI +- URL が `{resource}/openai/deployments/{name}/chat/completions?api-version=...`、**model_id ではなく deployment 名** +- content filter が `finish_reason: content_filter` や 400 で観測 + +## Capability 軸 + +モデル/プロバイダごとの機能差を表現する軸。**プロバイダ側高次ツール (web_search / code_interpreter / computer_use / Live Search) は insomnia では使用しない方針**のため capability 軸から除外。 + +### 1. tool calling +parallel tool calls 可否、tool_choice 対応度。DeepSeek reasoner のような「reasoner + tool 非対応」ケースあり。 + +### 2. structured output +- `json_object` のみ +- `json_schema` 対応 +- Grammar (Fireworks 独自) + +### 3. reasoning +- `reasoning.effort` (OpenAI / xAI) +- `thinking.budget_tokens` (Anthropic) +- `reasoning_content` 別フィールド出力 (DeepSeek) +- `reasoning_format` (Groq) +- OpenRouter は `reasoning` に統一投影 + +### 4. vision +モデル依存度が大 + +### 5. prompt caching +呼び出し側ロジックから見て 2 値に集約: +- **Explicit**: `cache_control` マーカー挿入(Anthropic のみ) +- **Auto**: scheme はマーカー挿入しない(OpenAI / DeepSeek / Groq の自動 prefix も、サーバ側で何もしないケースも同じ扱い) + +### 付随パラメータ +- `max_tokens` 名の差(`max_tokens` / `max_completion_tokens` / `max_output_tokens`)、Anthropic は必須 +- stateful (`previous_response_id`) は OpenAI Responses のみ +- streaming SSE の delta 粒度・usage 到達タイミング差 + +## ToolCall streaming の差異 + +| プロバイダ | ToolCall 引数の streaming | +|---|---| +| Anthropic | `input_json_delta` partial streaming — 安定 | +| OpenAI (chat) | `tool_calls[i].function.arguments` partial — 安定、parallel 時は複数 index 並走 | +| OpenAI Responses | reasoning item が別構造(`summary[]` / `encrypted_content`) | +| xAI / Groq / DeepSeek | OpenAI 互換、chat と同じ | +| Gemini | function_call は **delta なし、一括で返る** | +| Ollama `/v1` | **stream + tools で delta が欠けるバグ** (#9092) | +| Ollama `/api/chat` | native API、安定 | + +→ Gemini / Ollama `/v1` は scheme アダプタで「BlockStart → InputJson(全体 1 回) → BlockStop」の**擬似ストリーム化**で共通化可能。 + +## insomnia での採用方針 + +### 第一級サポート(専用アダプタ) +- **Ollama API** — ローカル + `:cloud` サフィックスで透過的にクラウド中継。エンドポイントは `localhost:11434` で統一 +- **Codex OAuth 経路** — `~/.codex/auth.json` を読み ChatGPT 枠を利用。Codex CLI 互換(Apache-2.0、社員が fork 自由と明言、ToS に名指し禁止なし) +- **Anthropic API** — 従量 API key 経路のみ + +### 二次サポート(共通 OpenAI 互換枠) +- `provider_kind: openai_compatible` + `base_url` + `available_models[]` の共通スロットで OpenRouter / xAI / Groq / Together / Fireworks / DeepInfra / BLACKBOX 等を一括収容 +- ルーター系は後追いで追加しやすい宣言型設計 + +### 非サポート +- **Claude Pro/Max OAuth 経路** — 2026-01-09 サーバ側ブロック、2026-02-19 ToS で明文禁止。リスクが第一級機能に見合わない +- `claude -p` CLI fork も同様に採用しないなので実装しない + +### 実装原則 +- 認証アダプタ(外部 CLI の認証ストアを読む類)は llm-worker 直下ではなく上位アダプタ層に配置。llm-worker は低レベル基盤に留める原則(project memory)と整合 +- モデル列挙は `auto_discover` と宣言型の両輪。Ollama は自動、ルーター系は宣言 +- `ollama launch insomnia` 対応を視野に入れ、env 注入 (`ANTHROPIC_BASE_URL` / `OPENAI_BASE_URL` 等) で起動設定を受け入れる作り diff --git a/docs/ref/memory-systems.md b/docs/ref/memory-systems.md new file mode 100644 index 00000000..fab68476 --- /dev/null +++ b/docs/ref/memory-systems.md @@ -0,0 +1,456 @@ +# エージェント向けメモリ機構の外部事例 + +調査日: 2026-04-21。本ドキュメントはユーザー依頼の3ソース(OpenAI Codex Chronicle / Shann³ の "AI Knowledge Layer" スレッド / Nous Research Hermes Agent)を中心に、直近で公開されたメモリ機構をまとめ、insomnia に入れる際の比較材料とすることを目的とする。2026年前半は各所からメモリ実装が同時多発的に登場しているので、「どの事例がどのレイヤを担っているか」を見失わないよう、各節で**何を記憶するか/いつ書くか/どう引き出すか/何に保存するか**を揃えて整理する。 + +数値・URLは一次ソースで再確認すること。挙動は研究プレビュー段階のものが多く、変わる前提で読む。 + +--- + +## 1. OpenAI Codex — Memories & Chronicle + +Codex CLI に 2026-03 頃から追加された "Memories" 機能と、2026-04-15 頃に macOS 向け研究プレビューとして乗った "Chronicle"。スクリーン内容まで観測対象に広げた点が目新しいが、**コアは素朴な 2 フェーズの要約パイプライン**で、実装の示唆が多い。 + +### データフロー(memories 本体) + +セッション開始時に走る 2-phase パイプライン。`codex-rs/core/src/memories/` を直接読み確認した挙動: + +- **Phase 1 — Extraction**(`phase1.rs`): 対象 rollout ごとにモデル呼び出しで **JSON schema 強制**の `StageOneOutput { raw_memory, rollout_summary, rollout_slug }` を返させる(`#[serde(deny_unknown_fields)]`)。`CONCURRENCY_LIMIT = 8` で並列実行、結果は SQLite の `stage1_outputs` テーブルに格納。`StateRuntime` の job leasing で duplicate work 防止 + - モデル: `gpt-5.4-mini` / Low reasoning(`memories.extract_model` で override 可) + - テンプレ: `codex-rs/core/templates/memories/stage_one_system.md` + `stage_one_input.md` +- **Phase 2 — Consolidation**(`phase2.rs`): **singleton** として走る(`jobs` テーブルで `kind = 'memory_consolidate_global'`, `job_key = 'global'` を `wx` 相当に claim)。`Phase2InputSelection` で前回 baseline との **added / retained / removed** 差分を計算し、その差分を prompt に埋めて sub-agent に投入 + - 出力は **自由形式 Markdown**(JSON schema なし)。sub-agent が memory_root を cwd に持ち、`MEMORY.md` / `memory_summary.md` / `skills//` を直接書き換える + - モデル: `gpt-5.4` / Medium reasoning(`memories.consolidation_model` で override 可) + - Heartbeat 90s / lease 3600s、失敗時 `retry_remaining` デフォルト 3 + - テンプレ: `codex-rs/core/templates/memories/consolidation.md`(836 行の詳細 instructions) +- 生成タイミングは「スレッドが十分アイドルになってから」(age + idle window で判定) +- `memory_summary.md` が **5,000 tokens cap** で system prompt に注入される(`MEMORY_TOOL_DEVELOPER_INSTRUCTIONS_SUMMARY_TOKEN_LIMIT`) + +**非対称の肝**: 抽出 (Phase 1) は structured output で分類を強制、統合 (Phase 2) は sub-agent に自由書き込みさせる。分類ブレを Phase 1 で封じ、統合の柔軟性は Phase 2 の agentic 判断に委ねる、という役割分担。insomnia の 2 フェーズ設計の直接の元ネタ。 + +### 保存場所 / 形式 + +- `$CODEX_HOME/memories/`(既定 `~/.codex/memories/`)配下に Markdown。 +- `memory_summary.md` を中心に「summaries / durable entries / recent inputs / supporting evidence」の Markdown を束ねる構成。 +- Chronicle 派生メモリは `$CODEX_HOME/memories_extensions/chronicle/` に分離。 +- スクリーンキャプチャ中間物は `$TMPDIR/chronicle/screen_recording/` に置かれ、running 中 6h で自動削除。サーバー側には保存しない(法的義務時を除く)。 + +### 設定 + +- `memories.generate_memories` / `memories.use_memories`: 生成 / 注入の on-off。 +- `memories.extract_model`: Phase 1 に使う軽量モデル。 +- `memories.consolidation_model`: Phase 2 のマージ側モデル(既定で reasoning 系)。 +- セッション内 `/memories` コマンドでスレッド単位に無効化可能。 +- シークレットは生成時に自動 redaction。 + +### Chronicle 固有 + +- 画面スクリーンショット + OCR テキスト + タイミング + ローカルファイルパスを入力に、**サンドボックス化した Codex セッション**をバックグラウンドで回して Markdown メモリを吐く。 +- 「Chronicle は rate limit を激しく消費する」「画面由来の prompt injection リスクが増える」「ローカル unencrypted」が公式に注意書きされている。Pro / macOS / 非EU+UK+CH 限定。 +- 可搬性の観点では、生成物が単なる Markdown ファイルでユーザーが手編集できる点が重要。 + +### 設計上の示唆 + +- **圧縮は reasoning モデルで非同期に走らせる**: 会話ループの応答性を落とさず、コストも分離する。 +- **中間テーブル(SQLite)と最終出力(Markdown)の 2 段構成**: 解析容易性と人間編集性を両立。 +- **5k tokens 固定注入**: 動的 retrieval ではなく "常駐 summary" として使う素朴さ。 +- **拡張はサブディレクトリで**: `memories_extensions//` というパターンで観測チャンネルを増やす構造。 + +一次ソース: +- https://developers.openai.com/codex/memories +- https://developers.openai.com/codex/memories/chronicle +- https://deepwiki.com/openai/codex/3.7-memory-system +- https://9to5mac.com/2026/04/20/codex-for-mac-gains-chronicle-for-enhancing-context-using-recent-screen-content/ + +--- + +## 2. Shann³ "AI Knowledge Layer" + +https://x.com/shannholmberg/status/2044111115878326444 で提唱している、**エージェントより先に読ませる知識層**という枠組み。エンジニア向けというよりマーケター / コンテンツ運用者向けだが、構造はかなり insomnia に転用しやすい。 + +### 2 層構造 + +- **Knowledge Base Layer (KBL) — 動的** + - 生素材(tweet / 記事 / ブックマーク / PDF / ノート / ボイスメモ)を raw フォルダへ投げ込む。 + - 別エージェントが読んで種類別に分類、相互参照つきの構造化 wiki ページ化、マスターインデックスを維持。 + - 毎日少しずつ賢くなる前提。 + +- **Brand Foundation (BF) — 静的** + - 声のルール / 視覚スタイル / ポジショニング / オーディエンス定義 / 禁則語などを**ユーザーだけが編集**。 + - エージェントは生成前に必ず BF を読む。書き換えはしない。 + - 別ソースの続編 ("Ronin runs 10 social accounts ... 17 markdown files") でも同じパターン。KBL + BF + エージェント 1 本で 10 アカウント運用している。 + +### ルーツ: Karpathy の llm-wiki + +Shann³ の構成は Karpathy が 2026-04 に公開した `llm-wiki` gist をそのまま応用したもの。構造は 3 層: + +- `raw/` — 人間のみ書き込む生ソース。 +- `wiki/` — LLM 所有、page / index / glossary / log を自動維持。1 ソース取り込みで 10-15 ページが同時更新される。 +- `CLAUDE.md`(または `AGENTS.md` / `GEMINI.md`)— ページ種別・ワークフロー・フォーマット・健全性チェックルールを定義するスキーマ。 + +運用は3種: + +- **ingest**: 新規ソース投入 → 要約ページ作成 → index 更新 → entity/concept 更新 → `log.md` 追記。 +- **query**: wiki を検索し citation 付きで答える。意義ある合成は `syntheses/` として保存。 +- **lint**: 矛盾・stale claim・孤立ページ・参照抜け・データ欠落の定期点検。 + +`SamurAIGPT/llm-wiki-agent` がこのパターンを具体化しており、ディレクトリ構造が示唆的: + +``` +wiki/ +├── index.md (全ページのカタログ) +├── log.md (append-only 作業記録) +├── overview.md (生きた合成ビュー) +├── sources/ (原文1つ = 1サマリ) +├── entities/ (人・会社・プロジェクト) +├── concepts/ (考え方・フレーム・手法) +└── syntheses/ (query 回答をページ化) +graph/ +├── graph.json (SHA256 キャッシュ付きノード/エッジ) +└── graph.html (vis.js で可視化) +``` + +スラッシュコマンド例: +``` +/wiki-ingest raw/papers/my-paper.md +/wiki-query "what are the main themes?" +/wiki-lint +/wiki-graph +``` + +### LLM Wiki vs RAG という論点 + +MindStudio の比較記事が要点をまとめている。 + +- **LLM Wiki が有利**: 文書 100 本未満、構造化コンテンツ、精度・監査性重視、速度(Git で versioning できる)。 +- **RAG が必要**: 1,000 本以上、非構造テキスト、open-ended な質問、未知のドメイン。 +- 決定論的 wikilink retrieval と、SHA256 キャッシュで差分のみ再処理。 + +### 設計上の示唆 + +- **可変 KBL と不変 BF の分離**が強い。insomnia なら前者が Chronicle 的な自動メモリ、後者が `AGENTS.md` / 人間のガイド。 +- **retrieval を埋め込みではなく決定論的 wikilink でやる**アプローチは、少量ドメインで意外と強い。 +- エージェントに書かせる `log.md` と `index.md` の append-only 運用は、後で diff / git で検証しやすい。 + +一次ソース: +- https://x.com/shannholmberg/status/2044111115878326444 +- https://x.com/shannholmberg/status/2043307903822844087 (Ronin / 17 markdown files) +- https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f +- https://github.com/SamurAIGPT/llm-wiki-agent +- https://www.mindstudio.ai/blog/llm-wiki-vs-rag-knowledge-base + +--- + +## 3. Nous Research Hermes Agent + +Self-improving agent を名乗るフレーム。メモリ周りは **3 層 + クローズドな学習ループ**。 + +### 3 層メモリ + +- **Persistent Memory**: `MEMORY.md` / `USER.md` の Markdown + SQLite + FTS5 による過去セッション全文検索。char limit が hard-coded で **`MEMORY.md` 2,200 chars / `USER.md` 1,375 chars**(合計 ≒ 1,300 tokens)、system prompt 起動時スナップショット +- **Skill Library**: 複雑タスクの帰結として skill 化。`agentskills.io` 標準準拠(§5)、`~/.hermes/skills//SKILL.md` に保存。`skill_manage` tool (action: create/patch/edit/delete/write_file/remove_file) 経由で agent が自ら CRUD する +- **User Model (Honcho)**: "dialectic user modeling" でセッションを跨いでユーザー像を蓄積(外部 Honcho サービス連携) + +### 学習ループ(`run_agent.py` 実装より) + +既存 ref の「5 + tool call で自律生成」「15 tool call ごと reflection」は誤り。実装を読むと: + +- `_skill_nudge_interval = 10`(デフォルト、設定可)で **skill review**、`_iters_since_skill` がツール呼び出しのたび increment +- memory nudge は別に **10 turns** で発火 +- 閾値超過で **background thread に別 AIAgent を fork**(max_iterations = 8)、review prompt を与える +- レビュー本体は **1 LLM call** の自由形式、続いて agent が必要なら `skill_manage` / `memory` tool を呼んで書き戻す +- review prompt 原文(`_SKILL_REVIEW_PROMPT`, `run_agent.py:2738-2746`): + > Review the conversation above and consider saving or updating a skill if appropriate. + > Focus on: was a non-trivial approach used to complete a task that required trial and error... + > **If nothing is worth saving, just say 'Nothing to save.' and stop.** +- 末尾の "Nothing to save." 指示が肝。頻繁発火でも中身ゼロの場合は NOP で抜ける設計。insomnia Phase 1 の「空配列許容」の直接ソース + +### 書き込み機構 + +- `skill_manage` tool: OpenAI function-calling schema。frontmatter YAML 検証、security scan、atomic write (temp + `os.replace()`) +- `memory` tool: target = `memory | user`、action = `add | replace | remove`。fcntl/msvcrt で file lock、entry-based rewrite +- FTS5: `messages_fts` virtual table + INSERT/DELETE/UPDATE trigger で auto-sync、CJK は LIKE フォールバック + +### 実装観点 + +- 主要ディレクトリ: `agent/` `skills/` `tools/` `gateway/` `hermes_cli/` `tui_gateway/` `cron/` `plugins/` `optional-skills/` など +- バックエンド抽象: local / Docker / SSH / Daytona / Singularity / Modal。Daytona / Modal ではアイドル時 hibernation で永続化コストを削る +- OpenClaw からの移行: `hermes claw migrate` + +### 設計上の示唆 + +- **"skill"= procedural memory として episodic / semantic から分離する**のは insomnia にとっても綺麗。Claude Code の skill と接続できる余地がある。 +- **FTS5 + LLM 要約ハイブリッド**で、ベクタを入れずにそこそこ回せる事例として参考価値が高い(少量ドメインなら LLM Wiki 論と同じ示唆)。 +- **Honcho 的 user model** を semantic profile として固定注入する運用は、Codex の memory summary と形式的に同じ。 + +一次ソース: +- https://github.com/NousResearch/hermes-agent +- https://github.com/NousResearch/hermes-agent/blob/main/README.md +- https://hermes-agent.nousresearch.com/docs/ +- https://hermes-agent.nousresearch.com/ + +--- + +## 4. 周辺事例(比較のため) + +### Letta / MemGPT + +- 4 区画: **Message Buffer**(直近)、**Core Memory**(in-context の編集可能 block: user prefs / persona)、**Recall Memory**(全履歴の検索)、**Archival Memory**(ベクトル or グラフ DB に外部化された宣言的知識)。 +- OS メタファ: context window = RAM、外部ストア = Disk。エージェントが function call で階層間を移動させる。 +- memory block = `{label, description, value, char_limit}`。 +- **sleep-time agent** が idle 中に非同期で block を書き換える。 +- context 限界近くで要約による eviction → 再帰要約で古いメッセージを累進圧縮。 + +### Cloudflare Agent Memory (2026-04 Agents Week) + +- 分類: **Fact / Event / Instruction / Task**(Task のみベクトル索引除外)。 +- 基盤: Durable Objects(プロファイル毎の SQLite、FTS・supersession chain・tx write を担う)、Vectorize(セマンティック)、Workers AI(Llama 4 Scout: 構造化、Nemotron 3: 自然文合成)。 +- API: + ```javascript + const profile = await env.MEMORY.getProfile("my-project"); + await profile.ingest([messages], { sessionId }); + await profile.remember({ content, sessionId }); + const results = await profile.recall("query"); + ``` +- Ingest は 10k 文字チャンク + 2 メッセージ重複、並行 4 チャンク。Verification が 8 種類(entity / temporal / factual 支援)。 +- Retrieval は 5 経路並行(FTS Porter stemming / exact fact-key / raw message / ベクトル直 / HyDE)を Reciprocal Rank Fusion でマージ。fact-key 優先、recency タイブレーク。時制クエリは regex + 算術で決定論的処理。 +- SHA-256 content-addressed ID で冪等。 +- 「メモリはあなたのもの」エクスポート前提。 + +### LinkedIn Cognitive Memory Agent (CMA) + +- 3 層: **Episodic**(対話履歴)/ **Semantic**(構造化事実)/ **Procedural**(学習済みワークフロー)。 +- 共有メモリ基盤として planner / reasoner / executor 複数エージェントが同じ層を参照。 +- Recent context retrieval + semantic search + summarization compaction で階層管理。 +- 高 stake 用途は human validation フローを挟む。 + +### OpenClaw + +Peter Steinberger が 2025-11 に出したメッセージングファースト agent。2026-02 に本人は OpenAI 入社、プロジェクトは foundation へ移譲。Hermes Agent の `hermes claw migrate` はここからの移行導線。設計は **Markdown ファイルのみに全状態を持つ**という極端な透明性志向で、insomnia の「ファイル + git」方針との親和性が最も高い。 + +Agent Workspace(`~/.openclaw/workspace/`)構成: + +| ファイル | 役割 | +|----------|------| +| `AGENTS.md` | 運用指示とメモリ使用ガイドライン | +| `SOUL.md` | ペルソナ・口調・境界 | +| `USER.md` | ユーザー識別と呼称の好み | +| `IDENTITY.md` | エージェント名・絵文字 | +| `TOOLS.md` | ローカルツールのメモ | +| `MEMORY.md` | 長期記憶(durable facts / preferences / decisions)。DM セッション起動時に常駐ロード | +| `memory/YYYY-MM-DD.md` | 日次ノート。**当日 + 前日のみ**自動ロード | +| `DREAMS.md` | dreaming sweep の人間レビュー用サマリ(任意) | +| `skills/` | workspace 固有 skill override(最高優先) | + +エージェントが触るツール: + +- `memory_search`: 文言違いでも引ける semantic search +- `memory_get`: ファイル or 行範囲の読み取り + +メモリ更新のタイミング(`extensions/memory-core/src/` 実装より): + +- **Compaction 前の silent turn**: `before_agent_reply` フックで `DREAMING_SYSTEM_EVENT_TEXT` を検出、`runShortTermDreamingPromotionIfTriggered` が発火。エージェントへの write-back 指示ではなく、**後述の dreaming pass をインラインで走らせる**仕組み +- **Dreaming pass (optional)**: 実体は **3 phase** (Light + Deep + REM)、cron default `"0 3 * * *"` UTC + - **Light**: 最近の recall / daily / session signals を short-term store に ingest + narrative 生成(**subagent LLM call 1 回**、`NARRATIVE_SYSTEM_PROMPT` で poetic な dream diary) + - **Deep**: `applyShortTermPromotions()` が promotion を決定。**LLM call ゼロ**、以下の 6 重みスコアと 3 ゲートで機械判断 + - 重み: frequency (0.24) / relevance (0.30) / diversity (0.15) / recency (0.15) / consolidation (0.10) / conceptual (0.06) + phase boost + - ゲート: `minScore 0.8` / `minRecallCount 3` / `minUniqueQueries 3` + - **REM**: テーマ反映 + narrative LLM call 1 回 + - promotion 通過項目のみ `MEMORY.md` に append、`DREAMS.md` に人間レビュー用 diary +- **書き込み制約**: `MEMORY.md` は hardcoded path、`memory/YYYY-MM-DD.md` のみ許可(regex `SHORT_TERM_PATH_RE`)。エージェントへ expose されているのは `memory_search` / `memory_get` の **read-only**。書き込みは系統内部のみ +- **Lock**: `memory/.dreams/short-term-promotion.lock` を `wx` フラグで exclusive create、60s stale 検出 + 10s wait timeout、in-process の Map も併用 +- **モデルが "覚えている" のはディスクに書かれた内容だけ**、という明示ポリシー。隠れた state 無し + +**insomnia にとって重要**: consolidation を LLM 依存から切り離せる見本。narrative は subagent が生成するが、promotion の判断は純機械(scoring)。insomnia の plan では Scope 外(Phase 2 は当面 agent 委任)だが、成熟したカテゴリから決定論的 promotion に差し替える upgrade path の参考になる。 + +設計上の示唆: + +- Workspace = git リポジトリ 1 本で完結、配置もフラット。insomnia の pod workspace 概念にそのまま借用できる。 +- 秘密は workspace **外**の `~/.openclaw/` 側(auth / credentials / session transcripts / managed skills)に退避する分離設計は、pod sandbox 境界を越えない運用の手本になる。 +- `memory/YYYY-MM-DD.md` の日次切り分け + 「当日+前日のみ load」は、時系列の自然減衰を Markdown で素直に表現できる良い pattern。 + +### LangChain Agent Builder memory(参考) + +- 記事は 301 redirect のため未取得。`https://www.langchain.com/blog/how-we-built-agent-builders-memory-system` に原文あり。semantic / episodic / procedural 三分類は共通。 + +一次ソース: +- https://www.letta.com/blog/agent-memory +- https://blog.cloudflare.com/introducing-agent-memory/ +- https://www.infoq.com/news/2026/04/linkedin-cognitive-memory-agent/ +- https://github.com/openclaw/openclaw/blob/main/docs/concepts/memory.md +- https://docs.openclaw.ai/concepts/agent-workspace +- https://en.wikipedia.org/wiki/OpenClaw + +--- + +## 5. Agent Skills 標準(procedural memory の実装単位) + +Hermes / OpenClaw / Claude Code / OpenAI Codex / Cursor / GitHub Copilot 等が揃って採用している **`agentskills.io` オープン標準**。Anthropic が 2025-12 に発表し、2026-03 時点で事実上の共通フォーマットになっている。memory 設計の「手続き的記憶 (procedural memory)」のほぼ全てがこの単位で流通するので、insomnia でも独自フォーマットを避けて素直にこれに乗るのが合理的。 + +### SKILL.md の最小仕様 + +``` +skill-name/ +├── SKILL.md # 必須: frontmatter + Markdown 本文 +├── scripts/ # 任意: 実行可能コード +├── references/ # 任意: 詳細ドキュメント +└── assets/ # 任意: テンプレ・画像・lookup など +``` + +```markdown +--- +name: pdf-processing +description: Extract PDF text, fill forms, merge files. Use when handling PDFs. +license: Apache-2.0 +metadata: + author: example-org + version: "1.0" +--- +``` + +frontmatter 仕様: + +| Field | 必須 | 制約 | +|-------|------|------| +| `name` | ○ | 64 字以下、小文字英数とハイフンのみ、親ディレクトリ名と一致 | +| `description` | ○ | 1024 字以下。**何をするか + いつ使うか**の両方を含める | +| `license` | ― | 任意 | +| `compatibility` | ― | 500 字以下。env 依存(特定 CLI / Python 3.14+ 等)がある場合 | +| `metadata` | ― | 任意 key/value | +| `allowed-tools` | ― | 実験的。space-separated `Bash(git:*) Read` 等 | + +### Progressive disclosure(これが核) + +Skill は 3 段でロードされる: + +1. **Metadata (~100 tokens)**: `name` + `description` のみ、全 skill が起動時に常駐 +2. **Instructions (<5k tokens 推奨)**: skill が発動した時に本文がロード +3. **Resources**: `scripts/` `references/` `assets/` は必要時のみ個別に読まれる + +→ **常駐コストは「名前 + 一行説明」だけ**で、本体は遅延ロード。これがメモリ設計における宣言的知識 (LLM Wiki の `index.md` 常駐) と完全に同型で、**知識 / skill を同じ「目次のみ常駐・本体リンクで遅延」モデルに統一できる**。 + +### Claude Code の拡張 + +標準 + α として Claude Code が独自に足している項目: + +- `disable-model-invocation: true`: ユーザーしか起動できない(deploy / commit 等の副作用ありタスクに使う) +- `user-invocable: false`: エージェントしか起動しない(背景知識用。`/` メニューから隠れる) +- `paths: ["src/**/*.ts"]`: **ファイル glob マッチ時のみ自動発動**。関心スコープを暗黙に絞る +- `context: fork` + `agent: Explore`: サブエージェントの fork context で実行(isolated context) +- `allowed-tools`: skill 有効時だけ事前承認されたツール +- `hooks`: skill ライフサイクルにフックを紐付け +- `$ARGUMENTS` / `${CLAUDE_SESSION_ID}` / `${CLAUDE_SKILL_DIR}` による文字列置換 +- `` !`` `` でプリプロセッサとしてシェルを走らせ、結果を本文に差し込む + +Claude Code の skill 格納階層(上から優先): + +``` +enterprise (managed) > personal (~/.claude/skills/) > project (.claude/skills/) > plugin (/skills/) +``` + +プラグイン skill のみ `plugin-name:skill-name` の namespace を持つ。Live change detection(ディレクトリ監視)と monorepo 対応のネストディレクトリ自動検出もある。 + +Skill content のライフサイクルは重要で、**一度発動すると rendered 内容が会話に単一メッセージとして入り、以後再読み込みされない**。auto-compaction 時は各 skill の直近 invocation だけが冒頭 5,000 tokens 維持され、全 skill 合算 25,000 tokens の予算内で新しい順に残す。つまり「skill の本体は session 中ずっと context に居座る」前提で書く必要がある。 + +### insomnia への示唆 + +- **procedural memory は SKILL.md で表現**する。`.claude/skills/` に人間手入れの skill を置き、エージェントが自動生成する手続きは別ディレクトリ(例: `memory/skills/`)で分離、混ざらないようにする。BF/KBL 分離の原則に一致。 +- **`paths:` によるスコープ絞り**は、前回議論した「Pod の所属スコープ = ディレクトリ階層」と自然に噛む。`paths: ["crates/protocol/**"]` の skill は protocol スコープの pod でだけ発動、という運用が素直にできる。 +- **progressive disclosure の 3 段構造**をメモリ本体にも適用する。知識ファイル先頭に 1 行 description を YAML で持たせ、`index.md` に description だけ集約、本体は wikilink で遅延ロード、という揃え方が可能。 +- 独自フォーマットを作らない。Hermes / OpenClaw / Claude Code のエコシステム skill をそのまま引き込める。 + +一次ソース: +- https://agentskills.io/specification +- https://code.claude.com/docs/en/skills +- https://github.com/anthropics/skills +- https://developers.openai.com/codex/skills + +--- + +## 6. プロンプト・スキルの継続的チューニング (external author empirical-prompt-tuning) + +メモリや skill の**中身を腐らせない**側の話。external author が 2025-07 頃に公開し、その後 Claude Code の SKILL として整備したメソッドで、「暗黙知の排除」を自動化する。insomnia のように skill を蓄積する設計では、**書いた直後に客観的に試す**仕組みが無いと品質が崩れていく。ここへの素直な当てはめ材料として記録。 + +### 基本思想 + +> Agent 向けテキスト指示(skill / slash command / task プロンプト / CLAUDE.md 節 / コード生成プロンプト)を、**バイアスを排した別の実行者**に動かしてもらい、**両面(実行者の自己申告 + 指示側メトリクス)**で評価して反復改善する。 + +書き手が自分で評価すると "こういう意図だったから" のバイアスで通ってしまう。だから毎反復ごとに **新規サブエージェント** に dispatch して、結果を構造化報告させる。 + +### 7 ステップ + +1. description と body の整合性チェック(description 1024 字制約もここで効く) +2. baseline: 要件チェックリスト + シナリオ(典型 1 + edge 1-2)を固定 +3. 新規 subagent で実行 +4. 両面評価 +5. 最小 patch で 1 箇所だけ直す +6. 新規 subagent で再実行 +7. 収束判定 + +### 実行 AI に出させるレポート形式 + +- 成果物 +- 要件達成([critical] は最低ライン明記) +- **不明瞭だった点** +- **裁量で補完した箇所** +- 再試行回数 + +### 指示側メトリクス(機械抽出) + +Claude Code の Task tool 戻り値から: +- `tool_uses` 数 +- `duration_ms` +- 要件チェックリスト達成率(critical / non-critical 別) + +### 収束判定 + +> 連続 2 回で新規不明瞭点ゼロ、精度の伸び ≤3pt、step 分散 ±10%、実行時間 ±15% + +シナリオは hold-out を用意して過適合検出、評価基準の後付け修正は無効化扱い。 + +### クリティカルな制約 + +> 毎回**新規** AI を dispatch すること。同じセッション再利用は前回の指摘を学習してしまい、指標が腐る。 + +### insomnia への示唆 + +- **skill や lessons を新規追加した直後に、同じ insomnia ハーネス内の別 pod で実行して評価**する自動フロー("skill doctor" 的な存在)を作れる。これは insomnia が pod factory を持っている点と相性がいい。 +- 失敗ログを書いた後、「同じ失敗が再現しないか」を新規 pod で試走する検証ステップが、構造的に**メモリ整備の一部**に組み込める。skill 化しない失敗ログでも有効。 +- 評価指標を自前で定義しておくと、後で他人(or 未来の自分)が skill を更新した時に腐敗検知できる。 +- 実体は skill 自身として配布されている(`public skill example/dot_claude/skills/empirical-prompt-tuning/SKILL.md`)。insomnia のメンテ用 skill セットのテンプレにそのまま借りられる。 + +一次ソース: +- https://zenn.dev/external author/articles/empirical-prompt-tuning +- https://github.com/public skill example/blob/main/dot_claude/skills/empirical-prompt-tuning/SKILL.md + +--- + +## 7. パターン抽出 + +上記を一枚にすると、2026 年現在のメモリ実装はだいたい以下の次元の組み合わせに収まる。 + +| 次元 | 主な選択肢 | +|------|-----------| +| 分類軸 | 宣言的知識 / エピソード / 手続き(skill) / プロファイル の 2〜4 層 | +| 抽出タイミング | ターン内(write-back)/ セッション終了(rollup)/ 完全非同期(sleep-time agent, bg chronicle) | +| 圧縮方式 | 要約 eviction / recursive summarization / consolidation pass / 差分更新 wiki | +| 保存形式 | Markdown ファイル群 / SQLite (+FTS) / Vector store / Graph DB | +| 取り出し方式 | 常駐注入(summary fixed) / 決定論的 wikilink / FTS / ベクトル / HyDE / RRF 融合 | +| ユーザー編集性 | 不可視 / 読取のみ / 手編集前提 | +| スコープ | per-user / per-project / shared across agents | + +insomnia で意思決定すべきポイントはこの対応表: + +- **Pod / Agent の「skill」概念を Hermes 風に明示すべきか**。現状の controller.rs には "sub-agent spawn" はあるが、skill を書き出して再利用する仕組みは無い。 +- **Codex Chronicle 風の "consolidation モデルを別途設定" 構成**は、insomnia の llm provider policy(Ollama / Codex OAuth / Anthropic)と相性が良い。軽量 extract と重い consolidation を別プロバイダに張れる。 +- **LLM Wiki パターンを採用する場合**、既に `docs/` と `tickets/` が Markdown + git で運用されているので、`memory/` ディレクトリを足して Git で可観測にしておくのが自然。RAG やベクトル化より先に、wikilink / index.md / log.md で足りるか見極めるべき。 +- **storage 層**: SQLite は既存 crate 構成にもフィット。Cloudflare Agent Memory / Codex の SQLite + 最終 Markdown の 2 段は移植しやすい。 +- **prompt injection 対策**: Chronicle が注意書きしている通り、観測チャンネルを増やすと攻撃面が広がる。insomnia では pod の sandbox 境界とメモリ生成を同じ境界で括る必要がある。 + +--- + +## 8. 未調査 / 次に掘るべき項目 + +- Letta `memory block` の Rust 実装例・永続化形式。 +- Cloudflare Agent Memory の supersession chain の具体アルゴリズム(記事は概略のみ)。 +- `agentskills.io` の CRDT 的バージョニング方針(標準は version metadata を任意 key にしている。実運用でどう衝突解決するかは未整備)。 +- Hermes の Honcho 連携の実体(外部サービス API 越しの dialectic user modeling、repo には prompt と API 呼び出しのみ)。 diff --git a/docs/ref/opencode-comparison.md b/docs/ref/opencode-comparison.md new file mode 100644 index 00000000..a181e292 --- /dev/null +++ b/docs/ref/opencode-comparison.md @@ -0,0 +1,312 @@ +# Insomnia × OpenCode 比較レポート + +## 概要 + +Insomnia(Rust製エージェントプラットフォーム、基礎実装段階)と OpenCode(TypeScript/Bun製AIコーディングアシスタント、本番稼働レベル)の設計を比較し、Insomniaの基礎設計に取り込めるパターンを特定する。 + +--- + +## 1. アーキテクチャ概観 + +### Insomnia(現状) + +``` +insomnia (stub) + └─ insomnia-core Pod / Controller / Protocol / SocketServer + └─ llm-worker-persistence Session永続化(JSONL + Blob) + └─ llm-worker Worker / Tool / Hook / Subscriber + └─ llm-worker-macros #[tool] / #[tool_registry] +``` + +- 実行単位は **Pod**(独立したエージェント) +- Unix Domain Socket + JSONL で外部通信 +- 状態遷移: Idle → Running → Paused/Idle +- 永続化: append-only JSONL ログ + +### OpenCode + +``` +packages/opencode (server) Session / Provider / Tool / Permission / Agent / Bus +packages/app (TUI) SolidJS + OpenTUI(Terminal UI) +packages/sdk (client) Hono OpenAPIから自動生成 +packages/desktop (Tauri) Web UIラッパー +``` + +- 実行単位は **Session**(会話)。Session内で **Agent** が切り替わる +- HTTP/SSE/WebSocket で外部通信 +- SQLite + Drizzle ORM で永続化 +- Effect.ts による依存注入・リソース管理 + +--- + +## 2. 設計判断の比較 + +| 観点 | Insomnia | OpenCode | 評価 | +|------|----------|----------|------| +| **DI** | ジェネリクス `` | Effect Service + Layer | Insomnia: コンパイル時保証。OpenCode: 実行時合成の柔軟性。方向性は正しい | +| **状態管理** | `RwLock` + ファイル書き出し | SQLite + Event Bus + SSE | Insomnia: 軽量で正しい。DBは将来の選択肢 | +| **プロトコル** | 自前 JSONL (Method/Event) | Hono HTTP API + SSE | Insomnia: Unix Socketに最適化。目的が違う | +| **ツール** | `Tool` trait + マクロ生成 | Zod schema + execute関数 | 同等のアプローチ。マクロの方が型安全 | +| **フック** | `Hook` trait 10種 | Plugin hooks (before/after) | Insomnia: 型安全で粒度が細かい。OpenCode: 動的で拡張しやすい | +| **永続化** | JSONL append-only + Blob | SQLite + Drizzle ORM | 方向性が異なる。両方とも正当な選択 | +| **プロバイダ** | 4種(Anthropic/OpenAI/Gemini/Ollama) | 20種+(ai-sdk経由) | 数は後から追加できる。抽象は同レベル | + +--- + +## 3. OpenCodeから取り込むべき設計パターン + +### 3.1 パーミッションシステム(重要度: 高) + +**OpenCodeの設計:** +- ツール実行前に **パターンベース** の権限チェック(`*.env` → deny、`src/**` → allow) +- 3段階: `deny` → `allow` → `ask`(ユーザーに確認) +- 「always」応答でパターンを永続的に許可 + +**Insomniaへの示唆:** + +Insomniaには `Scope`(書き込みディレクトリ制約)があるが、これは静的な境界。 +ツール単位の動的パーミッションが欠落している。 + +``` +提案: PreToolCall Hook でパーミッション評価を行う +``` + +- **設計原則3(再発明しない)** に沿って、新しいtrait は作らない +- `PreToolCall` Hook として実装し、Podマニフェストにルールを宣言 +- ルール定義は Scope の拡張ではなく、独立した概念として追加 + +```toml +# マニフェスト拡張案 +[[permission]] +tool = "bash" +pattern = "rm *" +action = "deny" + +[[permission]] +tool = "file_write" +pattern = "*.env" +action = "deny" +``` + +**今すぐ実装すべきか:** まだ。ツールが実装されてから。 +ただし、**Hook で差し込める設計になっている** ことは確認済み。 +拡張ポイントとして docs/pod.md の表に追加する価値あり。 + +--- + +### 3.2 ツール出力のトランケーション(重要度: 高) + +**OpenCodeの設計:** +- 50KB / 2000行を超える出力を切り捨て +- 切り捨て分はファイルに保存(7日間保持) +- LLMには「出力が大きすぎた。`grep` や `read` で絞り込め」とヒント + +**Insomniaの現状:** +- `llm-worker` に Tool Output の Inline/Stored 閾値(800 bytes)がある +- Stored 出力は Blob Storage に退避し、要約を自動生成 + +**比較:** +Insomnia の方が洗練されている(要約生成まで組み込み済み)。 +ただし OpenCode の「ヒント付きトランケーション」は追加の視点として有用。 + +**取り込み案:** +- Stored 出力時に「元データの場所と推奨アクション」を要約に含める規約 +- これは llm-worker のツール出力設計に自然に統合できる +- 現状の `tool-output-design.md` の Auto-Summarization に、推奨アクションのヒント生成を追加する余地がある + +--- + +### 3.3 コンテキスト圧縮(Compaction)(重要度: 高) + +**OpenCodeの設計:** +1. **Prune**: 古いツール出力を除去(直近40Kトークンは保護) +2. **Compact**: オーバーフロー時に専用エージェントで要約を生成 + - 構造化要約: Goal / Instructions / Discoveries / Accomplished / Files +3. **Replay**: 圧縮後に前回のユーザーメッセージを再送して作業継続 + +**Insomniaの現状:** +- Worker は history をそのまま保持 +- コンテキスト管理の仕組みは未実装 + +**これは重要な欠落。** 長時間実行エージェントである Insomnia にとって、コンテキスト管理はコア機能。 + +**取り込み案:** + +``` +Phase 1: Prune(Hook ベース) + PreLlmRequest Hook で古いツール出力を削除 + 設計原則3に従い、新しい抽象は作らない + +Phase 2: Compact(Agent ベース) + OnTurnEnd Hook でトークン数をチェック + 閾値超過時に要約生成を挿入 + Workerのresume機構で作業を継続 +``` + +**設計の要点:** +- Prune は `PreLlmRequest` Hook で history を変更する(Mutable state で可能) +- Compact は Pod レベルの制御(Controller が要約 Pod を起動) +- OpenCode の「構造化要約フォーマット」は良い規約 → system prompt に含める + +--- + +### 3.4 Event Bus / Typed Events(重要度: 中) + +**OpenCodeの設計:** +- 型付きイベント定義(Zod スキーマ) +- Instance スコープ + Global スコープの二段バス +- `publish` / `subscribe` / `subscribeAll` の3操作 + +**Insomniaの現状:** +- `broadcast::Sender` による単一チャネル +- Event enum で型安全 +- Pod 単位のスコープのみ + +**比較:** +Insomnia の broadcast channel は Pod 単位では十分。 +ただし、**複数 Pod の協調**(Supervisor)段階で Global Bus が必要になる。 + +**取り込み案:** +- 現時点では不要(設計原則4) +- Supervisor 実装時に参考にする設計として記録 +- OpenCode の「Instance スコープ → Global 伝播」パターンは、Pod スコープ → Supervisor 伝播に自然に対応 + +--- + +### 3.5 スナップショットシステム(重要度: 中) + +**OpenCodeの設計:** +- 内部 git リポジトリでファイル変更を追跡 +- ツール実行前にスナップショット取得 +- `restore` / `revert` / `diff` 操作 + +**Insomniaの現状:** +- Scope(書き込み制約)はあるが、変更追跡・復元は未実装 + +**取り込み案:** +- ファイル操作ツールの実装時に、git ベースのスナップショットを組み込む +- `PreToolCall` / `PostToolCall` Hook で自然に差し込める +- OpenCode の sparse checkout アプローチ(変更ファイルのみ追跡)は効率的 + +**今すぐ実装すべきか:** まだ。ファイル操作ツールの実装後。 + +--- + +### 3.6 Agent/Subagent パターン(重要度: 中) + +**OpenCodeの設計:** +- Primary Agent(build, plan)と Subagent(explore, general)の区別 +- Agent ごとにモデル・パーミッション・プロンプトを個別設定 +- `steps` パラメータでサブエージェントの反復回数を制限 +- 親セッションのコンテキストを子に渡す + +**Insomniaの現状:** +- Pod は独立実行単位。Pod 間通信は未実装 +- 拡張ポイント表に「Supervisor」として記載 + +**比較:** +OpenCode の Agent は Session 内のモード切り替え。 +Insomnia の Pod は完全に独立したプロセス。 + +**取り込み案:** +- OpenCode の `steps`(最大反復回数)は Pod マニフェストに追加する価値あり + - `[worker]` セクションに `max_turns` を追加 + - Worker の `OnTurnEnd` Hook で制御 +- Agent テンプレート(名前 + モデル + プロンプト + パーミッション)は + マニフェストがすでにこの役割を果たしている → 追加不要 +- Subagent パターンは Supervisor の責務 → 設計原則5に従い Pod 内部には入れない + +--- + +### 3.7 Config 階層(重要度: 低〜中) + +**OpenCodeの設計:** +- Managed → Remote → Global → Env → Project → Workspace の6段階 +- 配列フィールドはマージ(上書きではなく結合) +- Plugin の出自を追跡(PluginOrigin) + +**Insomniaの現状:** +- マニフェスト(TOML)のみ。階層なし + +**取り込み案:** +- Daemon 実装時にグローバル設定が必要になる +- OpenCode の「マージ戦略の明示」は参考になる + - 単純上書き vs 配列結合 vs 深いマージ +- 現時点ではマニフェスト一本で正しい(設計原則4) + +--- + +### 3.8 LSP 統合(重要度: 低) + +**OpenCodeの設計:** +- ファイル拡張子でサーバーを自動選択 +- ワークスペースルートを marker ファイルから検出 +- 遅延初期化(必要時にのみ起動) +- graceful degradation(サーバーなし → 無視) + +**Insomniaの現状:** +- LSP の言及なし + +**取り込み案:** +- コーディングエージェントとして使う場合にのみ必要 +- ツールとして実装する際に OpenCode のパターンを参考にする +- Hook ベースの差し込みではなく、Tool 実装として提供 + +--- + +## 4. 設計思想の根本的な違い + +### Insomnia: 「Pod は独立した実行単位」 + +- 各 Pod が完結したプロセス +- 協調は外部(Supervisor)が行う +- Unix の哲学に近い + +### OpenCode: 「Session が状態を持ち、Agent が振る舞いを切り替える」 + +- Session 内で Agent を動的に切り替え +- Session がコンテキストの境界 +- アプリケーションの哲学に近い + +**この違いは意図的であり、変える必要はない。** +Insomnia のアプローチは長時間自律実行に適しており、Pod の独立性がフォールトトレランスと拡張性の基盤になる。 + +--- + +## 5. 優先度付きアクション項目 + +### 今すぐ設計に反映(基礎段階で重要) + +| # | 項目 | 理由 | 実装場所 | +|---|------|------|----------| +| 1 | **max_turns をマニフェストに追加** | 暴走防止。OpenCodeのstepsに相当 | WorkerManifest | +| 2 | **コンテキスト圧縮の設計文書** | 長時間実行のコア要件。Hook ベースの Prune + Compact 方針を固める | docs/ | +| 3 | **パーミッションの拡張ポイント記録** | Pod 設計の拡張ポイント表にパターンベースのパーミッションを追加 | docs/pod.md | + +### ツール実装時に取り込む + +| # | 項目 | 理由 | +|---|------|------| +| 4 | ツール出力ヒント生成 | Stored 出力時に推奨アクションを含める | +| 5 | スナップショット(git ベース) | ファイル操作ツールと組み合わせ | +| 6 | パーミッション Hook | PreToolCall で動的権限チェック | + +### Supervisor/Daemon 実装時に取り込む + +| # | 項目 | 理由 | +|---|------|------| +| 7 | Global Event Bus | 複数 Pod 間のイベント伝播 | +| 8 | Config 階層 | グローバル設定 + プロジェクト設定のマージ | + +--- + +## 6. 取り込まないもの(と理由) + +| 項目 | 理由 | +|------|------| +| Effect.ts 的な DI | Rust のジェネリクス + trait がすでにこの役割。再発明になる | +| SQLite 永続化 | JSONL append-only は Pod の独立性と相性が良い。変える理由がない | +| HTTP API サーバー | Unix Socket + JSONL は Pod のユースケースに最適。Daemon 層で HTTP を追加する選択肢はある | +| Session 内 Agent 切り替え | Pod = 独立実行単位 の設計方針と矛盾。Subagent は Supervisor の責務 | +| SolidJS TUI | Rust には ratatui 等がある。技術スタックの違い | +| Plugin システム | 設計原則4に反する。Hook で十分 | diff --git a/docs/ref/reference-tool-spec.md b/docs/ref/reference-tool-spec.md new file mode 100644 index 00000000..c6a66f22 --- /dev/null +++ b/docs/ref/reference-tool-spec.md @@ -0,0 +1,98 @@ +# リファレンス: ローカルファイル操作ツール仕様 + +Claude Code がエージェントとして使うファイル操作ツールの仕様。 +Pod のツール設計の参考資料。 + +## Read + +ファイルを読む。画像・PDF・Jupyter notebook にも対応。 + +| パラメータ | 型 | 必須 | 説明 | +|---|---|---|---| +| `file_path` | string | 必須 | 絶対パス | +| `offset` | integer | 任意 | 読み開始行番号(0始まり) | +| `limit` | integer | 任意 | 読み取り行数(offset と併用) | +| `pages` | string | 任意 | PDF用ページ範囲 (例: `"1-5"`)。最大20ページ/回 | + +- デフォルトで先頭から最大2000行 +- 出力は行番号付き(1始まり) +- ディレクトリは読めない + +## Write + +ファイルを新規作成、または全体上書き。 + +| パラメータ | 型 | 必須 | 説明 | +|---|---|---|---| +| `file_path` | string | 必須 | 絶対パス | +| `content` | string | 必須 | 書き込む内容全体 | + +- 既存ファイルは上書き +- 既存ファイルを Write する前に Read が必要(未読だとエラー) + +## Edit + +既存ファイルの部分的な文字列置換。 + +| パラメータ | 型 | 必須 | 説明 | +|---|---|---|---| +| `file_path` | string | 必須 | 絶対パス | +| `old_string` | string | 必須 | 置換対象(ファイル内で一意であること) | +| `new_string` | string | 必須 | 置換後(old_string と異なること) | +| `replace_all` | boolean | 任意 | `true` で全出現箇所を置換。デフォルト `false` | + +- 事前に Read が必要(未読だとエラー) +- `old_string` がファイル内で一意でないとエラー(`replace_all: true` 除く) + +## Glob + +ファイルパターン検索。 + +| パラメータ | 型 | 必須 | 説明 | +|---|---|---|---| +| `pattern` | string | 必須 | glob パターン (例: `"**/*.rs"`) | +| `path` | string | 任意 | 検索ディレクトリ。省略時はカレント | + +- 結果は更新日時順ソート + +## Grep + +ファイル内容の正規表現検索(ripgrep ベース)。 + +| パラメータ | 型 | 必須 | 説明 | +|---|---|---|---| +| `pattern` | string | 必須 | 正規表現パターン | +| `path` | string | 任意 | 検索対象。省略時はカレント | +| `glob` | string | 任意 | ファイルフィルタ (例: `"*.rs"`) | +| `type` | string | 任意 | ファイル種別 (例: `"rust"`, `"py"`) | +| `output_mode` | enum | 任意 | `"files_with_matches"` (デフォルト) / `"content"` / `"count"` | +| `-n` | boolean | 任意 | 行番号表示 (content モード、デフォルト `true`) | +| `-i` | boolean | 任意 | 大文字小文字無視 | +| `-A` | number | 任意 | マッチ後の行数 | +| `-B` | number | 任意 | マッチ前の行数 | +| `-C` | number | 任意 | マッチ前後の行数 | +| `multiline` | boolean | 任意 | 複数行マッチ。デフォルト `false` | +| `head_limit` | number | 任意 | 出力件数制限。デフォルト 250 | +| `offset` | number | 任意 | 先頭N件スキップ | + +## Bash + +シェルコマンド実行。 + +| パラメータ | 型 | 必須 | 説明 | +|---|---|---|---| +| `command` | string | 必須 | 実行するコマンド | +| `description` | string | 任意 | コマンドの説明 | +| `timeout` | number | 任意 | タイムアウト ms (最大 600000、デフォルト 120000) | +| `run_in_background` | boolean | 任意 | バックグラウンド実行 | + +- 作業ディレクトリはコマンド間で永続 +- シェル状態(変数・エイリアス)は永続しない + +## 設計上の特徴 + +- **パスは全て絶対パス** +- **Read → Edit/Write の順序制約**: 未読ファイルの編集を防ぐ安全弁 +- **専用ツール優先**: cat/grep/find/sed の代わりに専用ツールを使う +- **冪等性**: Edit は差分ベース、Write は全体上書き +- **ワークフロー**: Glob/Grep で探索 → Read で確認 → Edit で変更 diff --git a/docs/ref/zed-workspace.md b/docs/ref/zed-workspace.md new file mode 100644 index 00000000..b2a428fb --- /dev/null +++ b/docs/ref/zed-workspace.md @@ -0,0 +1,238 @@ +# Zed モノレポのワークスペース・クレート規則 + +[zed-industries/zed](https://github.com/zed-industries/zed) のソースコードを読み解くための、ワークスペース構成とクレート分割に関する規則のまとめ。 + +--- + +## 1. ワークスペース構成 + +### 単一ワークスペース + +- リポジトリ直下の `Cargo.toml` が `[workspace]` を持ち、`crates/` 以下のすべてのクレート(200個超)を members として束ねる単一ワークスペース。 +- `default-members = ["crates/zed"]` が指定されており、ルートで `cargo run` するとエディタ本体が起動する。 +- `Cargo.lock` はルートに1つだけ。すべてのクレートで共有される。 +- ルートに `rust-toolchain.toml` / `clippy.toml` / `rustfmt.toml` / `.cargo/` を置き、ワークスペース全体に共通設定を効かせる。 + +### 内部依存は `[workspace.dependencies]` に集約 + +ルート `Cargo.toml` に **すべての内部クレートを path 指定で列挙** する: + +```toml +[workspace.dependencies] +acp_thread = { path = "crates/acp_thread" } +action_log = { path = "crates/action_log" } +agent = { path = "crates/agent" } +agent_ui = { path = "crates/agent_ui" } +anthropic = { path = "crates/anthropic" } +# ... 200個以上続く +``` + +外部クレート(serde, tokio, フォーク版 calloop など)も同じ場所に集約され、バージョンや git rev を一元管理する。 + +### 各クレートは `.workspace = true` で参照 + +個々の `crates//Cargo.toml` ではバージョン番号もパスも書かない: + +```toml +[dependencies] +gpui.workspace = true +project.workspace = true +serde.workspace = true +``` + +これにより: +- バージョン更新やフォーク差し替えがルート1ファイルで完結 +- 同じ依存が複数バージョンに分裂する事故が起きない +- 新しいクレートの追加は「フォルダ作成 → ルートに1行追加 → 使う側で `name.workspace = true`」だけ + +### `publish = false` + +ワークスペース全体で `publish = false`。crates.io には公開されないため、`editor` や `project`、`language` のような一般名詞を平気で使える。 + +--- + +## 2. クレート命名規則 + +### 表記ルール + +- **`snake_case` で統一**。ハイフンや CamelCase は使わない。 +- **全部小文字**、略語も小文字(`lsp`, `rpc`, `acp`, `aws`, `ui`)。 +- **ディレクトリ名 = `package.name` = `[workspace.dependencies]` のキー** で完全一致。grep しやすさを優先。 + +### 命名パターン + +#### 1. コア基盤は1語の名詞 + +`zed` / `gpui` / `editor` / `project` / `workspace` / `language` / `theme` / `ui` / `lsp` / `rpc` / `audio` / `assets` / `askpass` + +#### 2. 機能ファミリーは「共通プレフィックス + サフィックス」 + +``` +agent / agent_ui / agent_ui_v2 / agent_settings / agent_servers +auto_update / auto_update_ui / auto_update_helper +assistant_text_thread / assistant_slash_command / assistant_slash_commands +acp_thread / acp_tools +``` + +サフィックスの慣習: + +| サフィックス | 役割 | +|---|---| +| `_ui` | 機能のビュー/ウィジェット層(コアロジックとは分離) | +| `_settings` | 設定スキーマと読み込み | +| `_servers` | 外部プロセス連携アダプタ | +| `_helper` | 補助バイナリ | +| `_v2` | 既存版と並行する新実装の隔離 | +| `_tools` | デバッグ/開発者向けユーティリティ | + +#### 3. 外部プロトコル/ベンダー連携はその名前そのまま + +`anthropic` / `bedrock` / `aws_http_client` のように、相手のサービスや仕様の名前をそのまま使う。`acp_*` (Agent Client Protocol) のようにプロトコルの略号を頭に付けて系列化するのも同様。 + +--- + +## 3. クレート分割の方針 + +### `zed` クレートは薄いシェル + +`crates/zed/` の中身は最小限: + +- `main.rs` — エントリポイント、CLI 引数、シングルインスタンス処理、クラッシュハンドラ、パス初期化 +- `zed.rs` — グローバル action ハンドラ登録、`initialize_workspace` でのパネル組み立て +- `build.rs` / `RELEASE_CHANNEL` + +`app.run(...)` の中身は **200個以上のクレートの `init(cx)` を正しい順序で呼ぶだけ**: + +```rust +settings::init(cx); +theme::init(cx); +client::init(&client, cx); +workspace::init(app_state.clone(), cx); +editor::init(cx); +project_panel::init(cx); +agent_ui::init(fs, client, prompt_builder, languages, cx); +git_ui::init(cx); +// ... +``` + +`zed` は依存グラフの頂点に立つ唯一のクレートで、機能の置き場所ではなく **配線盤 (wiring)** として存在する。**「どこに置くか迷ったら `zed` 以外のどこかに置く」** が鉄則。 + +### 境界を切る基準 + +#### ① レイヤー(下から上への単方向依存) + +``` +gpui ← UI フレームワーク + ↓ +text / language / fs / rpc / settings / ← ドメインの基本型 +theme / ui + ↓ +project / lsp / git / terminal / ← サービス層 +multi_buffer + ↓ +editor / workspace ← 中核機能 + ↓ +project_panel / git_ui / agent_ui / ← 個別機能 (パネル/ビュー) +diagnostics / search / ... + ↓ +zed ← 配線だけ +``` + +下のレイヤーは上のレイヤーを知らない。逆方向の拡張点は trait(`workspace::Item`、`workspace::Panel` など)として下層が公開し、上層が実装する。 + +#### ② ロジックと UI の分離 + +GPUI 依存を上層に閉じ込めるため、ロジックと UI を別クレートに切る: + +| ロジック | UI | +|---|---| +| `agent` | `agent_ui` | +| `auto_update` | `auto_update_ui` | +| `git` | `git_ui` | +| `project` | `project_panel` | + +UI 側はロジック側を知るが、ロジック側は UI 側を知らない。 + +#### ③ `Project` と `Workspace` の二分 + +Zed の設計で最も象徴的な分割: + +- **`project`** — worktree、LSP、ファイルシステム、Git といったサービスを束ねる。ヘッドレスでも動く側。 +- **`workspace`** — ペイン分割、ドック、パネル、ステータスバーといったウィンドウ表示を束ねる。GPUI に強く依存する側。 + +これにより `Project` をリモートマシンで走らせ `Workspace` をローカルで走らせる、というリモート開発が素直に成立する。 + +#### ④ trait 境界 + Fake 実装 + +下層の重要な抽象は trait で公開し、テストでは Fake を差し込む: + +- `fs::Fs` ↔ `FakeFs` +- `git::GitRepository` ↔ Fake 実装 +- `language::LanguageRegistry` +- `gpui::Element`、`workspace::Item`/`Panel` + +trait が置かれているクレートがそのまま境界になる。「どこまでがプラグイン可能か」がクレート一覧から読み取れる。 + +#### ⑤ 外部世界との接点ごとにクレートを切る + +新規追加で既存クレートを編集しなくて済むように、外部依存ごとに独立させる: + +- `lsp` / `dap` / `rpc` (プロトコル層) +- `anthropic` / `bedrock` / `open_ai` (LLM プロバイダごと) +- `aws_http_client` (特定の HTTP クライアント実装) +- `acp_thread` / `acp_tools` (Agent Client Protocol) +- `extension_host` + `extension_api` (拡張機能 WASM サンドボックス境界) + +#### ⑥ コンパイル時間最適化 + +頻繁に編集される `editor` のような大きいクレートを切り離すことで、並列ビルドとインクリメンタルコンパイルの効率を上げる。ルート `Cargo.toml` には単一ファイルクレートに `codegen-units = 1` を効かせる調整も含まれる。 + +--- + +## 4. 「core」クレートを作らない + +Zed には `core` / `common` / `shared` / `zed_core` といったクレートが**意図的に存在しない**。「コア」になりそうなものは機能軸で水平に分割される: + +| ありがちな "core" の中身 | Zed での置き場所 | +|---|---| +| 基本データ型 (Rope, Point, Anchor) | `text` | +| ファイルシステム抽象 | `fs` (`Fs` trait + `FakeFs`) | +| 設定の読み書き | `settings` | +| テーマ・色 | `theme` | +| 共通 UI コンポーネント | `ui` | +| 汎用ユーティリティ関数 | `util` | +| RPC/シリアライズ | `rpc` / `proto` | +| ロギング | `zlog` | +| 言語抽象 | `language` | + +### `core` を作らない理由 + +1. **ビルドグラフの直列化を避ける** — `core` を作るとほぼ全クレートがそこに依存し、1行触るたびに数百クレートが再コンパイルされる。並列ビルドの利点が消える。 +2. **"ゴミ捨て場" 化の防止** — `core` や `common` は置き場所に迷ったコードが流れ込む磁石になり、責務が肥大化して循環依存の温床になる。Zed は **「迷ったら新しいクレートを作る」** 方向に振る。 +3. **依存方向のドキュメント化** — クレートを細かく分けると `Cargo.toml` を見るだけで何に依存し何に依存していないかが一覧できる。`core` があるとこの情報量がゼロになる。 + +### 例外的な `util` + +`crates/util` は雑多な汎用ヘルパ(`ResultExt`, paths, debouncer 等)が入る "ややコアっぽい" クレートだが: + +- 名前は `core` ではなく `util` (補助関数の入れ物の慣習) +- GPUI にも `editor` にも依存しない、完全な下層 +- trait や中核データ型は置かない (それは `text` や `fs` の役目) + +責務を「補助関数集」に限定することでゴミ捨て場化を防いでいる。 + +--- + +## 5. まとめ + +Zed のクレート分割の優先順位: + +1. **`zed` には何も入れない** — 機能は必ず別クレートに押し出し、`zed` は init 呼び出しと CLI と main だけを持つ +2. **下から上への単方向依存** — 上層が必要な拡張点は trait で下層に公開する +3. **ロジック / UI を分ける** — `xxx` と `xxx_ui` のペア、GPUI 依存を上層に閉じ込める +4. **`Project` と `Workspace` を分ける** — ヘッドレス/リモート実行可能性のため +5. **外部世界 (プロトコル・ベンダー・拡張) ごとにクレートを切る** — 新規追加を「ファイル追加だけ」で済ませる +6. **Fake が置けるところに trait を置く** — テスト境界 = アーキテクチャ境界 +7. **`core` を作らない** — 万能箱の代わりに責務を限定した小さな箱を用意する + +クレート数の多さは管理コストではなく、**境界を守るためのコスト払い**である。 diff --git a/docs/system-prompt-template.md b/docs/system-prompt-template.md new file mode 100644 index 00000000..b02e7e6a --- /dev/null +++ b/docs/system-prompt-template.md @@ -0,0 +1,85 @@ +# システムプロンプトテンプレート + +Pod のシステムプロンプトは minijinja テンプレート。first turn 直前に1回だけ render され、以降のターンおよび compact 後も同じ文字列を使い続ける。 + +## マニフェスト記法 + +既存の `[worker].system_prompt` をそのままテンプレート文字列として解釈する。新フィールドは無い。 + +```toml +[worker] +system_prompt = """ +You are operating in {{ cwd }}. +Today is {{ date }}. + +{{ scope.summary }} + +Available tools: {{ tools | join(", ") }} +{% if files.agents_md is defined -%} + +{{ files.agents_md }} +{%- endif %} +""" +``` + +構文は minijinja (Jinja2 互換) のサブセット。未定義変数の参照は `UndefinedBehavior::Strict` により render エラーになる。`{{` のリテラル出力は `{{ '{{' }}` で逃がす。 + +## 組み込み変数 + +| キー | 型 | 内容 | +|---|---|---| +| `date` | string | `YYYY-MM-DD` (UTC) | +| `time` | string | `HH:MM:SS` (UTC) | +| `datetime` | string | RFC3339 (UTC, 秒精度) | +| `cwd` | string | Pod の絶対 pwd | +| `scope.readable` | list\ | allow ルールの全パス (Read 以上) | +| `scope.writable` | list\ | allow ルールのうち Write のパス | +| `scope.summary` | string | "Readable:\n - ...\nWritable:\n - ..." 形式の整形済み文字列 | +| `tools` | list\ | 登録済みツール名の sort 済み一覧 | +| `files` | map\ | 外部ファイル。AGENTS.md 等の供給先として予約。空の場合もあるので `is defined` でガードする | + +`files` は常に存在する (本体が空 Map のことはあっても `files` 自体が未定義にはならない) が、個別キー (`files.agents_md` 等) は供給元次第で未定義になり得る。 + +## 評価モデル + +テンプレートの実体化 (render) は遅延評価。プロジェクト内の他の遅延初期化パターン (tool factory / hook builder) と同じ形に揃えている。 + +### タイミング + +1. `Pod::from_manifest` 時点: テンプレート文字列を `SystemPromptTemplate::parse` で **構文検査のみ** 行い、`Pod.system_prompt_template: Option` に保持する。この時点で Worker 側の system_prompt は `None`、session log もまだ書かれない (`head_hash: None`)。 + +2. `Pod::run` / `Pod::resume` 初回呼び出し冒頭 (`ensure_system_prompt_materialized`): + 1. `worker.tool_server_handle().flush_pending()` で pending な tool factory を materialize して tool 名を確定させる + 2. 現在時刻・cwd・scope・tool 名を集めて `SystemPromptContext` を作る + 3. `template.render(ctx)` で文字列化して `worker.set_system_prompt(rendered)` を呼ぶ + 4. `Pod.system_prompt_template = None` (`Option::take()` で構造的に1回性を保証) + +3. その直後の `ensure_session_head` が `head_hash` を見て初回なら `create_session_with_id` を呼び、materialize 後の system_prompt を **SessionStart ログエントリに焼き付ける**。 + +### 1回性の保証 + +- `Pod.system_prompt_template` は `Option` で、materialize 時に `take()` する。2 ターン目以降はフィールドが `None` なので `ensure_system_prompt_materialized` は早期 return し、再 render は発生しない。 +- compact は Worker の system_prompt フィールドを触らない (pod.rs の `compact` は `w.get_system_prompt()` を読み取って新セッションに引き継ぐだけ)。そのため compact 前後で同じ文字列が流れ続ける。 +- 統合テスト `compact_preserves_system_prompt` が実装で直接検証している。 + +### 責務分離 + +テンプレート機構は **Pod 層** に閉じる。`llm-worker` はテンプレートの存在を知らず、`Worker::set_system_prompt(String)` で render 済みの文字列を受け取るだけ。llm-worker 側に入った唯一の変更は `ToolServerHandle::flush_pending` を `pub` に昇格させたこと (tool 名を先取りするため)。 + +## エラー処理 + +- **構文エラー**: `Pod::from_manifest` の parse 段階で検出 → `PodError::InvalidSystemPromptTemplate { source: SystemPromptError::Parse }` で Pod の生成自体が失敗する。 +- **render エラー** (未定義変数など): first turn 直前の `ensure_system_prompt_materialized` で検出 → `PodError::SystemPromptRender { source: SystemPromptError::Render }` で初回 `Pod::run` が失敗する。 +- フォールバックはしない。どちらも fail-fast で Pod 起動を止める。 + +## 供給元の拡張 + +`SystemPromptContext.files: BTreeMap` は本チケットの範囲では常に空だが、key 空間として予約してある。AGENTS.md 取り込み (別チケット) では `ensure_system_prompt_materialized` 内で `files` を埋めるだけで拡張できる。テンプレート側・エンジン側の変更は不要。 + +## 関連ファイル + +- `crates/pod/src/system_prompt.rs` — `SystemPromptTemplate` / `SystemPromptContext` / `SystemPromptError` +- `crates/pod/src/pod.rs` — `Pod.system_prompt_template` フィールド、`ensure_system_prompt_materialized`、`ensure_session_head` の初回 append ロジック +- `crates/manifest/src/scope.rs` — `Scope::summary` / `readable_paths` / `writable_paths` +- `crates/session-store/src/session.rs` — `create_session_with_id` (事前生成 ID で SessionStart を書くための entry point) +- `crates/llm-worker/src/tool_server.rs` — `ToolServerHandle::flush_pending` (pub) diff --git a/docs/tui-keybindings.md b/docs/tui-keybindings.md new file mode 100644 index 00000000..1a72cd56 --- /dev/null +++ b/docs/tui-keybindings.md @@ -0,0 +1,63 @@ +# TUI キーバインド + +`crates/tui` の対話画面で効くキー一覧。`main.rs:handle_key` を単一情報源とし、このドキュメントは人間向けの目次。 + +## 入力編集 + +| キー | 動作 | +|---|---| +| 文字キー | カーソル位置に挿入 | +| `Backspace` | カーソル直前を削除 | +| `Delete` | カーソル直後を削除 | +| `Left` / `Right` | カーソル移動 | +| `Home` / `End` | 行頭 / 行末へ | + +## 送信(Enter) + +| 状態 | 入力あり | 入力空 | +|---|---|---| +| Idle | `Method::Run` で新ターン開始 | no-op | +| Paused | `Method::Run`(前ターンは割り込み終了として扱い、新ターンとして開始) | `Method::Resume`(前ターンの続きを再開) | +| Running | 入力は TUI 側でバッファのみ、送信はしない | 同左 | + +### Paused からの挙動 + +Paused 中に Enter すると、入力の有無で 2 通り: + +- **空**: 前ターンを中断した地点から **Resume**。LLM はそのまま続きを書く(partial text は破棄済み)、未実行の tool があれば実行して続行 +- **入力あり**: 前ターンは「割り込み終了」扱いとなり、新ターンとして **Run**。Pod 側では + 1. 未応答 `tool_use` があれば synthetic `tool_result("[Interrupted by user]")` で閉じる + 2. `[The previous turn was interrupted by the user]` system note を履歴に挿入 + 3. 入力を新しい user メッセージとして append + 4. ターン開始 + +## Pod 制御 + +| キー | Running 中 | Idle / Paused | +|---|---|---| +| `Ctrl-X` | `Method::Cancel`(進行中ターンを破棄 → Idle) | no-op(エラー表示のみ) | +| `Ctrl-C` | `Method::Pause`(進行中ターンを中断 → Paused) | 1 回目 warn、3 秒以内の 2 回目で TUI 終了(Pod は残る) | +| `Ctrl-D` | 1 回目 warn、3 秒以内の 2 回目で `Method::Shutdown` | `Method::Shutdown`(Pod を終了) | + +### Cancel と Pause の違い + +- **Cancel** は「ターンを捨てる」: 進行中の LLM リクエスト・未完了 tool を打ち切り、状態は Idle。続きは Resume できない +- **Pause** は「止めるけど続けられるように」: 同じく打ち切るが状態は Paused、空 Enter で Resume 可能 + +Running 中に割り込みたい場合、ほとんどのケースで `Ctrl-C`(Pause)が自然。Ctrl-X(Cancel)は明示的に破棄したい時(LLM が暴走した時など)用。 + +### Ctrl-C と Ctrl-D の 2 段階 UX + +どちらも「破壊的に見える操作」は確認を挟む: + +- Ctrl-C Running 中: 1 回目で即 Pause(破壊的ではない) +- Ctrl-C Idle / Paused: 1 回目で warn メッセージ、3 秒以内の 2 回目で TUI 終了(Pod は残る) +- Ctrl-D Running 中: 1 回目で warn、3 秒以内の 2 回目で Shutdown +- Ctrl-D Idle / Paused: 1 回目で即 Shutdown + +`Ctrl-C` は Pod は落とさず TUI プロセスだけ抜ける。`Ctrl-D` は Pod 自体に `Method::Shutdown` を送って終了させる(Pod プロセスが消える)。 + +## 履歴メモ + +- かつて存在した `Ctrl-R`(Resume 専用)は、空 Enter での Resume に統合されたため廃止 +- かつて存在した `Esc`(TUI 終了)は、`Ctrl-C` の 2 連打 UX に統合されたため廃止