memory実装チケット

This commit is contained in:
Keisuke Hirata 2026-04-26 17:00:38 +09:00
parent 89c2c701fd
commit 123fc3b0ad
13 changed files with 812 additions and 13 deletions

12
TODO.md
View File

@ -1,4 +1,3 @@
- [ ] テスト設計 → [tickets/test-design.md](tickets/test-design.md)
- [ ] Agent Skills サポート → [tickets/agent-skills.md](tickets/agent-skills.md)
- [ ] ツール設計
- [ ] Bash ツール (Permission 層と統合) → [tickets/bash-tool.md](tickets/bash-tool.md)
@ -9,3 +8,14 @@
- [ ] TUI 拡充
- [ ] フルスクリーン化によるオーバーホール → [tickets/tui-fullscreen-overhaul.md](tickets/tui-fullscreen-overhaul.md)
- [ ] 新しい Pod を spawn する UI の設計 → [tickets/tui-pod-spawn-ui.md](tickets/tui-pod-spawn-ui.md)
- [ ] サブミット入力
- [ ] protocol Segment 化 → [tickets/submit-segment-protocol.md](tickets/submit-segment-protocol.md)
- [ ] TUI 補完 + 型付き atom 化 → [tickets/submit-tui-completion.md](tickets/submit-tui-completion.md)
- [ ] メモリ機構
- [ ] ファイル形式 + Linter 土台 → [tickets/memory-file-format.md](tickets/memory-file-format.md)
- [ ] memory / Knowledge 検索ツール → [tickets/memory-search-tools.md](tickets/memory-search-tools.md)
- [ ] `model_invokation: ON` の常駐注入 → [tickets/memory-resident-injection.md](tickets/memory-resident-injection.md)
- [ ] Phase 1 活動抽出 → [tickets/memory-phase1-extract.md](tickets/memory-phase1-extract.md)
- [ ] Phase 2 consolidation → [tickets/memory-phase2-consolidation.md](tickets/memory-phase2-consolidation.md)
- [ ] 使用頻度メトリクス + Knowledge 化候補レポート → [tickets/memory-usage-metrics.md](tickets/memory-usage-metrics.md)
- [ ] GC定期再評価 → [tickets/memory-gc.md](tickets/memory-gc.md)

View File

@ -27,7 +27,7 @@ Workflow`/<slug>` で呼び出される制約付き作業フロー)は別 p
- 同主題の content 進化 = 上書き update + git log で履歴追跡
- 別主題が古い主題を置き換える場合のみ、別 slug で新規作成し古い方に `status: replaced` + `replaced_by: <新 slug>` を記録
- Phase 1 の中間ストアとして `memory/_staging/<id>.json` を使う短命、P2 完了で cleanup。ここは衝突回避と順序のため UUIDv7 可)
- Raw session log は既存 `llm-worker-persistence` で保持する。memory 対象外、参照経路のみ
- Raw session log は既存 `session-store` で保持する。memory 対象外、参照経路のみ
### Knowledge の呼び出し制御

View File

