yoi/docs/architecture.md

139 lines
8.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Insomnia アーキテクチャ
## プロジェクトの目的
複数の LLM エージェントPodが独立プロセスとして並行動作し、自律的に分業・統括できるエンジン。シングルエージェントの LLM リグとの差別化は、タスクを別 Pod に委譲し結果を集約する**オーケストレーション**を LLM 自身に行わせる点にある。
## 設計原則
### 層の分離と方向性
各クレートは下位層に依存し、上位層を知らない。上位層は下位層の API を丸ごと隠蔽せず、制約が必要な部分だけをラップして提供し、それ以外は下位層を直接使わせる。
### 宣言した層が解決する
ある層が構成を宣言として受け取ったなら、その解決もその層の責務。マニフェストに `[model] ref = "anthropic/claude-sonnet-4-6"`(あるいは `scheme = "anthropic"` + `model_id = ...` の inline 形式)と書いた以上、`ModelManifest` → `LlmClient` の変換は insomnia 側(`crates/provider`)が行う。逆に 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"
[model]
ref = "anthropic/claude-sonnet-4-6"
[worker]
instruction = "$insomnia/default"
max_tokens = 4096
temperature = 0.3
[[scope.allow]]
target = "/abs/path"
permission = "write"
```
`[model]``ref = "<provider>/<model_id>"` でプロバイダ / モデルカタログを引く短縮形と、`scheme` / `model_id` / `auth` を直書きする inline 形式の両方を受ける。カタログは `resources/{providers,models}/builtin.toml` を builtin、`<config_dir>/{providers,models}.toml` を user override として解決する(`<config_dir>` の解決ルールは `manifest::paths` 参照)。詳細は `docs/pod-factory.md``crates/provider/README.md`
### Manifest / profile 入力
通常の Pod 起動は Lua profile discovery/default から `PodManifest` を生成する。bundled `builtin:default` が fallback default で、user/project `profiles.toml` は profile registry と default selection だけを担う。user/project `manifest.toml` の ambient cascade は通常起動では使わない。
`insomnia-pod --manifest <PATH>` は explicit one-file compatibility/debug input で、指定 TOML 1 枚だけに builtin defaults を merge し、`PodManifestConfig -> PodManifest` の required validation を通す。
`PodFactory` の user/project/overlay API は低レベル構成部品として残るが、CLI の通常起動 path では generic TOML overlay を公開しない。
### Instruction とプロンプト資産
`worker.instruction` はファイル参照。3 層の prefix addressing でプロンプト資産を解決:
- `$insomnia/...` — バイナリ同梱(`resources/prompts/`、`include_dir!` で埋め込み)
- `$user/...``<config_dir>/prompts/``manifest::paths` で解決)
- `$workspace/...``<project>/.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` がセッション内のコンテンツハッシュ追跡を行う。