yoi/docs/architecture.md

153 lines
10 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 をラップし、マニフェストによる宣言的構成、ディレクトリスコープ、Pod 名に紐づく永続状態を加える。
- 1 live Pod process は 1 Pod name/current state を所有する
- 会話の永続化は `session-store``session_id` / `segment_id` ログ、Pod 名の current state は `pod-store` metadata が担う
- マニフェストまたは profile から完結構築できる
- 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 は残す。LLM request context だけを加工し、永続 history 本体は変更しない
- **Compact**: 履歴 prefix を要約し、同じ `session_id` 配下の新 `segment_id` へ rotate する。`SegmentStart.compacted_from` が元 segment を参照し、Pod metadata の active pointer は新 segment を指す
- サーキットブレーカー: compact が3回連続失敗したら無効化
## Protocol
Pod の制御・監視に使う JSONL ベースのメッセージプロトコル。トランスポートに依存しない。正確な wire enum は `crates/protocol/src/lib.rs::{Method, Event}` を正とし、この節はカテゴリの目次として扱う。
- **Client → Pod (`Method`)**
- turn 制御: `Run`, `Resume`, `Cancel`, `Pause`, `Shutdown`
- context/session 制御: `Compact`, `ListRewindTargets`, `RewindTo`
- typed injection / child lifecycle: `Notify`, `PodEvent`
- client 補助: `ListCompletions`
- Pod visibility / restore: `ListPods`, `RestorePod`
- **Pod → Client (`Event`)**
- accepted input / history seed: `Snapshot`, `UserMessage`, `SystemItem`, `SegmentRotated`
- generation stream: `TurnStart`, `TurnEnd`, `LlmCallStart`, `LlmCallEnd`, retry/continuation events, `Text*`, `Thinking*`, `ToolCall*`, `ToolResult`, `Usage`, `RunEnd`
- control replies: completions, rewind, visible Pod list / restore results
- operational status: `Status`, `Alert`, `MemoryWorker`, `Compact*`, `Error`, `Shutdown`
- リクエストとレスポンスの紐付けを一般化した RPC にはしない。多くの状態は broadcast event と Pod status で観測する
- 一部の reply例: completionsは要求 socket にだけ返る。broadcast event と request-local reply の違いは enum variant のコメントを正とする
- 操作の競合は先勝ち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 衝突は runtime registry / scope lock で検出する。child Pod へ委譲した write scope は親の effective scope から delegated-out deny として差し引かれ、child 停止・prune 時に reclaim される
## セッション永続化
- `session-store` は append-only JSONL segment log。1 `LogEntry` = 1 行
- `SessionId` は論理会話の fork-tree root、`SegmentId` はその中の現在の書き込み先 segment
- fresh conversation だけが新 `SessionId` を作る。compact / fork は同じ `SessionId` 配下に新 `SegmentId` を作り、`SegmentStart.{compacted_from,forked_from}: SegmentOrigin` で出自を持つ
- segment log の先頭は `LogEntry::SegmentStart`。以降に `Invoke`, `UserInput`, `AssistantItem`, `ToolResult`, `SystemItem`, `TurnEnd`, `RunCompleted` / `RunErrored`, `ConfigChanged`, `LlmUsage`, `Extension` などを append する
- replay は segment log から Worker state を再構成する。lineage は entry hash ではなく `SegmentOrigin.at_turn_index` と segment id で参照する
- Pod 名の durable current stateactive pointer、resolved manifest snapshot、spawned child delegation/reclaim`pod-store` metadata が担う。socket path や runtime mirror は liveness authority ではない
## 組み込みツール
正確な callable set と description は ToolRegistry / manifest permission / scope / profile に依存する。高レベルには以下のカテゴリを持つ。
| カテゴリ | 例 | 概要 |
|---|---|---|
| File / shell | `Read`, `Write`, `Edit`, `Glob`, `Grep`, `Bash` | workspace ファイル操作と shell 実行。file tools は `ScopedFs` と read-before-edit tracker を通る。`Bash` は permission policy と出力退避で制御する |
| Task | `TaskCreate`, `TaskUpdate`, `TaskList`, `TaskGet` | セッション内の短期 task 状態管理 |
| Memory / Knowledge | `MemoryQuery`, `MemoryRead`, `MemoryWrite`, `MemoryEdit`, `MemoryDelete`, `KnowledgeQuery` | manifest の memory 設定が有効な時に登録される durable memory / knowledge 操作 |
| Pod orchestration | `SpawnPod`, `SendToPod`, `ReadPodOutput`, `StopPod`, `ListPods`, `RestorePod` | child / visible Pod の起動・通信・停止・一覧・復元 |
| Web | `WebSearch`, `WebFetch` | manifest で明示設定された provider と local secret-store reference 経由の bounded web access |
すべての tool call は manifest tool permission と scope/policy のチェックを通る。ファイル write scope、Pod delegation、memory layout、web provider 設定はそれぞれ別の authority を持ち、UI 表示だけで権限を広げない。