@ -0,0 +1,244 @@
# `llm-worker` vs Rust LLM ライブラリ群 比較レポート
調査日: 2026-04-26
対象: `crates/llm-worker` (本プロジェクト) / `rig` / `genai` / `swiftide`
調査方法: 各リポジトリを external checkout で取得し、ソースREADME, Cargo.toml, src/, examples/)を直接読解。
---
## 0. TL;DR
```
低レイヤ ←──────────────────────────────────────→ 高レイヤ
HTTP wrapper Worker/Loop Agent Framework Pipeline/RAG
───────────── ──────────── ──────────────── ──────────────
genai ●●●●● (out-of-scope)
llm-worker ●●●●● (一部)
rig ●●● ●●●●● ●●●
swiftide ●● ●●●● ●●●●● (主軸)
```
- **`genai`** … マルチプロバイダの「統一 chat client」。エージェントループ・状態管理は明示的にスコープ外。
- **`llm-worker`** … Worker = 「LLM 対話の中央実行器」。低レベルHTTPと高レベルAgent / RAG / Pipelineの中間に位置するレイヤを精緻に作っている。
- **`rig`** … Agent / Tool / Pipeline / VectorStore を一通り揃えた汎用フレームワーク。Provider 数とエコシステムが最大の武器。
- **`swiftide`** … 一次目的はストリーミング Indexing / Query パイプライン。Agent はあとから足された二の矢で、`async-openai` / `async-anthropic` をラップ。
要点: **`llm-worker` の独自性は「型状態でのキャッシュ保護」「Interceptor による上位層への決定委譲」「Prune を context projection として非破壊で扱う」の 3 点**。これらは他 3 者の誰も持っていない。一方、Provider カバレッジ・VectorStore・Pipeline DAG では rig に大差で負ける。
---
## 1. スコープ宣言(各プロジェクトが自分を何と呼んでいるか)
| プロジェクト | 自称 | 提供する | 提供しない(明示・暗黙) |
|---|---|---|---|
| **llm-worker** | "LLM との対話を管理する低レベル基盤クレート" | Worker による turn 実行、Tool 実行、stream イベント、Interceptor、cache 保護 | RAG / Embedding / VectorStore / Pipeline DAG / 永続化 |
| **rig** | "Ergonomic & modular library for building LLM applications" | Agent / Tool / Pipeline / Vector / Embedding / OTel | (ほぼ全部入り。明示的な non-goal は薄い) |
| **genai** | "Standardizing chat completion APIs across major AI services" | Chat (sync/stream)、Tool 定義、Embedding | **Agent loop / 会話状態管理を明示的に out-of-scope** |
| **swiftide** | "Fast, streaming indexing, query, and agentic LLM applications" / "data pipeline" | Indexing pipeline / Query state machine / Agent / Hook | (LLM HTTP は外部 SDK = `async-openai` 等に委譲) |
`llm-worker` のスコープ宣言は他より控えめで、「何かの上位層から呼ばれる前提」が読み取れる(`Interceptor` の存在がそれを裏付ける)。
---
## 2. レイヤ階層と被り
`Worker` が担っているレイヤを基準にすると、各プロジェクトの「同じ層」と「上下層」は次のように整理できる。
### 同じ層(== 競合)
| 機能 | llm-worker | rig | genai | swiftide |
|---|---|---|---|---|
| Provider 抽象 (HTTP) | ◎ 自前 (4) | ◎ 自前 (20+) | ◎ 自前 (18+) | ○ 外部 SDK ラップ |
| Streaming イベント正規化 | ◎ | ◎ | ◎ | ○ |
| Tool 定義 + 実行ループ | ◎ | ◎ | △ (定義のみ、loop は呼び出し側) | ◎ |
| Multi-turn loop | ◎ | ◎ | ✕ | ◎ |
| Hook / Interceptor | ◎ Interceptor + closure | ◎ PromptHook | △ Resolver のみ | ◎ 6+ Hook |
| 履歴管理 | ◎ Item / ContentPart | ◎ Message | △ ChatRequest を呼び出し側で append | ◎ MessageHistory trait |
### 上下層(== 補完関係)
| 機能 | llm-worker | rig | genai | swiftide |
|---|---|---|---|---|
| Embedding / VectorStore | ✕ | ◎ 10+ | △ (Embed のみ) | ◎ 9+ |
| RAG | ✕ | ◎ | ✕ | ◎ |
| Pipeline DAG | ✕ | ◎ `parallel!` / `try_op` | ✕ | ◎ Indexing pipeline |
| 永続化 | ✕ | △ | ✕ | ◎ MessageHistory swap |
| OTel / Observability | △ tracing のみ | ◎ GenAI Semantic Conv | △ | ◎ Langfuse 等 |
| 型状態でのキャッシュ保護 | **◎ 唯一** | ✕ | ✕ | ✕ |
---
## 3. コアの抽象を並べる
### `llm-worker::Worker<C, S>`
- `C: LlmClient`(プロバイダ)、`S: WorkerState``Mutable` | `Locked` の sealed trait
- ライフサイクル: `Mutable` で履歴/プロンプト編集 → `lock()``Locked``run()` / `resume()``unlock()` で戻る
- `state.rs:1-61`: `Locked` 中は履歴 append-only、`set_system_prompt` 等は型レベルで呼べない
- `prune.rs:66-118`: `prunable_indices()` + `project()` で context のみ縮約。永続履歴は不変 = "context projection"
### `rig::Agent` + `PromptRequest`
- 状態機械型: `max_turns`、`extended_details()` で usage / message tracking
- `PromptHook` trait: `on_completion_call`, `on_completion_response`, `on_tool_call` (skip 可), `on_tool_result`, `on_text_delta`, …
- `ToolSet` + `ToolEmbedding` で RAG 可能なツール
- Pipeline DAG: `Op` trait + `parallel!` macro
### `genai::Client`
- `exec_chat(model, ChatRequest, options) -> ChatResponse`
- `ChatStreamEvent { Start, Chunk, ReasoningChunk, ToolCallChunk, End }`
- Adapter pattern: `to_web_request_data` / `to_chat_response` / `to_chat_stream`
- モデル名の prefix で provider 推論 (`gpt-` → OpenAI, `claude-` → Anthropic)
- 状態管理は呼び出し側(`ChatRequest` を毎ターン clone & append
### `swiftide-agents::Agent`
- Builder: `llm`, `context`, `tools`, `toolboxes`, `hooks`
- `AgentContext` trait + `DefaultContext` (AtomicUsize で completions ポインタ管理)
- State enum: `Pending` / `Running` / `Stopped(StopReason)`
- Tool execution: `LocalExecutor` と MCP (Model Context Protocol)
- Provider 層は `async-openai` 0.33+ / `async-anthropic` 0.6 を流用
---
## 4. 三大独自点(`llm-worker` だけが持っているもの)
### 4.1 型状態によるキャッシュ保護 — `state::WorkerState`
```
Worker<C, Mutable> Worker<C, Locked>
├─ history_mut() ✓ ├─ history_mut() ✗ (compile error)
├─ set_system_prompt() ✓ ├─ set_system_prompt() ✗
├─ register_tool() ✓ ├─ run() / resume() ✓
└─ lock() ─────────────────→ │
└─ unlock() ──────────→ Mutable
```
- **何を防ぐか**: KV cache の prefix が変わるような編集を `Locked` 中に行うこと(=次の `run()` で cache miss を引く事故)
- **誰がやっているか**: rig / genai / swiftide のいずれもキャッシュ整合性は「呼び出し側のお気持ち」レベル。genai は `CacheControl::Ephemeral / Stable` enum を持つが、それはマーク用であって状態機械ではない
- **意義**: production の Anthropic / OpenAI prompt cache 利用で、誤った `set_system_prompt` が静かに課金を倍増させる事故を**コンパイル時に潰せる**
### 4.2 Interceptor による上位層への決定委譲 — `interceptor.rs`
`Interceptor` trait が持つフック点:
- `on_prompt_submit``PromptAction`
- `pre_llm_request``PreRequestAction`
- `pre_tool_call``PreToolAction`
- `post_tool_call``PostToolAction`
- `on_turn_end``TurnEndAction`
**rig の `PromptHook` との違い**: rig のフックは「観察 + skip/abort のフラグ」。`llm-worker` は **戻り値が ActionEnum** で、上位層が次の挙動を選択できる(例: `PreToolAction::Override(custom_result)`)。これは `Worker` を「自律ループ」と「上位 orchestrator から駆動される実行器」の両方として使える設計上の選択。
### 4.3 Context Projection による prune — `prune.rs`
- 削るのは `Item::ToolResult { content }` の中身だけ。`Item` 自体は履歴に残すsummary は維持)
- `project()`**イミュータブル変換** で永続履歴を変えない
- savings 推定は意図的に上位層に委譲(`prune.rs` 末尾コメント)
- swiftide も MessageHistory に `Summary` メッセージで似たことをやるが、**永続履歴を直接置換**する方式。`llm-worker` の方が rollback / 再生 / debugging に強い
---
## 5. 各競合の「これは llm-worker に勝っている」点
### rig
- **Provider 数**: 20+OpenAI / Anthropic / Gemini / Bedrock / Vertex / Azure / Groq / DeepSeek / Cohere / Mistral / OpenRouter / Together / xAI / Perplexity / HuggingFace …)
- **VectorStore**: MongoDB / LanceDB / Neo4j / Qdrant / SQLite / SurrealDB / Milvus / ScyllaDB / S3Vectors / HelixDB
- **Pipeline DAG**: `parallel!` macro / `try_op` / agent_opsAgent を Op として組み立て可能)
- **OTel**: GenAI Semantic Convention に完全準拠
- **WASM**: `rig-core` のみ WASM 互換
- **更新頻度**: 直近 20 commit が 1〜2 週間以内
### genai
- **Provider 数**: 18+
- **薄さ**: 6,539 LOC / 35 deps で multi-provider client が完成している(`llm-worker` の `llm_client/` は 1.5k LOC で 4 provider
- **Native protocol サポート**: Anthropic reasoning, Gemini thinking, OpenAI strict mode, OpenAI Responses API の `previous_response_id`/`store` を素通し
- **Tool call streaming**: `ToolCallChunk` が分離イベント
- **Resolver** で auth / model / endpoint をフレキシブルに差し替え
### swiftide
- **Indexing pipeline**: streaming loader → transformer → chunker → storage の fluent パイプライン
- **Query state machine**: Pending → Retrieved → Answered の型状態(くしくも型状態だが対象が違う)
- **Hook 粒度**: `before_all` / `before_completion` / `after_completion` / `before_tool` / `after_tool` / `on_new_message` / `on_stop` 等 6+
- **MCP サポート**: ToolExecutor として LocalExecutor と MCP の両方
- **MessageHistory trait** で永続化バックエンドRedis 等)に差し替え可能
- **既存 SDK の活用**: `async-openai` / `async-anthropic` をベースに薄く乗せる戦略
---
## 6. 各競合の「これは llm-worker の方が良い」点
### vs rig
- **キャッシュ整合性**: rig は `extended_details()` で usage を観測できるが、prefix 変更を**防止**するメカニズムはない
- **依存の薄さ**: rig-core 25.5K LOC + 大量 sub-crate vs llm-worker 5.8K LOC
- **Worker の単一責務**: rig の `Agent` は LLM 呼び出し + Tool + (optional) RAG までを抱え込む。`llm-worker` は orchestrator を分離
### vs genai
- **Tool 実行ループ**: genai は呼び出し側で書く必要があるREADME/example で明示)
- **会話履歴の構造化**: genai は `ChatRequest` を毎ターン clone する素朴な API
- **Interceptor**: genai は `Resolver` で初期化時のカスタマイズのみ。実行中フックなし
### vs swiftide
- **Provider 直接実装**: swiftide は `async-openai` 0.33 / `async-anthropic` 0.6 に依存しており、その上流に出来る/出来ないが縛られる。`llm-worker` は HTTP まで自前で握る
- **Agent loop の中核設計**: swiftide-agents は `DefaultContext` の AtomicUsize ベースで素朴。型状態保護なし
- **依存の少なさ**: swiftide-agents 単体でも `async-openai` を呼ぶ前提
---
## 7. 「ライブラリ化したとき競合するか」の解像度
| 相手 | 競合度 | 理由 |
|---|---|---|
| **rig** | 🟥 高 | 同じ "agent + tool + multi-provider" を狙う層。rig は既にエコシステムを持っており、正面衝突は不利 |
| **genai** | 🟨 中 | 層が違うclient vs worker。むしろ `llm-worker``llm_client/` を捨てて genai に乗る選択肢がある |
| **swiftide** | 🟩 低 | 一次目的が違うpipeline。agent の中核実行を `llm-worker` に置き換える "embed" 戦略すら成立し得る |
---
## 8. ポジショニング案(仮にライブラリ化するなら)
`llm-worker` がぶつからない・かつ需要がある場所を考えると、次のような立ち位置が現実的:
> **"Production-grade LLM worker primitive — 型状態でキャッシュ整合性を守り、Interceptor で上位層 (rig / swiftide / 自社 orchestrator) から駆動できる低レベル実行器"**
具体的な打ち出し方:
1. **キャッシュ整合性をコンパイル時に保証する唯一のクレート** という明確な売り文句。Anthropic の prompt cache 課金事故で困った人を狙う
2. **Tool 実行ループ + Interceptor** をプリミティブとして提供し、上位フレームワークから plug できることを主張
3. **HTTP まで自前** はやめて、`llm_client/` を外す or feature 化し、`genai` バックエンドを optional 提供する選択肢も検討に値する(実装コストを 1.5k LOC 削れる)
4. **Pipeline / RAG / VectorStore は提供しない** ことを明示。rig / swiftide の代替を狙わないことで、共存ストーリーが書ける
---
## 9. ライブラリ化の判断材料(再)
- **需要がある層か**: yes。rig / swiftide / genai は揃って "もう一段下のキャッシュ整合性プリミティブ" を持っていない
- **既存と被るか**: 上記 3 案でかわせる
- **維持コスト**: API 安定化 + provider 追従が恒常的に乗る。`llm_client/` を外すか genai に寄せるかでだいぶ軽くなる
- **タイミング**: insomnia 本体がリリースされ、`Worker` の API が stress test を受ける前に公開すると、後から破壊的変更を強いられる。**先に insomnia をリリースして、production 使用例として参照させてからライブラリ化する**方が安全
---
## 付録: 主要ファイルへの参照
### llm-worker
- `crates/llm-worker/src/lib.rs:1-59` — モジュール構成
- `crates/llm-worker/src/worker.rs:101-191` — Worker<C, S>
- `crates/llm-worker/src/state.rs:1-61` — Mutable / Locked 型状態
- `crates/llm-worker/src/interceptor.rs:1-147` — Interceptor trait
- `crates/llm-worker/src/prune.rs:66-118` — context projection
- `crates/llm-worker/src/llm_client/{auth,client,event,transport,types}.rs`
- `crates/llm-worker/src/tool_server.rs:27-52` — Deferred 登録
### rig
- `github.com/0xPlaygrounds/rig/rig/rig-core/src/{agent,completion,tool,pipeline,vector_store,streaming}/mod.rs`
### genai
- `github.com/jeremychone/rust-genai/src/lib.rs:1-25`
- `.../src/client/client_impl.rs:62-67`
- `.../src/chat/chat_request.rs:10-34`
- `.../src/chat/chat_stream.rs:11-86`
- `.../src/adapter/adapter_types.rs:11-59`
### swiftide
- `github.com/bosun-ai/swiftide/swiftide-agents/src/agent.rs:45-100`
- `.../swiftide-agents/src/hooks.rs`
- `.../swiftide-core/src/chat_completion/traits.rs`
- `.../swiftide-indexing/src/pipeline.rs`
- `.../swiftide-query/src/`

