From b62b5c47b22ed4d7c26b090a3ab5a1b50f5cdf4a Mon Sep 17 00:00:00 2001 From: Hare Date: Mon, 27 Apr 2026 18:30:21 +0900 Subject: [PATCH] =?UTF-8?q?memory-resident-injection=E5=AE=8C=E4=BA=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TODO.md | 1 - crates/memory/src/workspace.rs | 31 +++++++++++++ crates/pod/src/controller.rs | 6 +-- crates/pod/src/pod.rs | 12 +---- tickets/memory-phase2-consolidation.md | 6 +++ tickets/memory-resident-injection.md | 37 --------------- tickets/memory-resident-injection.review.md | 50 --------------------- 7 files changed, 40 insertions(+), 103 deletions(-) delete mode 100644 tickets/memory-resident-injection.md delete mode 100644 tickets/memory-resident-injection.review.md diff --git a/TODO.md b/TODO.md index bb5a651f..39d16210 100644 --- a/TODO.md +++ b/TODO.md @@ -15,7 +15,6 @@ - [ ] TUI 補完 + 型付き atom 化 → [tickets/submit-tui-completion.md](tickets/submit-tui-completion.md) - [ ] セッションログの Segment 保持 → [tickets/session-log-segments.md](tickets/session-log-segments.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) diff --git a/crates/memory/src/workspace.rs b/crates/memory/src/workspace.rs index 37860f43..6ba489e3 100644 --- a/crates/memory/src/workspace.rs +++ b/crates/memory/src/workspace.rs @@ -63,6 +63,20 @@ impl WorkspaceLayout { Self { root: root.into() } } + /// Resolve a layout from a `MemoryConfig`, falling back to + /// `default_root` (typically the Pod's pwd) when the manifest does + /// not pin `workspace_root` explicitly. Single source of truth for + /// the `workspace_root.unwrap_or(pwd)` convention used across the + /// codebase (controller wiring, scope-deny build, system-prompt + /// resident-injection). + pub fn resolve(cfg: &manifest::MemoryConfig, default_root: &Path) -> Self { + let root = cfg + .workspace_root + .clone() + .unwrap_or_else(|| default_root.to_path_buf()); + Self::new(root) + } + pub fn root(&self) -> &Path { &self.root } @@ -295,4 +309,21 @@ mod tests { .unwrap_err(); assert!(matches!(err, LintError::InvalidPath(_))); } + + #[test] + fn resolve_uses_workspace_root_when_set() { + let cfg = manifest::MemoryConfig { + workspace_root: Some(PathBuf::from("/explicit")), + ..Default::default() + }; + let layout = WorkspaceLayout::resolve(&cfg, Path::new("/fallback")); + assert_eq!(layout.root(), Path::new("/explicit")); + } + + #[test] + fn resolve_falls_back_to_default_when_workspace_root_missing() { + let cfg = manifest::MemoryConfig::default(); + let layout = WorkspaceLayout::resolve(&cfg, Path::new("/fallback")); + assert_eq!(layout.root(), Path::new("/fallback")); + } } diff --git a/crates/pod/src/controller.rs b/crates/pod/src/controller.rs index 6cd627b4..21d93c99 100644 --- a/crates/pod/src/controller.rs +++ b/crates/pod/src/controller.rs @@ -241,11 +241,7 @@ impl PodController { // companion deny rules on the generic CRUD scope were // already applied during `Pod::from_manifest`. if let Some(mem) = memory_config.as_ref() { - let workspace_root = mem - .workspace_root - .clone() - .unwrap_or_else(|| pwd_for_tools.clone()); - let layout = memory::WorkspaceLayout::new(workspace_root); + let layout = memory::WorkspaceLayout::resolve(mem, &pwd_for_tools); let search_cfg = memory::tool::SearchConfig::from(mem); worker.register_tool(memory::tool::read_tool(layout.clone())); worker.register_tool(memory::tool::write_tool(layout.clone())); diff --git a/crates/pod/src/pod.rs b/crates/pod/src/pod.rs index ebef774c..757a980a 100644 --- a/crates/pod/src/pod.rs +++ b/crates/pod/src/pod.rs @@ -569,11 +569,7 @@ impl Pod { .memory .as_ref() .map(|mem| { - let workspace_root = mem - .workspace_root - .clone() - .unwrap_or_else(|| self.pwd.clone()); - let layout = memory::WorkspaceLayout::new(workspace_root); + let layout = memory::WorkspaceLayout::resolve(mem, &self.pwd); memory::collect_resident_knowledge(&layout) }) .unwrap_or_default() @@ -1567,11 +1563,7 @@ pub enum PodError { fn build_scope_with_memory(manifest: &PodManifest, pwd: &Path) -> Result { let mut scope_config = manifest.scope.clone(); if let Some(mem) = manifest.memory.as_ref() { - let root = mem - .workspace_root - .clone() - .unwrap_or_else(|| pwd.to_path_buf()); - let layout = memory::WorkspaceLayout::new(root); + let layout = memory::WorkspaceLayout::resolve(mem, pwd); scope_config.deny.extend(memory::deny_write_rules(&layout)); } Scope::from_config(&scope_config).map_err(PodError::Scope) diff --git a/tickets/memory-phase2-consolidation.md b/tickets/memory-phase2-consolidation.md index 07e3fa98..47e50b83 100644 --- a/tickets/memory-phase2-consolidation.md +++ b/tickets/memory-phase2-consolidation.md @@ -30,6 +30,12 @@ Knowledge 新規作成は「候補レポート掲載の source から派生す - Knowledge 検索ツール - post-write Linter Hook(違反時 turn 戻し、N 回失敗 abort) +### 常駐注入の無効化 + +- consolidation Worker の Pod 起動直後に `Pod::set_resident_knowledge_injection(false)` を呼ぶ +- `model_invokation: ON` の Knowledge description を system prompt に載せず、Knowledge 検索ツール経由で agent に引かせる方針(本セクション「入力」と整合) +- 仕組み自体は `tickets/memory-resident-injection.md` で導入済み。lever は用意されているが Phase 2 spawn 経路がまだないので、本チケットの実装範囲で必ず呼ぶこと + ### 処理内容 - 新規 decisions / requests を 1 件 1 ファイルで追加、`sources` は staging の `source` をコピー(LLM 推論ではない) diff --git a/tickets/memory-resident-injection.md b/tickets/memory-resident-injection.md deleted file mode 100644 index ba064c7d..00000000 --- a/tickets/memory-resident-injection.md +++ /dev/null @@ -1,37 +0,0 @@ -# メモリ機構: `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) - -## Review -- 状態: Approve -- レビュー詳細: [./memory-resident-injection.review.md](./memory-resident-injection.review.md) -- 日付: 2026-04-27 diff --git a/tickets/memory-resident-injection.review.md b/tickets/memory-resident-injection.review.md deleted file mode 100644 index d3d19b9f..00000000 --- a/tickets/memory-resident-injection.review.md +++ /dev/null @@ -1,50 +0,0 @@ -# Review: メモリ機構 `model_invokation: ON` の常駐注入 - -## 前提・要件の確認 - -- 「Pod 起動時に `knowledge/*` を走査し、`model_invokation: ON` の record の description を system prompt に連結」 - - `crates/memory/src/resident.rs:25` の `collect_resident_knowledge` が `/knowledge/*.md` を走査し、`KnowledgeFrontmatter` を deserialize して `model_invokation: true` のみ採用、slug 順にソートして返す。`crates/pod/src/pod.rs:567-588` で system prompt 生成時に呼び出され、`crates/pod/src/prompt/system.rs:223-231` で `## Resident knowledge` セクションが trailing 部に追記される。要件充足。 -- 「`model_invokation: false` のものは含まれない」 - - `resident.rs:58` で `if fm.model_invokation` 判定。`picks_only_model_invokation_true` テストで担保済み。 -- 「Phase 2 Pod では注入しない(consolidation は検索ツール経由)」 - - `Pod::set_resident_knowledge_injection(false)` の lever が用意され、`ensure_system_prompt_materialized` 内で `inject_resident_knowledge` フラグと `manifest.memory.is_some()` の両方を条件に注入。Phase 2 Pod の実装はまだ存在しないため、現時点では「lever は用意されたが呼び出し側がない」状態(後述 Follow-up 参照)。 -- 「既存の system prompt 構成(AGENTS.md / scope summary / skills 等)と共存」 - - `append_trailing_section` で Working boundaries → AGENTS.md → Resident knowledge の順で追記。`trailing_section_renders_resident_knowledge_entries` テストで順序検証あり。共存 OK。 -- 「予算はシステムプロンプト全体予算に含める。`memory/summary.md` の 5k 枠とは別管理にしない」 - - 別バジェット管理は導入されていない。要件通り。 -- 「初期は件数キャップ / 優先順位ルール不要」 - - 単純に slug 順で全件出力。要件通り。 - -## アーキテクチャ・スコープ - -- レイヤ境界: 走査ロジックは `memory` クレートに置かれ、`pod` 側は `memory::collect_resident_knowledge` を呼び出すだけ。レイヤ責務に整合。 -- catalog 拡張: `PodPrompt::ResidentKnowledgeSection` の追加と `internal.toml` の対応エントリは既存パターン(`AgentsMdSection` 等)に揃っている。`ALL` / `KEYS` の同期と build-time 検査がそのまま機能する。 -- prompt rendering: 文字列フォーマットは `format_resident_knowledge_entries` として system.rs にローカル化。テンプレートは `entries` を pre-formatted で受け取るので、後で「list 以外の表現にしたい」になっても catalog 側の差し替えで済む(チケットの「フォーマットは初期 simple リスト、後で再検討」と整合)。 -- スコープ膨張は感じられない。新規追加は約 200 行で、要件達成のために必要な最小構成に近い。 - -## 指摘事項 - -### Blocking - -なし。 - -### Non-blocking / Follow-up - -- `manifest.memory` の `workspace_root` 解決ロジック(`mem.workspace_root.clone().unwrap_or_else(|| pwd.clone())` + `WorkspaceLayout::new`)が今回の追加で 3 箇所に増えた: - - `crates/pod/src/controller.rs:244-248` - - `crates/pod/src/pod.rs:567-577`(今回追加) - - `crates/pod/src/pod.rs:1567-1577`(`build_scope_with_memory`) - これ自体は本チケットで生まれた重複ではなく既存パターンの踏襲だが、3 箇所目に達した時点で `MemoryConfig::workspace_layout(pwd: &Path) -> WorkspaceLayout` のような小さなヘルパに寄せておくと健全。本チケット範囲外で OK。 -- 統合テスト: 単体では `collect_resident_knowledge` と `append_trailing_section` の挙動が個別に担保されているが、Pod の `ensure_system_prompt_materialized` を通る経路(`knowledge/*.md` を置いた状態で system prompt が組み上がるところまで)の end-to-end 確認はない。回帰防止としては unit 2 種で十分カバー範囲に入っているとも読めるが、`inject_resident_knowledge=false` / `manifest.memory=None` の枝を実際の Pod 経路で踏むテストがあると配線ミスを早期に検知できる。 -- Phase 2 Pod 自体が未実装で、`set_resident_knowledge_injection(false)` を呼ぶ箇所がない。`tickets/memory-phase2-consolidation.md` 側で「Phase 2 spawn 時にこの setter を呼ぶ」旨を明記しておかないと、将来「lever はあるが誰も呼ばずに常駐注入されてしまう」事故になりうる。Phase 2 チケット側に注記推奨。 -- `internal.toml` の `resident_knowledge_section` 文言 ("Use the KnowledgeSearch / MemoryRead tools to fetch the full body when relevant.") はモデル向けの英語固定。多言語 prompt pack を作る運用になった時点で overlay で差し替える前提なので現状で問題ないが、ツール名の改名が起きたら追従が必要(`KnowledgeSearch` / `MemoryRead` が tool catalog 側の正式名と一致しているか確認推奨)。 -- `format_resident_knowledge_entries` の改行畳み込み: linter は `description` の長さ上限(1024 chars)は強制するが「単一行」は強制していないので、defensive な `\n` / `\r` → space 変換は妥当な防御。挙動は `trailing_section_renders_resident_knowledge_entries` でカバー済み。 - -### Nits - -- `resident.rs` のテストヘルパ `write_knowledge` は description を `"{description}"` で raw quote しているため、「description に改行が混ざるケース」は単体テストでは触れていない。改行畳み込みは `prompt::system` 側のテストで担保されているので二重には不要だが、collector 側で意図的に `\n` を含む description を 1 件入れて round-trip 確認すると堅牢。 -- `pod.rs:567-588` の `resident` / `resident_slice` の二段構えは `Vec` を所有しつつ `Option<&[..]>` を欲しい、という要件のための定石だが、コメントを 1 行足しておくと後続読者に親切(owned `Vec` がスコープを跨ぐ理由)。すでに `// Owned `Vec` lives for the duration of `render` below` の注釈はある。十分。 - -## 判断 - -**Approve** — チケットの要件は実装側で漏れなく満たされており、レイヤ責務 / 既存パターンとも整合。Phase 2 側の lever 呼び出しは Phase 2 チケットに引き継ぐ形で問題なく、本チケットの完了条件は満たしている。