8.9 KiB
8.9 KiB
Review: Workflow 実装
前提・要件の確認
-
Workflow loader / 検証:
<workspace>/.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。
-
/<slug>呼び出し経路: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は<workspace>/.insomnia/knowledge/<slug>.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が IPCListCompletionsに流れる(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)。MemoryToolKindenum に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。
- 汎用 Write/Edit に対する
-
単体テスト:
- 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。
- frontmatter 検証の正常系:
完了条件はすべて満たされている。
アーキテクチャ・スコープ
- 低レベル基盤の
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で、433ee0f1 コミットのみ。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 /<slug>は LLM 側に invoke 経路がなく、かつmodel_invokation: true && user_invocable: falseの組み合わせ(resident に出るが/<slug>拒否)が存在するため二重に誤誘導だった。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 /<slug>; you cannot invoke any of them yourself.に書き換え(user_invocable に依存しない汎用形)。
Non-blocking / Follow-up
- Workflow body の
#<slug>リファレンス — Markdown 本文に#<slug>を書いても展開されない(resolver はrequiresフロントマターのみ参照)。requiresで代用する設計だが、Workflow 著者が混乱しないよう将来の Workflow Linter で「本文中の#<slug>が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 レベル。