View File

@ -0,0 +1,74 @@
# メモリ機構: ファイル形式 + Linter 土台
## 背景
`docs/plan/memory.md` で決めたメモリ機構の永続化レイヤの土台。`memory/*` と `knowledge/*` の record を保存・編集する際の静的スキーマと、汎用 CRUD への post-write Hook として挟む Linter を成立させる。Phase 1/2、検索ツール、常駐注入、GC はすべてこの層に乗る。
Workflow`docs/plan/workflow.md`)も同じ frontmatter / Linter 経路で扱うため、`memory/workflow/<slug>.md` の frontmatter 検証と書き込み制限も本チケットに含める。実行経路(`/<slug>` dispatchは別。
## 要件
### ディレクトリと record 種別
- `memory/summary.md` — Always-on サマリ1 ファイル固定)
- `memory/decisions/<slug>.md` — Decisions
- `memory/requests/<slug>.md` — Requests
- `memory/workflow/<slug>.md` — Workflowfrontmatter 検証のみ対象)
- `memory/_staging/<id>.json` — Phase 1 中間(本チケットはパス予約と Linter 対象外化のみ)
- `knowledge/<slug>.md` — Knowledge`memory/` の兄弟)
slug は kebab-case小文字英数とハイフン。ファイル名がそのまま識別子で、frontmatter に `id` / `name` は持たない。
### frontmatter スキーマ
種別ごとの必須フィールドは `docs/plan/memory.md` §ファイル形式 / §書き込み経路と Linter の表に従う。具体的な必須項目:
- 共通: `created_at`, `updated_at`
- Decisions: `sources`, `status: open | resolved | replaced`、置き換え時 `replaced_by: <slug>`
- Requests: `sources`
- Knowledge: `kind`, `description`, `model_invokation`, `user_invocable`, `last_sources`
- Summary: `updated_at`optional: `last_rewritten_from_range`
- Workflow: `description`, `auto_invoke`, `user_invocable`, `requires`
### Linter ルール
静的 errorpost-write Hook が turn を戻し、sub-Worker に自己修正させる。N 回失敗で abort:
- frontmatter 必須 field 欠落・型違反
- `memory/workflow/` への書き込み禁止sub-Worker のみ。人間編集は対象外)
- 同 slug での新規作成禁止(既存があれば update に切り替えるサイン)
- `#<slug>` / `replaced_by: <slug>` / `requires: [..]` が実在 record を指す
- Decisions `status` の enum 違反
- `model_invokation: true` な Knowledge の description 1024 chars 上限
- 種別ごとの char 硬上限(具体値は設定ファイルで tune
膨張抑制 Warnerror ではなく改善ヒント):
- 低重要度 × char の天秤
- `sources` 配列長の累積
- 類似 slug 乱立
### 適用経路
- sub-Worker の汎用 CRUDread/write/editへの post-write Hook として挟む
- 人間編集(エディタ / git commitに対しても同一ルールで検証できる CLI または pre-commit hook 経路を用意(詳細は実装で判断、結果として同じルールが一箇所で定義されていること)
## 範囲外
- 検索ツール、常駐注入、Phase 1/2、GC の実装
- 意味破壊rewrite で主張が落ちる等)の検出 — 監査 LLM 層は将来検討
- staging JSON の schema — Phase 1 チケット
- Workflow の `/<slug>` 実行経路
## 完了条件
- 上記パスに手で record を置いて Linter を走らせると、スキーマ違反 / 参照切れ / 同 slug 競合が error として返る
- sub-Worker が CRUD で書き込んだ際、違反時は turn が戻り自己修正が走る
- Warn は error を止めず、出力されることが確認できる
- `memory/workflow/` への sub-Worker からの書き込みは error で止まり、人間編集は通る
- 既存ビルド・テストを壊さない
## 参照
- `docs/plan/memory.md` §ファイル形式 / §書き込み経路と Linter / §Knowledge の採択基準
- `docs/plan/workflow.md` §格納先とファイル形式 / §生成・更新ポリシー

