diff --git a/TODO.md b/TODO.md index 33d923f1..08c53032 100644 --- a/TODO.md +++ b/TODO.md @@ -1,5 +1,4 @@ - [ ] Workflow / Skills - - [ ] Workflow 実装 → [tickets/workflow.md](tickets/workflow.md) - [ ] 内部 Worker / 内部 Pod の Workflow 化 → [tickets/internal-worker-workflow.md](tickets/internal-worker-workflow.md) - [ ] Agent Skills を Workflow として ingest → [tickets/agent-skills.md](tickets/agent-skills.md) - [ ] ツール設計 diff --git a/tickets/workflow.md b/tickets/workflow.md deleted file mode 100644 index 68d98594..00000000 --- a/tickets/workflow.md +++ /dev/null @@ -1,83 +0,0 @@ -# Workflow 実装 - -## 背景 - -`docs/plan/workflow.md` で決まった「制約付きの強制的な作業フロー」を `/` で呼び出せるようにする。Knowledge (`#`) を依存として inject できる経路を持つことで、procedural な能力を再利用可能な単位に固定する。 - -memory 機構(`docs/plan/memory.md`)からは独立してスタートできる: Workflow は人間が書く / consolidation の offer 経由でしか作られず、自動書き込み禁止のため Phase 2 の前提に依存しない。Knowledge resolver は `requires` の inject 経路として相互依存する。 - -agent-skills (agentskills.io 形式) は本チケットの ingest 経路を再利用して Workflow として読み込む側になる(`tickets/agent-skills.md` 参照)。 - -## 決定事項の参照 - -詳細は `docs/plan/workflow.md` 参照。要点のみ: - -- 呼び出し: `/`、フラットな名前空間、kebab-case -- 配置: `/.insomnia/memory/workflow/.md`(ファイル名 = slug、frontmatter に `name` を持たない) -- frontmatter: `description` / `model_invokation` (default OFF) / `user_invocable` (default ON) / `requires: [knowledge-slug, ...]` -- 実行: `requires` の Knowledge 本文を context に inject してから Workflow 本文を実行 -- 自動書き込み禁止(consolidation の write tool schema に `workflow` カテゴリを含めないことで構造的に担保。Linter で人間にも見える形で再保証) - -## 方針 - -### MVP スコープ - -1. **Workflow loader / 検証** - - `/.insomnia/memory/workflow/*.md` を走査 - - frontmatter を仕様通り検証。必須欠落・型不一致・slug とファイル名の不一致は hard error(Pod 起動失敗) - - 未知フィールドは `tracing::warn!` して無視(既存 manifest と同方針) - - 重複 slug は最初に見つかったものを採用 + warn(後述の skill ingest が乗ると衝突解決ルールが追加で必要になる) - -2. **`/` 呼び出し経路** - - `Segment::WorkflowInvoke { slug }` を Pod 側で resolve - - 解決失敗(slug 未登録 / `user_invocable: false`)は `ToolError` 相当でユーザーに返し、Worker には届かない - - `requires` の Knowledge 本文を Knowledge 検索ツールの slug 完全一致経路で取得し、Workflow 本文の前に context へ inject - - Workflow 本文は Markdown のままサブミット内容として扱う(DSL 化はしない) - -3. **`model_invokation` 注入** - - `model_invokation: true` な Workflow の `description` を通常 Pod の system prompt に常駐注入する。Phase 2 prompt には入れない - - 予算は Knowledge の常駐注入(`memory.md` §retrieval 経路)と合算管理。description 上限は agentskills 準拠の 1024 chars に揃える - -4. **Linter ルール** - - `memory/workflow/*.md` への write/edit は memory 専用 Tool 経由でのみ許可(汎用 Write/Edit は Scope deny) - - consolidation の write tool schema からは `workflow` カテゴリを除外(自動書き込み禁止の構造的担保) - - Workflow 自体の Linter は frontmatter 検証 + slug/ファイル名一致のみ。意味検証は将来検討 - -### 範囲外 - -- DSL 化や step 粒度の制約(Markdown 本文をそのまま実行) -- 中断・再開・トランザクション管理 -- 品質検証フロー(mizchi empirical-prompt-tuning 相当、`docs/plan/workflow.md` §将来検討) -- LLM による Workflow 自律生成(offer までで留める方針は本チケットでは扱わず、consolidation 側の責務) -- Knowledge 検索ツール本体の実装(memory チケット側)。本チケットは slug 完全一致経路の利用者に留まる - -## 完了条件 - -- `/.insomnia/memory/workflow/*.md` をロードし、frontmatter 違反は Pod 起動エラーになる -- `/` を含む submit が `Segment::WorkflowInvoke` として送られ、Pod 側で `requires` Knowledge を inject した上で本文が実行される -- `model_invokation: true` の Workflow description が通常 Pod の system prompt に列挙される -- `user_invocable: false` の Workflow は `/` 補完候補から除外され、明示呼び出しもエラーになる -- 単体テストで frontmatter 検証の正常 / 異常系、`requires` 解決、フラグ別の挙動が verify される - -## 実装順序 - -1. `manifest` または既存 memory クレートに `Workflow` 構造体と `WorkflowDirectoryLoader` を置く。frontmatter パースと検証のみでテスト完結 -2. Pod に Workflow registry を持たせ、`model_invokation` description の system prompt 注入を組む -3. `Segment::WorkflowInvoke` の resolver を Pod 側に実装。Knowledge 検索ツールの slug 完全一致経路で `requires` を inject -4. 汎用 Write/Edit に対する `memory/workflow/` deny を Scope に追加、Linter 仕上げ - -各ステップ終了時点でビルド通過・既存テスト合格を維持する。 - -## 参照 - -- 設計: `docs/plan/workflow.md` -- Knowledge / `#` の retrieval: `docs/plan/memory.md` §retrieval 経路 -- Submit segment: `tickets/submit-tui-completion.md`(`Atom::WorkflowInvoke`)、`tickets/session-log-segments.md` -- 後続: `tickets/agent-skills.md`(外部 SKILL を Workflow として ingest する経路) - -## Review - -- 状態: Approve -- レビュー詳細: [./workflow.review.md](./workflow.review.md) -- 日付: 2026-05-02 -- 主要指摘: なし(フォローアップ/nit レベルのみ)。 diff --git a/tickets/workflow.review.md b/tickets/workflow.review.md deleted file mode 100644 index 6df0bf45..00000000 --- a/tickets/workflow.review.md +++ /dev/null @@ -1,69 +0,0 @@ -# Review: Workflow 実装 - -## 前提・要件の確認 - -- **Workflow loader / 検証**: - - `/.insomnia/memory/workflow/*.md` のスキャン → `crates/memory/src/workflow.rs:120-180`(`load_workflows`)。OK。 - - frontmatter 必須欠落・型不一致は hard error → `WorkflowLoadError::Frontmatter` 経由で `PodError::WorkflowLoad` まで持ち上がる(`crates/pod/src/pod.rs:2310-2315`)。OK。 - - slug とファイル名の不一致は `Slug::parse(stem)` で hard error 化 → `crates/memory/src/workflow.rs:154-159`。`Slug::parse` の kebab-case 強制が一致検証を兼ねている。OK。 - - 未知フィールドは `tracing::warn!` → `warn_unknown_workflow_fields`(`crates/memory/src/workflow.rs:218-238`)。OK。 - - 重複 slug は最初を採用 + warn → `crates/memory/src/workflow.rs:148-152`。OK。 - -- **`/` 呼び出し経路**: - - `Segment::WorkflowInvoke` を `Pod::resolve_workflow_invocations` で resolve → `crates/pod/src/pod.rs:712-740`。 - - 解決失敗(未登録 / `user_invocable: false`)は Worker に届ける前に `Event::Error { code: InvalidRequest }` で返す → `crates/pod/src/controller.rs:366-372`、`Pod::validate_workflow_invocations`(`pod.rs:746-769`)。OK。 - - `requires` は `/.insomnia/knowledge/.md` を `WorkspaceLayout::knowledge_path` 経由で直接読み、`Item::system_message` として Workflow 本文の前に積む(`crates/pod/src/workflow/mod.rs:90-130`)。ticket の「slug 完全一致経路で取得」は満たす。 - - Markdown 本文をそのまま利用、DSL 化なし。OK。 - -- **`model_invokation` 注入**: - - `WorkflowRegistry::resident_entries` → `Pod::system_prompt` で `resident_workflow_slice` として `SystemPromptContext` に注入し、`resident_workflows_section` テンプレートで `## Resident workflows` 節を末尾に追加する(`crates/pod/src/pod.rs:592-612`、`crates/pod/src/prompt/system.rs:227-240`、`resources/prompts/internal.toml:47-54`)。OK。 - - description 上限 1024 chars(`WORKFLOW_DESCRIPTION_HARD_CAP`)を `model_invokation: true` の場合のみロード時に強制 → `crates/memory/src/workflow.rs:170-179`。Knowledge と同じポリシーで一貫。OK。 - - Phase 2 prompt には入れない: `set_resident_knowledge_injection(false)` 系のフラグで `inject_resident_knowledge` を落とすと workflow 注入も同時に無効化される(`pod.rs:589-598`)。OK。 - -- **`user_invocable: false` の挙動**: - - 補完候補から除外 → `WorkflowRegistry::list_user_invocable` は `record.user_invocable` で filter(`crates/memory/src/workflow.rs:67-73`)。`shared_state.list_workflow_completions` が IPC `ListCompletions` に流れる(`crates/pod/src/ipc/server.rs:106-115`)。OK。 - - 明示呼び出しはエラー → `validate_workflow_invocations` が `NotUserInvocable` を返し `InvalidRequest` で client に返却。OK。 - -- **Linter ルール**: - - 汎用 Write/Edit に対する `memory/workflow/` deny: `deny_write_rules` が `memory_dir()` を recursive deny するので `memory/workflow/` も covered(`crates/memory/src/scope.rs:19-24`)。OK。 - - memory tool 自身の workflow 書き込み禁止: `RecordKind::Workflow` を `Linter::lint` 冒頭で `LintError::WorkflowWriteForbidden` として弾く(`crates/memory/src/linter/mod.rs:101-105`)。`MemoryToolKind` enum に `Workflow` を含めないことで write/edit tool 自体が deserialize 段階で拒否(`crates/memory/src/tool/mod.rs:26-27`、`tool/write.rs:236-247`、`tool/edit.rs:242-253`)。OK。 - - consolidation の write tool schema に `workflow` カテゴリは含まれず(`crates/memory/src/consolidate/`、`extract/` に workflow への書き込み経路なし)。OK。 - - `lint_workflow` で frontmatter 検証 + `requires` 整合性チェック(`crates/memory/src/linter/mod.rs:250-280`)。OK。 - -- **単体テスト**: - - frontmatter 検証の正常系: `loads_valid_workflow_with_default_flags` / 異常系: `invalid_filename_is_hard_error`、`missing_description_is_hard_error`(`crates/memory/src/workflow.rs` テスト)。 - - `requires` 解決: `resolves_requires_before_workflow_body`、`missing_required_knowledge_errors`(`crates/pod/src/workflow/mod.rs` テスト)。 - - フラグ別の挙動: `model_invokation_uses_typo_field`、`user_invocable_false_errors`。 - - すべてパス確認済み(`cargo test -p memory --lib workflow`、`cargo test -p pod --lib workflow`)。OK。 - -完了条件はすべて満たされている。 - -## アーキテクチャ・スコープ - -- 低レベル基盤の `memory` クレートに loader / registry / linter を置き、Pod 側は resolver と registry の保持に留める。`llm-worker` 直下には触らず層分けは保たれている。OK(`feedback_llm_worker_scope` の方針に整合)。 -- 新規モジュールはいずれもファイル名のみで `insomnia-` プレフィックスなし。OK。 -- `crates/memory/src/workflow.rs` を新設し、schema/linter は既存ファイルへの最小追記。`pod.rs` への注入は既存の resident knowledge 経路を素直に拡張する形で、過剰な抽象化はない。`format_resident_entries` を `(slug, description)` のイテレータで共通化したのも妥当(`prompt/system.rs:255-285`)。 -- `WorkflowResolveError` は `crates/pod/src/workflow/mod.rs` 内に閉じ、`PodError::WorkflowResolve` で `From` 実装を介してチェイン。Pod 公開 API は `validate_workflow_invocations` / `workflow_completions` の 2 つのみで surface も最小。OK。 -- 依存追加は Cargo.lock の差分にあるが新規 crate 依存は導入されていない(既存 `memory`/`tracing`/`thiserror` で完結)。`cargo add` 違反なし。 -- スコープ外の変更は無し。本ブランチの merge-base は `31d9b9b` で、`433ee0f` 1 コミットのみ。develop 直近の `00755cf "manifestで一部値のzeroの扱いを変更"` は merge-base より後の develop 側コミットであり、3-way merge で develop に残る(本ブランチは触れていない)。 - -## 指摘事項 - -### 対応済み(追加修正) - -- **`lint_workflow` の description hard cap 検証** — loader 側 (`workflow.rs:170-179`) と挙動を揃えるため `Linter::lint_workflow` (`linter/mod.rs:250-`) で `model_invokation: true` の 1024 chars 上限を検証するよう追加。テスト 2 件 (`workflow_lint_flags_long_description_when_model_invokation` / `workflow_lint_allows_long_description_when_not_model_invokation`) を追加。 -- **`resident_workflows_section` の文言** — `Invoke one with /` は LLM 側に invoke 経路がなく、かつ `model_invokation: true && user_invocable: false` の組み合わせ(resident に出るが `/` 拒否)が存在するため二重に誤誘導だった。`When a user request matches one, follow its procedure as authoritative instead of improvising. User-invocable workflows can additionally be triggered by the user typing /; you cannot invoke any of them yourself.` に書き換え(user_invocable に依存しない汎用形)。 - -### Non-blocking / Follow-up - -- **Workflow body の `#` リファレンス** — Markdown 本文に `#` を書いても展開されない(resolver は `requires` フロントマターのみ参照)。`requires` で代用する設計だが、Workflow 著者が混乱しないよう将来の Workflow Linter で「本文中の `#` が `requires` に登録されているか」のチェックを追加する余地あり。 - -### Nits - -- `crates/memory/src/workflow.rs:218-238` の `warn_unknown_workflow_fields` は許可フィールドの一覧をハードコードしており、`WorkflowFrontmatter` 側にフィールドを追加した際の同期忘れリスクがある。serde の `deny_unknown_fields` 相当を別パスで模しているとはいえ、配列定数を `pub const ALLOWED_FIELDS: &[&str]` のように schema 側に置いて両方から参照する方が安全。 -- `WorkflowFrontmatter::updated_at` を `Option` 化して `epoch()` フォールバックする実装(`schema/workflow.rs:35-40`)は MVP 用途として合理的だが、`Frontmatter::updated_at` が「実時刻 or epoch」のどちらを返すかが呼び出し側で見えない。利用箇所(`size::check_body` 経由)に影響しない範囲だが、トレイトのコメントに「workflow は epoch を返し得る」旨を一言入れておくと混乱を避けられる。 -- `WorkflowResolveError::InvalidSlug` は `validate_workflow_invocations` 経由でしか発火しないが、ユーザー入力の段階で TUI 側 `Atom::WorkflowInvoke` が同じ `Slug::parse` を通っているはず(`crates/tui/src/input.rs`)。重複検証は害ではないが冗長。 - -## 判断 - -**Approve** — チケット要件は完全に満たされ、テストも妥当。スコープ外の変更も無し。指摘はすべて follow-up / nit レベル。