61
tickets/memory-gc.md Normal file
View File

@ -0,0 +1,61 @@
# メモリ機構: GC定期再評価
## 背景
`docs/plan/memory.md` §GC の実装。Phase 2 は情報統合寄りだが、それでも残る重要度低下・類似 slug 乱立・`replaced` 滞留・sources 累積・現状不整合を整理する定期再評価経路。人間 offer はかけず、結果は git diff で検証する建て付け。
保護閾値は使用頻度メトリクスの明示 invoke frequency に依存する。
## 要件
### Trigger
- 定期実行(累積 input token ベース推奨、具体値は設定で tune、実装判断
### 実行主体と渡すツール
- GC Agent が spawn
- Phase 2 と同じ汎用 CRUD + 検索ツール + post-write Linter Hook
- 入力: GC 対象 record 群 + Linter Warn + 使用頻度メトリクス + `replaced` chain + sources 過多情報
### 操作粒度
- ファイル単位: 丸ごと drop / 複数ファイル merge / 1 ファイルの split
- ファイル内部分: 節・箇条の削除 or 圧縮、`sources` 古いエントリの trim
### 評価カテゴリ
`outdated`, `superseded`, `unused`, `noisy` を GC 理由の分類として使う。record に一律の「stale」フラグは付けない。drop / merge / split / trim / rewrite のどれを選ぶかをこのカテゴリで説明可能にする。
### 判断ルール
- 保護閾値: **明示 invoke**`frequency >= 1.0 invokes/Mtoken` の record は drop / 大幅圧縮の対象外
- 初期値 1.0、workspace 設定でカスタマイズ可
- `model_invokation` 注入による常駐は計数対象外(別指標として参照のみ)
- 単一 record が複数カテゴリに該当してもよい
- 直接削除してよいが、誤判定しやすいものは merge / trim を優先prompt 側で誘導)
### prompt
- `docs/plan/memory-prompts.md` §GC prompt に従う
## 範囲外
- 監査 LLM 層(将来検討)
- Vector index / FTS5 導入将来検討、GC 判断には影響しない)
- Workflow の GC 対象化(初期は触らない)
## 完了条件
- Trigger で GC Agent が走り、不要 record が整理される
- Linter Warn で検出した類似 slug 乱立などが GC でまとめて収斂する
- 保護閾値超過 record が drop / 大幅圧縮から外れる
- 置き換えは `status: replaced` + `replaced_by` で残り、直接削除と区別可能
- git diff で drop / merge / split / trim / rewrite の理由が読める
## 参照
- `docs/plan/memory.md` §GC / §使用頻度メトリクス / §判断ルール
- `docs/plan/memory-prompts.md` §GC prompt
- `tickets/memory-phase2-consolidation.md`(ツール構成の共通化)
- `tickets/memory-usage-metrics.md`(保護閾値の依存)

View File

@ -0,0 +1,56 @@
# メモリ機構: Phase 1 活動抽出
## 背景
`docs/plan/memory.md` §Phase 1 の実装。activity tokens の累積閾値で発火し、前回 Phase 1 以降の session log 範囲から「起きたこと」を 4 種の活動ログ候補として抽出、`memory/_staging/<id>.json` に書き出す。Knowledge 化や summary rewrite は Phase 2 に委ねる。
Pod を立てずに既存 compact と同じ Worker spawn 機構を再利用する。raw session log は `session-store` で保持されており、ここから range を切り出して入力に使う。
## 要件
### Trigger
- activity tokens 累積閾値(設定ファイルで tune
- tool call カウントは不採用(ツールカスタマイズ非依存・大小重みづけのため)
### 実行主体と入出力
- 既存 compact の Worker spawn 機構を再利用、Pod は立てない
- 入力: 前回 Phase 1 以降の session log 範囲(処理済み境界 pointer は session 側に保持、寿命を session と揃える)
- 出力 JSON schema: `decisions`, `discussions`, `attempts`, `requests` の候補配列。抽出対象なしは空配列
- 出力に自由文の補足説明を入れさせないschema 準拠のみ)
### 書き込み
- 書き込み先: `memory/_staging/<id>.json`1 件 1 ファイル、UUIDv7 可)
- pod 側ラッパーが `source: { session_id, range: [start_entry, end_entry] }` を**機械付与**して LLM 出力と wrap
- LLM に source を推論させない
### モデル
- 設定 key `memory.extract_model`(軽量だが文脈理解できる中堅クラス想定)
### prompt
- prompt 要件は `docs/plan/memory-prompts.md` §Phase 1: 活動抽出 prompt に従う
## 範囲外
- Phase 2 による staging の消費・クリーンアップ(別チケット)
- staging の cleanup 戦略の詳細Phase 2 で完了時に消す、実行中追加分は残す、という契約だけ本チケットで守る)
- compact Worker spawn 機構自体の拡張(既存をそのまま使う。共通化が必要になったら別途)
## 完了条件
- Pod 稼働中に閾値超過で Phase 1 が発火し、`memory/_staging/<id>.json` にファイルができる
- ファイルは schema に準拠、`source` が機械付与されている
- 抽出対象なしのときは空配列として書き出される(または発火そのものを skip、どちらでもよい
- session 側の処理済み pointer が更新され、次回 Phase 1 は続きから走る
- 既存 compact の動作に回帰がない
## 参照
- `docs/plan/memory.md` §Phase 1: 活動抽出 / §ファイル形式staging
- `docs/plan/memory-prompts.md` §共通原則 / §Phase 1: 活動抽出 prompt
- 既存 `session-store` クレートsession log range 取得)
- 既存 compact の Worker spawn 経路

View File

@ -0,0 +1,80 @@
# メモリ機構: Phase 2 consolidation
## 背景
`docs/plan/memory.md` §Phase 2 の実装。staging の活動ログ + 既存 `memory/*` + Knowledge 化候補レポートを入力に、consolidation Worker が汎用 CRUD + 検索ツール + Linter Hook で agentic に統合する。Phase 1 を終えた pod が spawn し、並走防止は staging 配下の進行状況ファイルで担保する。
Knowledge 新規作成は「候補レポート掲載の source から派生する場合」に限る。使用頻度メトリクス候補レポートの集計元が未完のうちは、レポートは空入力として動作し、Phase 2 は decisions / requests / summary / 既存 Knowledge update のみ行う。
## 要件
### Trigger
- staging の累積ファイル数 or bytes 閾値(設定で tune
- compact 発火時に必ず flushcompact で失われる raw を漏らさない)
### 実行主体と入力
- Phase 1 を終えた pod が consolidation Worker を spawn
- 起動時スナップショットで consumed ID list を確定
- 入力:
- consumed ID 分の staging エントリ(活動ログ + `source`
- 既存 `memory/*`summary / decisions / requests全文
- Knowledge 化候補レポート(メトリクスチケットの成果物。未完のうちは空)
- 既存 `knowledge/*` は prompt に埋めず、Knowledge 検索ツール経由で agent が引く
### 渡すツール
- 汎用 CRUDfile read / write / edit
- memory 検索ツール
- Knowledge 検索ツール
- post-write Linter Hook違反時 turn 戻し、N 回失敗 abort
### 処理内容
- 新規 decisions / requests を 1 件 1 ファイルで追加、`sources` は staging の `source` をコピーLLM 推論ではない)
- 活動ログから派生する Knowledge を新規作成 or 既存 patch。**新規作成は候補レポート掲載の source 由来に限る**
- summary を必要に応じて rewrite1-5k tokens 目安)
- 削除は `status: replaced` + `replaced_by: <slug>` で置き換え記録、直接削除しない
- 書き込み先: `memory/*`, `knowledge/*`。`memory/workflow/` は Linter で弾かれる
### 並走防止
- staging 配下に 1 ファイルPod 識別子 + consumed ID list
- 存在し、示された Pod が動作している間、そのプロセスが排他占有
- 実行中に Phase 1 が追加した staging は触らず、次回 Phase 2Coalesceに委ねる
- 完了時は consumed ID list の staging のみ cleanup、追加分は残す
- Phase 2 完了時に staging 新着があれば次を発火Coalesce
- 占有の実現方法pid 存在確認 / flock / 他)は実装判断
### モデル
- 設定 key `memory.consolidation_model`reasoning 系)
### prompt
- `docs/plan/memory-prompts.md` §共通原則 / §Phase 2: 統合 prompt / §Phase 2: Knowledge 書き込み prompt に従う
## 範囲外
- 使用頻度メトリクスと Knowledge 化候補レポートの集計(別チケット。未完の間は空レポートで動作)
- GC別チケット
- Workflow 関連の offer別チケット、Notification 経路が先)
- 意味破壊検出の監査 LLM 層(将来検討)
## 完了条件
- Phase 1 が staging に残した活動ログを Phase 2 が `memory/*` / `knowledge/*` に統合する
- Linter 違反時は turn が戻り、sub-Worker が自己修正する
- 並走防止ファイルが想定通り機能し、複数 Phase 2 の重複起動が防げる
- Coalesce で実行中追加分が次回に引き継がれる
- compact 発火時に Phase 2 が flush される
- 空レポートでも新規 Knowledge を作らずに動くdecisions / requests / summary / 既存 Knowledge update のみ)
## 参照
- `docs/plan/memory.md` §Phase 2 / §Phase 2 agent への原則 / §Compact との関係
- `docs/plan/memory-prompts.md` §Phase 2 関連
- `tickets/memory-file-format.md`Linter
- `tickets/memory-search-tools.md`(検索ツール)
- `tickets/memory-phase1-extract.md`staging 生産)

View File

@ -0,0 +1,32 @@
# メモリ機構: `model_invokation: ON` の常駐注入
## 背景
`docs/plan/memory.md` §retrieval 経路 の「常駐注入」項目。`model_invokation: ON` な Knowledge record の description を通常 Pod の system prompt に載せ、モデルが必要と判断した時点で検索ツール経由で本文を引く形を成立させる。Phase 2 Pod には注入しない。
専用の auto-invoke 経路は用意しない。モデルが description を見て自発的に検索ツールを呼ぶ経路に一本化する。
## 要件
- Pod 起動時に `knowledge/*` を走査し、`model_invokation: ON` の record の description を system prompt に連結
- Phase 2 Pod では注入しないconsolidation は検索ツール経由で自律探索)
- 予算はシステムプロンプト全体予算に含める。`memory/summary.md` の 5k 枠とは別管理にしない
- 超過時の件数キャップ / 優先順位ルールは初期不要description 1024 chars 上限で通常は収まる前提。ON record 数が増えて問題になったら別チケットで対応
## 範囲外
- auto-invoke 用の別経路(採用しない)
- ON/OFF 切替の自動判定(初期は手動。将来検討)
- Workflow 側の `auto_invoke` 同等機能 — 仕様対称だが本チケットは Knowledge のみ
## 完了条件
- `model_invokation: true` の knowledge を置いた状態で通常 Pod を起動すると、system prompt に description が含まれる
- `model_invokation: false` のものは含まれない
- Phase 2 Pod では注入されない
- 既存の system prompt 構成AGENTS.md / scope summary / skills 等)と共存する
## 参照
- `docs/plan/memory.md` §retrieval 経路 / §Knowledge の呼び出し制御
- `tickets/memory-file-format.md`(依存: `model_invokation` frontmatter

View File

@ -0,0 +1,51 @@
# メモリ機構: memory / Knowledge 検索ツール
## 背景
`docs/plan/memory.md` §retrieval 経路 で定義した 2 本の検索ツールを Pod から呼べる LLM ツールとして実装する。memory 検索と Knowledge 検索は対象ディレクトリが違うだけで同型の仕様。grep ベースで始め、FTS / vector は将来検討。
このツールは Phase 2 Pod の agentic 探索経路としても、通常 Pod の `#<slug>` 展開経路としても、使用頻度メトリクスの観測点としても使う(メトリクスの hook 挿入は本チケットの範囲外、経路だけ揃える)。
## 要件
### ツール仕様(両者共通)
- Input:
- `query: string`(自由文字列、必須)
- `slug: string`(完全一致 1 件返し、`#<slug>` 解決に使う)
- `kind: string`Knowledge のみ、filter
- Output: `{ slug, kind, description, model_invokation, excerpt }` の配列
- `excerpt` はマッチ箇所の前後数行
- ソート: grep 出現順(初期)
- ヒット件数上限と excerpt 行数は設定ファイルで tune
- 対象ファイルは都度スキャン。派生 index は持たない
### 対象
- memory 検索: `memory/{summary,decisions,requests}/*.md`workflow / \_staging は除外)
- Knowledge 検索: `knowledge/*.md`
### 登録
- 通常 Pod と Phase 2 Pod の両方に渡せる tool 定義
- Phase 2 Pod には Knowledge 検索を必ず渡す(全 Knowledge 本文を prompt に埋めない前提)
## 範囲外
- 使用頻度メトリクス本体hook 点の予約のみ。カウント・レポートは別チケット)
- slug サジェスト補完 UITUI 側、別途)
- FTS / vector index
- 常駐注入(別チケット)
## 完了条件
- 通常 Pod / Phase 2 Pod 双方から両ツールが呼べる
- `slug` 指定で完全一致の 1 件が返り、`#<slug>` の本文展開経路として成立する
- `query` 指定で frontmatter 含む全文から excerpt 付きでヒットが返る
- Knowledge 検索の `kind` filter が効く
- 対象外ディレクトリ(`memory/workflow/`, `memory/_staging/`)はヒットしない
## 参照
- `docs/plan/memory.md` §retrieval 経路 / §Knowledge の採択基準
- `tickets/memory-file-format.md`(依存: frontmatter スキーマ)

View File

@ -0,0 +1,60 @@
# メモリ機構: 使用頻度メトリクス + Knowledge 化候補レポート
## 背景
`docs/plan/memory.md` §使用頻度メトリクス の実装。memory 検索ツール / Knowledge 検索ツール内に invoke 計測フックを入れ、時間単位ではなく累積 input token で正規化した頻度を算出する。Phase 2 の Knowledge 新規作成 gate と GC の保護閾値の両方で使われる。
## 要件
### 観測経路
- memory 検索ツール / Knowledge 検索ツール内に hook を挿入
- `#<slug>`slug 完全一致経路)、`/<slug>`workflow 側、将来接続)、明示検索呼び出しをすべて同じ経路に集約
### カウント対象
- **明示 invoke**: 検索ツール経由の読み取り / `#<slug>` / `/<slug>``n回 / Mtoken` でスコア化
- **`model_invokation` 注入**: 明示 invoke の分子には含めない。**コスト側**(注入した record に対する消費 input tokensとして別途記録。使われ率 ratio や ON/OFF 判断材料として後段で参照
- ファイル token 数
### 記録先
- staging と独立
- workspace 側に記録session データ喪失で統計が消えない)
- invoke event を UUID + Stats 形式
- 具体 schema / フォーマットは実装で決定
### 集計
- 累積方式: 最大 10 回前の invoke から現在までの時系列窓でフィルタして集計
- **Knowledge 化候補レポート**:
- 対象は `memory/*` 配下の recordPhase 1 成果物の decisions / requests + 既存 knowledge
- 明示 invoke 頻度が閾値超過のものを列挙
- 同一 session 内の連続参照は 1 count に丸める
- 複数 session での再参照を要件とするspike 除外)
- 閾値は設定ファイルで tune
### 消費者
- Phase 2 Worker の入力として候補レポートを渡す
- GC Agent の保護閾値判定(明示 invoke frequency >= 1.0 invokes/Mtoken
## 範囲外
- GC の実装本体(別チケット。本チケットは保護閾値判定に必要なメトリクスの提供まで)
- `model_invokation` ON/OFF の自動判定ロジック(将来検討)
- Shallow request の自動除外(将来検討)
## 完了条件
- 検索ツール呼び出しで invoke event が workspace 側に積まれる
- `model_invokation` 注入のコスト側集計が別口で積まれる
- 候補レポート API が Phase 2 Worker の起動時に呼べる
- 閾値未満の record は候補レポートに載らない
- 同一 session 内連続参照は 1 count に丸まる
## 参照
- `docs/plan/memory.md` §使用頻度メトリクス / §判断ルール / §retrieval 経路
- `tickets/memory-search-tools.md`hook 挿入点)
- `tickets/memory-phase2-consolidation.md`(消費者)

View File

@ -0,0 +1,81 @@
# サブミット入力: protocol Segment 化
## 背景
現状の `Method::Run { input: String }` はユーザー入力を素の文字列で運んでいる。TUI の `Atom::Paste`bracketed paste で受けた塊)は表示用には `[Clipboard #N | X chars, Y lines]` ラベルを持っているが、submit 時に `InputBuffer::submit_text` で flatten され、Pod や session log にはただの長い user message として伝わる。
memory / workflow 導入に伴い、submit には paste 以外にも以下を載せたくなる:
- `@<path>` のファイル参照 + auto_read
- `#<slug>` の Knowledge 参照(`docs/plan/memory.md`
- `/<slug>` の Workflow 起動(`docs/plan/workflow.md`
これを protocol 上で **String + marker パース**で扱うと、Pod が正規表現と escape 規則を抱え、code block 内の `#foo` を literal にするか等の ambiguity と escape 漏洩が常に付きまとう。さらにリッチクライアントGUI / web / ネイティブ)はファイル添付や paste を file chip / clipboard card として描くため、protocol が String だと「chip → string marker → Pod で再 parse → 表示用に再構築」という 3 段 round-trip を全 client で再実装する形になる。
protocol を typed segment の列に上げると、Pod は parser を持たず resolve に集中でき、client は自分の表現単位のまま intent を送れる。最低限 `Segment::Text(String)` を fallback として置くので、CLI piping 等の dumb client は 1 segment を作るだけで従来通り動くprotocol の dictation が「Text を作れること」に閉じる)。
## 要件
### protocol
`Method::Run` の payload を `String` から `Vec<Segment>` に置き換える。Segment は最低限以下の variant を持つ:
- `Text` — 自由文字列。dumb client の fallback
- `Paste` — TUI 等の bracketed paste 由来の塊。`id` と本文を持つ
- `FileRef``@<path>` の auto_read 対象
- `KnowledgeRef``#<slug>` の Knowledge 参照
- `WorkflowInvoke``/<slug>` の Workflow 起動
`Event::UserMessage` の payload も同じ `Vec<Segment>` に揃え、複数 client で見たり log replay する際に typed のまま再構築できる状態にする。
未知 variant に対する forward compatibility`#[serde(other)]` 相当の吸収)を入れる。
### Pod 側 resolve
Pod は `Vec<Segment>` を LLM context へ flatten する単一経路を持つ:
- `Text` → そのまま
- `Paste` → 本文を inline 展開(現状 TUI が flatten してきたのと同じ結果)。**session log / Event::UserMessage 上ではラベル化情報を保持**し、表示時に `[Clipboard #N | X chars]` を再構築できること
- `FileRef` → scope 内なら本文 read + inline、scope 外は明示エラー
- `KnowledgeRef` / `WorkflowInvoke` → resolver が登録されていれば展開、未登録はフォールバック(後述)
resolver の trait 化と memory / workflow 用 resolver 実装は別チケット。本チケットは **Text と Paste を typed のまま end-to-end で運び切る経路**で完結させる。`FileRef` 以降は variant 定義と「resolver 未登録時のフォールバック」だけ仕込んで実装は後続に委ねる。
### TUI 側
`InputBuffer::Atom::Paste` を submit 時に flatten せず、`Segment::Paste` として送出する。`Event::UserMessage` を typed segment で受けて `Block::UserMessage` を typed のまま描画paste は引き続き magenta ラベル)。
text しか作れない client が引き続き存在しても良いことを protocol 仕様に明記する(`vec![Segment::Text(_)]` のみで動く)。
### unknown variant / 未登録 resolver の扱い(要決定)
新 variant を持つ client が古い Pod に投げる、または resolver 未登録の variant を Pod が受けるケースの挙動を本チケットで決める。候補:
- (a) 黙って drop — 静かに情報が落ちて user/LLM 双方が気付けない
- (b) `[unknown input: kind=foo]` 相当の placeholder を LLM context に差し込み、LLM が気づいて指摘できる
- (c) hard error で submit 拒否
(b) を仮の第一候補として実装方針を詰める。serde 側は unknown variant を吸収する形を初期から入れる。
## 範囲外
- `@` / `#` / `/` token の TUI parsing と補完 UI`tickets/submit-tui-completion.md`
- KnowledgeRef / WorkflowInvoke の resolver 実装memory / workflow チケット)
- FileRef の引数拡張行範囲、glob 等)
## 完了条件
- `Method::Run` / `Event::UserMessage``Vec<Segment>` で wire を通る
- TUI から paste した内容が `Segment::Paste` で送出され、Pod の LLM context では本文展開、Event 経由の再描画では `[Clipboard #N | ...]` が復元される
- `vec![Segment::Text(_)]` だけ送る client は従来通り動く
- 未登録 variant の扱いが決定済みルール通りに動く(決定は本チケット内で合意)
- forward compat: 未知 variant を含む payload を deserialize しても panic / parse error にならない
- 既存ビルド・テストを壊さない
## 参照
- `docs/plan/memory.md` §retrieval 経路 / §Knowledge の呼び出し制御
- `docs/plan/workflow.md` §呼び出しと依存
- `crates/protocol/src/lib.rs``Method::Run`, `Event::UserMessage`
- `crates/tui/src/input.rs``Atom::Paste`, `submit_text`
- `crates/tui/src/app.rs``submit_input`, `Block::UserMessage` 描画)

View File

@ -0,0 +1,61 @@
# サブミット入力: TUI 補完 + 型付き atom 化
## 背景
`tickets/submit-segment-protocol.md` で protocol が `Vec<Segment>` を運べるようになった前提で、TUI 側に「`@` / `#` / `/` を打鍵中に候補を出し、確定したら typed atom (= 送出時の `Segment`) に昇格させる」UX を載せる。
`@` / `#` / `/` は TUI の入力アフォーダンスであって protocol contract ではないGUI などは同じ intent をボタンや picker で表す)。本チケットは TUI 限定の UX に閉じる。
## 要件
### token 検出と昇格
入力中に prefix を検出して候補を浮かせる:
- `@<部分パス>` — workspace 内のファイル / ディレクトリ候補
- `#<部分slug>` — Knowledge slug
- `/<部分slug>` — Workflow slug + client-side コマンド(`/clear` など)
確定Tab / Enter 等、入力 UX の詳細は実装で)で対象範囲を `Atom::FileRef` / `Atom::KnowledgeRef` / `Atom::WorkflowInvoke` の indivisible atom に置き換える。挙動は既存の `Atom::Paste` と同等cursor は中に入れない、Backspace で塊ごと削除。submit 時に対応する `Segment` 変種に変換して送る。
### 候補列挙のための protocol query
補完用に Pod へ問い合わせる軽量経路を追加:
- ファイル候補scope 内、prefix マッチ)
- Knowledge / Workflow slug 候補kind 指定 + prefix マッチ)
`Event` ストリームに載せる性質ではないため、request/response 形式を新設する(具体形式は実装で判断、既存 `Method` の枠に増やすか別経路を作るかも実装側で決める)。
### 表示
確定後の atom は paste と同じ「indivisible chip」スタイルで描画する。`@` / `#` / `/` ごとに色を変える程度の差異化を入れる。`Block::UserMessage` 側でも同一スタイルで再描画する(`Event::UserMessage` が typed segment で来る前提)。
### client-side `/<slug>` の dispatch
`/clear` のような client 完結コマンドは Pod に送らず TUI 内で処理する。TUI 内に簡易な dispatch 表を持ち、未知の `/<slug>``Segment::WorkflowInvoke` として送る。初期 dispatch 表は `/clear` 程度で良く、拡張は別途。
## 範囲外
- Pod 側の resolver 実装memory / workflow チケット)
- 候補スコアリング、fuzzy search、preview 等の高度な補完体験
- リッチクライアントGUI / webの同等 UX
## 完了条件
- `@` / `#` / `/` を打鍵すると候補が出て、確定で chip 化される
- chip 化された atom が対応する `Segment` として Pod に送出され、`Event::UserMessage` で戻ってきた typed segment が同じ見た目で再描画される
- 候補列挙の query / response が wire を通る
- `/clear` が client-side で処理され、Pod には届かない
- 既存ビルド・テストを壊さない
## 依存
- `tickets/submit-segment-protocol.md`
## 参照
- `crates/tui/src/input.rs``Atom` 体系の拡張)
- `crates/tui/src/app.rs``submit_input`、`Block::UserMessage` 描画)
- `docs/plan/memory.md` §retrieval 経路slug 補完対象)
- `docs/plan/workflow.md` §呼び出しと依存

View File

@ -1,11 +0,0 @@
# テスト設計
## 背景
各クレートのテスト方針が未策定。クレート間の依存関係と非同期処理が絡むため、
テストの層(単体/結合/E2Eと mock 境界を明確にする必要がある。
## 検討事項
低層部分のテストを信頼し、上層までモックデータを引っ張ってきてテストする必要は無いのか?
実際の認証を使ったE2Eはどの様に結果を出すか/