Skip to Content
デザイン宣言型エージェント定義 — Claude Code 2.1.168 からの移植

宣言型エージェント定義 — Claude Code 2.1.168 からの移植

Claude Code の宣言型エージェント(markdown + YAML frontmatter)スキーマを qwen-code に移植するための内部設計ドキュメント。issue #4821  に対応し、issue #4721  / PR #4732  のワークフロー移植と連携する。

実装ステータス(垂直スライス)

PR #4842  により、当時エンドツーエンドのランタイムパスを持つフィールドが出荷された。その後、PR #4870  でブロックスカラをサポートするために YAML パーサーが置き換えられた。このフォローアップ PR は両者を基盤としている。YAML stringifier を置き換え(PR #4870 では手書きのままだった — docs/design/yaml-parser-replacement.md を参照)、SubagentConfigmcpServershooks を公開し、サブエージェント実行時にエージェントごとの MCP サーバーとフックが実際に発火するようにランタイムに接続する。

フィールドステータス備考
permissionModeshipped (#4842)既存の qwen approvalMode にパース時にブリッジ
maxTurnsshipped (#4842)既存の runConfig.max_turns ランタイムパスに接続
color allowlistshipped (#4842)既存のフィールドを CC の _Y セット + auto レガシーセンチネル処理に厳格化
mcpServersshipped (follow-up)eemeli/yaml stringify によりネストされた YAML のラウンドトリップが安全に;ランタイムオーバーライドはサブエージェント Config ラッパー経由でセッション + エージェントサーバーをマージし、ツールレジストリを強制再ビルド
hooksshipped (follow-up)サブエージェント生成時に登録される一時的な HookRegistry エントリ。onStop 経由で削除;v1 はグローバルに発火(エージェントスコープフィルタなし)
effortdeferredqwen プロバイダーにはまだモデル層の effort パラメータが存在しない
memorydeferredqwen の自動メモリにはまだ user/project/local スコープの区別がない
isolationdeferredワークフロー PR #4732 がランタイムを所有;エージェントごとのデフォルトはそれがマージされた際に実装される
initialPromptdeferred--agent CLI フラグが必要(qwen にはメインセッションエージェントのインフラがない)
skillsdeferredconfig.skills を消費する SkillManager が必要

以下の完全なリバースエンジニアリングの記録は、延期されたフィールドの設計リファレンスとして保持されている。スキーマ定数、DL7/Ig5 のセマンティクス、エラーメッセージ、およびワークフローとの連携マトリックスは、その作業において依然として不可欠である。


フェーズ 0 — 境界

項目
確認済みの最新アップストリームClaude Code 2.1.168(issue #4821 は ≥ 2.1.167 を参照、我々はそれより 1 バージョン上)
ネイティブバイナリ/private/tmp/cc-2.1.168/package/claude (220 MB)
文字列抽出/private/tmp/cc-2.1.168/claude.strings(約 342k 行)
ワークツリー.claude/worktrees/gifted-hamilton-684741
ブランチmain @ 45efb1d3a から分岐した lazzy/gifted-hamilton-684741
対象外PR #4732 ワークフローコード(別のワークツリー lazzy/lucid-pare-974192)— インターフェース経由でのみ連携
作成ルール著者は LaZzyMan。コミット、PR、issue、コメントに Co-Authored-By や AI ツールのトレーラーは含めない~/.claude/CLAUDE.md に準拠)

フェーズ 1 — リバースエンジニアリングの知見

ここでのすべての主張は、claude.strings に対して独立に grep され、反証可能な検証を生き残った。信頼度レベル: C = Confirmed(直接バイナリ証拠あり)、I = Inferred(複数の確認済み事実から合成)、O = Open(まだ不確実)。

スキーマ — 15 のフィールド、反証と再確認

エージェントの frontmatter シャドウスキーマは Ig5 であり、tengu_frontmatter_shadow_unknown_key / _mismatch テレメトリのために ug5.agent 内で使用される。本番ローダーは DL7parseAgentFromMarkdown)であり、カスタムエラーメッセージを使用して手書きのフィールドごとのバリデーションを実行する。別の JSON 形式スキーマ JL7fL7 / parseAgentFromJson で使用)はより厳密だが、異なるコードパスである(--agents <json>settings.agents で使用)。

#フィールド型 (Ig5 / DL7)必須デフォルト列挙型 / 制約信頼度
1name文字列、非空yesなし — DL7: if(!T||typeof T!=="string")return nullC strings:308120, 309074
2description文字列、非空yesJL7: .min(1, "Description cannot be empty")C strings:308120, 309074, 309076
3model文字列noundefinedinherit(大文字小文字を区別しない)はリテラル "inherit" に正規化;それ以外の場合はトリムされてパススルーC strings:308120, 309075, 309076
4tools文字列|配列 (MDH union)noundefined単一トークン *undefined(「すべて継承」を意味する);AXH/FbK 経由で複製C strings:308120 (MDH/AXH), 309075
5disallowedTools文字列|配列 (MDH)noundefinedtools が設定されている場合は無視される」(describe テキストによる);呼び出し側によって強制C strings:308120, 309075
6effort文字列|整数noundefined列挙型 GN=["low","medium","high","xhigh","max"] または int;エイリアス P37={med:"medium"}C strings:308120, 309075, GN/P37 inline
7permissionMode文字列noundefined列挙型 $E = Gmq = [...kc]kc=["acceptEdits","auto","bypassPermissions","default","dontAsk","plan"](6 つの値)C strings:307649 (kc), 308120, 309075
8mcpServersz.unknown() (Ig5); array(jL7) (JL7)noundefined各アイテム: 文字列 または record(string, MCPServerSpec);DL7 でアイテムごとに safeParseC strings:308120, 309075, 309076
9hooksz.unknown() (Ig5); _u() (JL7)noundefinedTKO_u().safeParse(settings.json のフック形状)経由で実行時に遅延バリデーションC strings:308120, 309073 (TKO), 309076
10maxTurnsunion(number, string, null)noundefined正の整数(W46 でパース — 数値または数値文字列を受け付ける)C strings:308120, 309075 (W46), 309076
11skills文字列|配列 (MDH)no[] (出力される)ml(q.skills) = FbK(H) ?? [] 経由で正規化;* ワイルドカードなし(tools とは異なる)C strings:308120, 309075
12initialPrompt文字列noundefined空白のみの場合 → undefined;エージェントがメインセッション--agent / settings 経由)の場合にのみ自動送信され、サブエージェントの場合は無視C strings:308120, 309075
13memory文字列noundefined列挙型 ["user","project","local"]C strings:308120, 309075, 309076
14background文字列|真偽値 (eiH=EL8)noundefinedtrue / false / "true" / "false" を受け付ける;真と評価されるもののみ true に正規化、それ以外は undefinedC strings:308120, 309075
15isolation文字列noundefined列挙型はのみ ["worktree"]["none","worktree"] ではない — それは strings:313284 のバックグラウンドセッション設定用の別のスキーマ)C strings:308120, 309075, 309076

反証を生き残った微妙な観察: skills は「オプション」であるにもかかわらず、DL7 の出力句は ...I !== void 0 && {skills: I} であり、ml(undefined)[](非 undefined)を返すため、frontmatter がフィールドを省略している場合でも、最終的に出力されるレコードには skills: [] が含まれる。これは下流の等価性チェックに影響を与えるため、qwen-code への移植時のフラグとする。

15 個を超える可能性のある追加フィールド

#フィールドデフォルト列挙型 / 制約信頼度
16color文字列undefined列挙型 _Y = ["red","blue","green","yellow","purple","orange","pink","cyan"]"@internal — display color in the agents UI" と記述;_Y 以外の値はパース時にサイレントに破棄される(DL7 は ...z && typeof z === "string" && _Y.includes(z) && {color: z} を出力)C strings:308120, 309075, _Y inline
これは #4821 のリストを超える、agent-frontmatter の唯一の新しいフィールドです。Ig5 / JL7 で検索されましたが見つからなかったフィールド: version, tags, labels, category, icon, alias / aliases, experimental, deprecated, owner, author, homepage, displayName, shortDescription(これらはすべて、skill スキーマ bg5 または無関係な識別子でのみ見つかりました)。

ローダー — ファイルと関数のマップ

責務関数場所Conf
最上位レジストリアセンブラQL (エクスポート名 getAgentDefinitionsWithOverrides)strings:309076C
ファイルシステムウォーカー(skills/commands/output-styles と共有)Gmh6 経由でメモ化)strings:312887C
.md 単位の検出d_q(= loadMarkdownFiles--files --hidden --follow --no-ignore --glob *.md を指定した ripgrep、3 秒の AbortSignal.timeout__("true") の場合はフォールバック wY3strings:312887C
ファイル単位のパーサー(Markdown)DL7 (= parseAgentFromMarkdown)strings:309074C
ファイル単位のパーサー(JSON)fL7 (= parseAgentFromJson)、JL7 スキーマを使用strings:309073C
プラグインエージェントローダーb0_ → ディレクトリ単位の oR7 → ファイル単位の sR7strings:308780, 308779C
組み込みnaH()[JqH=general-purpose, KL7=statusline-setup, …] に加えて暗黙の YI=fork を生成strings:309073, 308663C
オーバーライドリゾルバーDS() (= getActiveAgentsFromList) — 解決順序を参照strings:309073C
キャッシュの無効化u0_() (= clearAgentDefinitionsCache) — QL.cacheGm.cache をクリアstrings:309073C
FS ウォッチャー(chokidar)s_T() → モジュール初期化時(WB6)に Q4_=s_T()strings:316417C

Gm("agents", _) は 3 つの baseDirs(policySettingsuserSettingsprojectSettings)を読み取り、それぞれレコードにタグを付けます。その後、inode によって重複を排除します(シンボリックリンク/ハードリンクからの同一 inode の重複を削除し、Skipping duplicate file '<path>' from <source> (same inode already loaded from <firstSource>) をログに出力します)。 テレメトリ: managedFilesFounduserFilesFoundprojectFilesFoundprojectDirsSearchedsubdir を含む tengu_dir_search

解決順序 — 確定的な優先順位

関数 DS() は入力を source でフィルタリングし、次に固定順の配列を agentType をキーとする Map に反復処理します。Map.set は上書きするため、最後に処理されたバケットが優先されます:

[built-in, plugin, userSettings, projectSettings, flagSettings, policySettings] ^ 最高優先順位
ソース起点オーバーライド優先度Conf
built-innaH()(バイナリにハードコード)1(最低)C strings:309073
pluginb0_ → プラグイン単位の agentsPath/agentsPaths2C strings:308780
userSettings~/.claude/agents/CLAUDE_CONFIG_DIR または ~/.claude3C strings:312887, 307489
projectSettings<cwd>/.claude/agents/ に加え、iV_() で homedir / git root まで走査4C strings:312887, iV_ inline
flagSettings--agents <json> CLI フラグ(スキーマ qKO = h.record(h.string(), JL7())5C strings:330190, 309076
policySettingsシステム管理ディレクトリ: macOS /Library/Application Support/ClaudeCode/.claude/agents、Linux /etc/claude-code/.claude/agents、Windows C:\Program Files\ClaudeCode\.claude\agents6(最高)C strings:307649 (H2), 312887

競合はサイレントに解決されます。tengu_plugin_name_collision テレメトリイベントが発生するのみで(winner_source: T.at(-1))、ユーザーに「X が組み込みをオーバーライドしました」といった警告は表示されません。(strings:308742 hMH。)

微妙な動作: iV_()cwd から上に向かって最も内側から先に走査しますが、Map.set は最後が優先されるため、projectSettings 内では外側のツリーの .claude/agents/ が内側のツリーより優先されます。これは驚くべき動作です — 未解決の質問としてフラグを立てておきます。

Frontmatter パーサー

質問回答Conf
使用ライブラリなしBun.YAML.parse を呼び出す手作りの分割子 lz(ラッパー l5H 経由)。バイナリに gray-matterjs-yamlfront-matter は含まれていません。C strings:307902 (l5H), 307905 (lz), 110303 (Bun.YAML errors)
正規表現n5H = /^---\s*\n([\s\S]*?)---\s*\n?/C strings:307905
失敗時の処理YAML パース失敗 → タブをスペース 2 つに正規化して再試行。それでも失敗する場合は、warn レベルで Failed to parse YAML frontmatter in <file>: <err> をログに出力し、{frontmatter: {}, content: body} を返します(決してスローしません)。C strings:307905, 151839
本文の抽出閉じ --- の後のプレーンな文字列スライス H.slice(K[0].length)。後で v$H によって正規化されます(おそらく先頭の改行の削除)。C strings:307905
agents / skills / commands / output-styles で共有されているか?はい — 同じ lzIq_(skill ローダー)、f13(非推奨の commands ローダー)、および Gmd_q 経由のエージェントローダーで再利用されています。C strings:312690
スキーマバリデーターZod v4(バンドル済み)。v4 専用のマーカー looseObjecttreeifyErrorprettifyErrortoJSONSchema が存在します。C strings:141270-141395, 141586
バリデーションモードシャドウahH("agent", frontmatter) はテレメトリ専用ug5.agent().strict().safeParse() を実行します。DL7 は結果を無視し、独自のフィールド単位のバリデーションを進めます。寛容な frontmatter オブジェクトが実行時の唯一の信頼できる情報源(source of truth)です。C strings:308120 (ahH/ug5), 309074 (DL7 calls but ignores)
テレメトリイベントtengu_frontmatter_shadow_unknown_keytengu_frontmatter_shadow_mismatch(プロセス内の Set A37 経由で重複排除)C strings:154634, 154636

配線 — エージェントツール + CLI フラグ

レイヤー動作Conf
Task/Agent ツールスキーマ($_3subagent_type: string.optional() を宣言。省略時は general-purpose(または AI() が true を返す場合は fork)にフォールバックC strings:~309220
サブエージェントの検索toolUseContext.options.agentDefinitions.activeAgents に対して activeAgents.find(a => a.agentType === requestedType) を実行C strings:~309220
ファジーフォールバックMWK(s) = s.normalize("NFKC").toLowerCase().replace(/[\p{White_Space}\p{Pd}_]+/gu, "")。曖昧なマッチ → AgentTypeError。クリーンな再マッチ → tengu_subagent_type_normalizedC strings:~309220
権限ゲートlV_(toolPermissionContext, "Task", agentType) — 拒否時 → Agent type '<x>' has been denied by permission rule 'Task(<x>)' from <source>.C strings:~309220
システムプロンプトのソースMarkdown の本文が getSystemPrompt: () => body + ('\n\n' + UVH(agentType, memoryScope) when memory enabled) になります — パース時にクロージャがキャプチャされます。C strings:309074-6 (DL7)
メインスレッドのレンダリングPp({mainThreadAgentDefinition, …}) — エージェントが appendSystemPrompt: true(包括的な claude 組み込み)を持つ場合、本文がデフォルトに追加されます。それ以外の場合はデフォルトを置換します。C strings:311015
--agent <name> CLICommander 経由で宣言。アクションハンドラ if(I) process.env.CLAUDE_CODE_AGENT = I; — 環境変数に格納し、他の場所で appState.agent として読み取られます。pid ファイルにも記録されます。C strings:330190, 142138
--agents <json> CLI別のフラグ。JSON レコード {name: {description, prompt, …}}qKO = h.record(h.string(), JL7()) によってバリデーションされます。source: flagSettings として同じ activeAgents レジストリに参加します。C strings:330190, 309076

ライフサイクル — コールドロード + ホットリロード

項目動作Conf
コールドロード遅延評価 — QLh6(キャッシュラッパー)を介してメモ化されます。初回アクセス時にファイルシステムとプラグインを読み込み、それ以降のアクセスではキャッシュを返します。C strings:309076
ホットリロードの仕組みモジュール初期化時に chokidar watcher s_T() (WB6) が登録されます。.claude/agents(ユーザーおよびプロジェクト)と、skills、commands ディレクトリを監視します。C strings:316417
Watcher フラグpersistent:true, ignoreInitial:true, depth:2, awaitWriteFinish:{stabilityThreshold,pollInterval}, ignored:(p,s) => s?.isFile() ? !p.endsWith(".md") : false, usePolling:kZ4 (macOS true), events add/change/unlinkC strings:316417
デバウンス300 ms (l_T = 300)。ハンドラは RIH(), Vv(), u0_(), … を呼び出します。u0_() はエージェントキャッシュを無効化します。C strings:316417, 309073
アダプティブポーリングアクティブ時は n_T = 2000 ms の間隔。アイドル時(r_T = 60000 ms 間インタラクションなし)は i_T = 30000 ms。切り替え時に chokidar インスタンスを再作成します。C strings:316417
/agents スラッシュコマンドエージェントを管理するための local-jsx UI(Library/create/edit/delete/run)— リスキャンコマンドではありません。C strings:314593
/reload-plugins スラッシュコマンドQL(W8()) を再実行し、エージェントを再カウントします。プラグイン由来のエージェント(chokidar が監視しないもの)をカバーします。C strings:314595, 190948
その他の無効化パスclearSessionCaches/clear で使用)も u0_() を呼び出します。C strings:313246

未解決の質問 (Phase 1)

#質問Conf解決パス
Q1color が #4821 から省略されているのは意図的なもの(@internal であるため)か、それとも見落としか?O意図的なものとして扱う — フィールドを移植するが、internal/UI-only としてマークする
Q2寛容な DL7 の動作(background が文字列を受け入れる、maxTurns が文字列を受け入れる)は、ドキュメント化されたユーザー向け機能か、後方互換性のためのハックか?O同等性のためにミラーリングするが、移植ドキュメントで警告する
Q3isolation 列挙型 ["worktree"] がエージェント専用であるのに対し、background-session 設定スキーマは ["none","worktree"] を受け入れるのはなぜか?Oおそらく「isolation なし」= フィールドの省略。明示的にドキュメント化する
Q4--agents <json> (flagSettings) は意図的に優先度 5(プロジェクトより上、ポリシーより下)に配置されているか?Oqwen-code は v1 でこのフラグをスキップし、決定を先送りできる
Q5iV_ による最内側優先のプッシュと Map.set の last-wins により、projectSettings の競合では最外側ツリーが優先される。これは地雷か、それとも意図的なものか?Oqwen-code はこの地雷を避けるために最内側優先のセマンティクスを採用すべき

Phase 2 — qwen-code の実装計画

現状 — 概要

qwen-code にはすでに大規模なサブエージェントインフラが搭載されています。 SubagentManager (packages/core/src/subagents/subagent-manager.ts) は、 カスタム YAML パーサー (packages/core/src/utils/yaml-parser.tsgray-matter / yaml への依存なし、package.json で確認済み) を使用して、.qwen/agents/ (プロジェクト) および ~/.qwen/agents/ (ユーザー) 内の markdown+YAML frontmatter ファイルに対する CRUD を実装しています。 SubagentConfig (packages/core/src/subagents/types.ts:41-122) にはすでに namedescriptiontoolsdisallowedToolsapprovalModesystemPromptmodelrunConfigcolorbackground が含まれています。SubagentLevel はすでに 5 つのスコープ (session, project, user, extension, builtin) をサポートしており、優先順位は session > project > user > extension > builtin です (subagent-manager.ts:189-220)。Agent ツール (packages/core/src/tools/agent/agent.ts) は subagent_type を宣言し、 subagentManager.changeListener を介してスキーマ列挙型を動的に更新します。 convertClaudeAgentConfig() ブリッジは、ツール名のマッピングと permissionMode → approvalMode のマッピングを備えて、 packages/core/src/extension/claude-converter.ts:162-220 にすでに存在します。ギャップは以下の通りです: (a) スキーマに #4821 の 8 つのフィールド (effort、ファーストクラスとしての permissionModemcpServershooks、トップレベルとしての maxTurnsskillsinitialPromptmemoryisolation) が欠けていること。(b) --agent <name> CLI フラグがないこと。(c) chokidar スタイルのホットリロードがないこと(extension スタイルの無効化は存在するが、ファイルシステムエージェント用ではない)。(d) maxTurns が現在 runConfig.max_turns の下にネストされていること — #2409 に従ってトップレベルに昇格させる必要がある。

アーキテクチャの決定事項

D1. frontmatter に既存の yaml-parser を再利用する

決定: packages/core/src/utils/yaml-parser.ts を再利用する(すでに SubagentManager.parseSubagentContent と skill ローダーで使用されている)。 理由: Claude Code の lz は、skills + commands + agents に使用されるのと同じ共有パーサーであり、 qwen-code はすでにそのパターンをミラーリングしている。gray-matterjs-yaml を追加するのは 不要な変更である。既存のパーサーは --- … --- の分割を処理し、不正な入力に対しては沈黙する (lzwarn-and-return-empty の姿勢と一致する)。

D2. 解決 / 優先順位

決定: `session > project (.qwen/agents/) > user (~/.qwen/agents/)

extension > builtinを使用します。つまり、**既存の qwen-code の SubagentLevel の順序を維持し、v1 では Claude Code のflagSettings/policySettings バケットを ミラーリングしません**。 **理由:** Claude Code の policySettings(管理ディレクトリ)はエンタープライズデプロイのストーリーであり、 qwen-code にはない。フラグで注入されたエージェント(—agents )はパワーユーザー向け機能であり、 P4 で実装できる。既存の 5 段階の qwen-code の優先順位は、#4821 が重視するケース (プロジェクトがユーザーをオーバーライドし、ユーザーがビルトインをオーバーライドする)をすでにカバーしている。 extension` レベルは、ユーザーと > ビルトインの間にきれいに収まる。

D3. バリデーション — 既存の SubagentValidator を維持する

決定: SubagentValidator (packages/core/src/subagents/) を拡張して、8 つの新しいフィールドを バリデーションします。skillManager のパイプラインがすでに使用していない限り、 zod を導入しません。既存のバリデーターがハンドロールされている場合は、 ハンドロールのままにします。 理由: Claude Code の Ig5 はシャドウのみであり、ランタイムバリデーションはハンドロールされた DL7 である。そのパターンに合わせることで、別の依存関係を持ち込まずに、エラーメッセージを 読みやすく保つことができる(例: Agent file <path> has invalid permissionMode '<x>'. Valid options: …)。 skillManager がすでに zod を使用している場合は、一貫性のためにその選択に従う — P1 準備で skill コードを読んで TBD(To Be Determined)。

D4. ホットリロード — 延期。コールドロード + 明示的なリロードに依存する

決定: v1 には chokidar watcher を搭載しません。キャッシュ無効化フックはすでに存在します (subagentManager には changeListener と明示的な CRUD 駆動の更新がある)。 プロジェクトレベルのリロードはセッション開始時に発生し、セッション内の /agents UI を介した編集は 無効化されます。ユーザーの需要があれば、/reload-agents(または /reload-plugins に乗っかる) スラッシュコマンドを P4 で実装できます。 理由: FS watcher によるホットリロードはコストが高い(chokidar はアダプティブスケジューリングを伴う ポーリングループを追加する — Claude Code の実装だけで約 150 行の管理コードがある)。 起動時のコールドロードは v1 には十分であり、現在の SubagentManager の配線方法と一致する。 P4 への道を開く。

D5. --agent <name> CLI フラグの組み込み — v1 のスコープ内

決定: packages/cli/src/config/config.ts の CliArgs に --agent <name> を追加します。 動作: 解決されたレジストリに対してルックアップし、エージェントをメインスレッドのエージェントとして設定し、 名前が解決されない場合は明確なエラーをスローします。Claude Code のセマンティクスに一致させます (エージェントが appendSystemPrompt: true を持たない限り、デフォルトのシステムプロンプトを置き換える)。 CLAUDE_CODE_AGENT 環境変数による間接参照は使用しません — qwen-code の Config オブジェクトが 直接保持できます。 理由: これは #4821 のユーザー向けハンドルです — これがないと、宣言型エージェントは Agent ツールの subagent_type パラメータ経由でのみアクセス可能となり、「デフォルトのエージェントを設定する」という ユースケースには間接的すぎます。--agents <json>(複数形)は P4 に先送りできます。

D6. Workflow.agentType の調整 — インターフェース契約

決定: PR #4732 の createProductionDispatch がマージされたときに呼び出せる、 安定したリゾルバーインターフェースを公開します。具体的には:

契約オーナー
Frontmatter の name は workflow の agentType 文字列である(キーの等価性、大文字小文字を区別)this PR
Workflow のハードコードされた disallowedTools のフロア([SEND_MESSAGE, EXIT_PLAN_MODE]、アップストリームの Tg8 からミラーリング。PR #4732 で ToolNames.SEND_MESSAGE, ToolNames.EXIT_PLAN_MODE として確認済み)は、エージェントレベルの disallowedTools と UNION される — エージェント定義が tools を設定している場合でも、フロアは常に適用されるworkflow PR consumes
呼び出しごとの opts.isolation は、エージェントごとの isolation: 'worktree' デフォルトをオーバーライドするworkflow PR consumes
エージェント定義からの model, effort, permissionMode, maxTurns は、設定されている場合に workflow のデフォルトをオーバーライドするworkflow PR consumes
エージェントの本文がサブエージェントの systemPrompt になる。workflow の WORKFLOW_SUBAGENT_SYSTEM_PROMPT は、agentType が解決されない場合のフォールバックとなるworkflow PR consumes
agentType が未設定または解決に失敗した場合、workflow はビルトインの workflow サブエージェントにフォールバックする(グレースフル、スローなし)workflow PR consumes
#4721 / #4821 の矛盾の解決toolsdisallowedTools の優先順位): この移植では、エージェントレジストリが disallowedToolstools とは常に別々に保持するように記述します。#4821 のテーブルにある「tools が設定されている場合は無視される」というルールは、パース時ではなくエージェントツールの呼び出し側(つまり、サブエージェントの ToolConfig を構築する際)によって適用されます。これにより、ワークフローはエージェントが tools を設定しているかどうかに関わらず、常にそのフロアと disallowedTools の和集合を取ることができます。エージェントレジストリは単なるデータキャリアであり、優先順位のルールはディスパッチサイトに存在します。これにより、#4821 の「無視される」ルールと #4721 の「和集合」ルールの間の表面的な矛盾が解決されます。

ツール名の正規化: ToolNames.SEND_MESSAGEToolNames.EXIT_PLAN_MODE を使用します(PR #4732 の差分で検証済み)。これらはマージ後に packages/core/src/agents/runtime/workflow-orchestrator.ts から名前付き定数としてエクスポートされます。宣言的エージェントの移植自体はこれらをインポートする必要はありません。これらはワークフローのフロアであり、ワークフローのディスパッチサイトで適用されるためです。

モジュール構成

パス新規 / 変更目的
packages/core/src/subagents/types.ts変更SubagentConfig に 8 つの新規フィールドを追加: effort, permissionMode(すでに approvalMode 経由でマッピングされる — 両方保持するか?以下の D7 を参照), mcpServers, hooks, maxTurns(トップレベルに昇格、runConfig.max_turns は非推奨化), skills, initialPrompt, memory, isolation
packages/core/src/subagents/subagent-manager.ts変更parseSubagentContent / serializeSubagent を拡張して新規フィールドをラウンドトリップさせる。SubagentValidator の呼び出しも拡張
packages/core/src/subagents/subagent-validator.ts (想定パス)変更DL7 のエラーメッセージに一致するフィールドごとのバリデーションを追加: Agent file <path> has invalid permissionMode '<x>'. Valid options: … など
packages/core/src/subagents/agent-frontmatter-schema.ts新規列挙定数の単一の真実の情報源: EFFORT_VALUES, PERMISSION_MODE_VALUES, MEMORY_VALUES, ISOLATION_VALUES, COLOR_VALUES。Claude Code 2.1.168 と完全に一致させる
packages/core/src/subagents/builtin-agents.ts変更新規フィールドのデフォルトは undefined。動作の変更なし
packages/core/src/tools/agent/agent.ts変更サブエージェントオプション(model, maxTurns, permissionMode, effort)の構築時に、解決された SubagentConfig から新規フィールドを読み取る。#4721 向けの isolation の呼び出しごとのオーバーライドセマンティクスを配線
packages/cli/src/config/config.ts変更--agent <name> フラグを追加。起動時に SubagentManager に対して解決。名前が解決できない場合はエラー
packages/cli/src/config/config.test.ts変更--agent フラグの解決とエラーパスのテスト
packages/core/src/extension/claude-converter.ts変更Claude の .md ファイルをインポートする際の新規フィールドのマッピングを追加(mcpServers, hooks, maxTurns のトップレベル化, memory, isolation など)
packages/core/src/subagents/agent-frontmatter-schema.test.ts新規列挙リストのスナップショットテスト。パース/シリアライズのラウンドトリップテスト
packages/core/src/subagents/subagent-manager.test.ts変更新規フィールドのバリデーション、優先順位、エラーメッセージのテスト
packages/core/src/tools/agent/agent.test.ts変更サブエージェントランタイムへの新規フィールドの配線テスト
docs/cli/agents.md(存在する場合)または docs/declarative-agents.md新規ユーザー向けリファレンス: 16 フィールドのスキーマと例

D7. permissionMode と approvalMode — 置き換えではなくブリッジ

決定: フロントマターで permissionMode(Claude 互換)と既存の approvalMode(qwen 互換)の両方を受け入れます。パース時に permissionMode が設定されている場合、claude-converter.ts:195-208 の既存テーブル(default → default, plan → plan, acceptEdits → auto-edit, dontAsk → default, bypassPermissions → yolo)を使用して approvalMode にマッピングします。両方が存在する場合は approvalMode が優先され(qwen-code により特化しているため)、両方が設定されたことを示す tengu_frontmatter_shadow_* 形式のテレメトリエベントを発行します。 理由: approvalMode を使用する既存の .qwen/agents/*.md との後方互換性を維持しつつ、Claude Code の permissionMode をそのまま受け入れることで、ユーザーは Claude Code のエージェントファイルをそのまま変更せずにドロップインできます。

スキーママッピングテーブル

Claude Code 2.1.168 フィールドqwen-code フィールド適応備考
namenameなし同一、必須
descriptiondescriptionなし同一、必須
modelmodelinherit, fast, haiku, sonnet, opus, または authType:model-id を受け入れるqwen-code はすでにより広い語彙をサポート。inherit は新規
toolstools文字列または配列を受け入れる。* → undefined(すべて継承)すでに配列としてサポート済み。文字列と * の処理を追加
disallowedToolsdisallowedTools文字列または配列を受け入れる。常に tools とは別々に保持される優先順位のルール(#4821 の「tools が設定されている場合は無視される」)は、パーサーではなく呼び出し側によって適用される
efforteffort (新規)列挙型 low/medium/high/xhigh/max と整数。エイリアス med → mediumランタイムへの影響は qwen 固有(存在する場合は既存の thinking-effort ノブにマッピング、なければ保存して無視)
permissionModepermissionMode (新規) + approvalMode へのブリッジ列挙型 acceptEdits/auto/bypassPermissions/default/dontAsk/plan。D7 に従ったマッピングテーブルClaude 形式をそのまま受け入れる
mcpServersmcpServers (新規)文字列または {name: spec} の配列。項目ごとにバリデーションし、不正なエントリは警告付きで破棄P4 で MCP ランタイムへ配線
hookshooks (新規)settings.json の hooks 形状に一致するオブジェクトP4 で hook ランタイムへ配線
maxTurnsmaxTurns (新規トップレベル)正の整数。整合性のために数値文字列も受け入れるrunConfig.max_turns から昇格。ネストされた形式は非推奨のエイリアスとして保持
skillsskills (新規)スキル名の配列。カンマ区切りの文字列も受け入れランタイム: エージェント開始時に skillManager 経由でプリロード
initialPromptinitialPrompt (新規)文字列。空白のみの場合は undefined。エージェントがメインセッションの場合にのみ発火--agent フラグパス経由で配線
memorymemory (新規)列挙型 user/project/local.qwen/agent-memory/<name>/ などからロードP4 でランタイム実装
backgroundbackground真偽値または文字列 "true"/"false" を受け入れる。真と評価されるもののみ trueすでにサポート済み。パースルールを緩和
isolationisolation (新規)列挙型 のみ ["worktree"]ランタイムはワークフロー PR (#4732 P3+) が所有。レジストリは単にフィールドを保持
color (未文書化 #16)color列挙型 _Y = ["red","blue","green","yellow","purple","orange","pink","cyan"]。リスト外の値はサイレントに破棄すでに qwen の SubagentConfig に存在。Claude Code の許可リストに一致するようにバリデーションを厳格化

TDD テスト計画

チャンクテストファイル検証内容
スキーマ列挙定数agent-frontmatter-schema.test.ts (新規)EFFORT_VALUESPERMISSION_MODE_VALUESMEMORY_VALUESISOLATION_VALUESCOLOR_VALUES が Claude Code 2.1.168 とバイト単位で一致すること(スナップショット)
パーサー — ハッピーパスsubagent-manager.test.ts全16フィールドを含む .qwen/agents/test.md を往復パースし、出力されるレコードが期待される形状を持つこと
パーサー — 必須フィールドsubagent-manager.test.tsname がない場合は null を返し、警告ログを出力する。description がない場合も同様に null を返し、警告ログを出力する
パーサー — 列挙型の検証subagent-manager.test.ts不正な permissionMode / memory / isolation / effort / color はそれぞれ固有の警告(DL7 の文言に準拠)を出力し、そのフィールドは破棄される
パーサー — 寛容なフィールド型subagent-manager.test.tsbackground: "true"truemaxTurns: "5"5effort: "med""medium"tools: "Read,Edit"["Read","Edit"]tools: "*" → undefined
パーサー — 色の許可リストsubagent-manager.test.tscolor: "magenta" は暗黙に破棄される(エラーなし)。color: "blue" は保持される
skills フィールドの特殊な挙動subagent-manager.test.tsskills を省略すると skills: [] になる(Claude Code DL7 の出力動作と一致)
解決の優先順位subagent-manager.test.tsproject と user で同じ name の場合 → project が優先。user と builtin の場合 → user が優先。extension と builtin の場合 → extension が優先
inode の重複排除subagent-manager.test.ts同じ inode(シンボリックリンク)への2つのパス → レコードは1つのみ、ログが出力される
permissionMode ブリッジsubagent-manager.test.tspermissionMode: bypassPermissions → 解決されて approvalMode: yolo になる。両方設定されている場合 → approvalMode が優先され、テレメトリが送信される
--agent CLI フラグpackages/cli/src/config/config.test.tsフラグでメインスレッドのエージェントを設定する。未解決の名前の場合、Agent type '<x>' not found. Available agents: … でスローする
エージェントツールのファジーフォールバックagent.test.tssubagent_type: "Test_Engineer" は NFKC 小文字正規化を通じて登録済みの test-engineer に解決される
エージェントツールの not-found エラーagent.test.ts未解決の subagent_type → エラーメッセージが Agent type '<x>' not found. Available agents: <list> に一致する
ワークフローの契約agent-frontmatter-schema.test.tsエクスポートされた getAgentByName(name) インターフェースが、isolationdisallowedToolsmodeleffortpermissionModemaxTurns を含む完全な SubagentConfig を返すこと(ワークフロー PR #4732 で利用可能)

フェーズ分けされた PR 計画

フェーズタイトルスコープブロック
P1feat(core): declarative agent schema fields (effort, permissionMode, maxTurns top-level, memory, isolation, color allowlist)SubagentConfig にフィールドを追加。パーサー、バリデーター、シリアライザーを拡張。runConfig.max_turns を非推奨化。列挙定数モジュールを追加。テストなし
P2feat(core): wire new agent fields into Agent tool runtimemodeleffortmaxTurnspermissionMode/approvalMode ブリッジを AgentTool.execute()AgentHeadless.create() の呼び出しサイトに組み込む。テストP1
P3feat(cli): --agent flag for main-thread agent selectionCliArgs--agent <name> を追加。起動時に解決。エラーパス。テストP1
P4(オプション、スコープクリープ)feat(core): mcpServers + hooks + skills + initialPrompt + memory runtime「v1 ではメタデータのみの」4つのフィールドを実際のランタイム動作に組み込むP1、および skill/MCP/hook サブシステム

各 PR のターゲットは差分 ≤ 800 LOC(テストを除く)。P1 が最大で、バリデーターとテストで約 600 LOC。


フェーズ 3 — ワークフロー移植との連携マトリクス (#4721 / PR #4732)

宣言型エージェントの機能ワークフローとの連携オーナーブロック要因
レジストリキーとしての name フィールドワークフローの opts.agentType 検索文字列(#4721  で明示)本 PR がレジストリの契約を定義。ワークフロー PR が消費なし — まずレジストリの形状を安定化できる
エージェントの disallowedTools フィールドワークフローがハードコードされた下限 [SEND_MESSAGE, EXIT_PLAN_MODE] と UNION する(#4721  §2 に準拠 — PR #4732 の差分で確認済み: ToolNames.SEND_MESSAGEToolNames.EXIT_PLAN_MODE本 PR がフィールドを持つ。ワークフロー PR がディスパッチ時に UNION するワークフロー PR #4732 P3 のマージ
エージェントの tools フィールドワークフローがサブエージェントの ToolConfig.tools にそのまま渡す本 PR がフィールドを持つ。ワークフロー PR が組み込むワークフロー PR #4732 P3
エージェントの model フィールドワークフローの opts.model が呼び出しごとにオーバーライドする。エージェントの model はデフォルト本 PR がフィールドを持つ。ワークフロー PR が優先順位を解決するワークフロー PR #4732 P3
エージェントの effort フィールドワークフローの呼び出しサイトでのオーバーライドが優先される。エージェントのデフォルトはフォールバック本 PR がフィールドを持つ。ワークフロー PR が解決するワークフロー PR #4732 P3
エージェントの permissionMode フィールドディスパッチ時にサブエージェントの approvalMode にマッピングされる。ワークフローの呼び出しサイトでのオーバーライドが優先される本 PR が D7 ブリッジ経由でフィールドを持つ。ワークフロー PR が組み込むワークフロー PR #4732 P3
エージェントの maxTurns フィールドエージェントが設定した場合、ワークフローのハードコードされた WORKFLOW_SUBAGENT_MAX_TURNS = 50 を置き換える本 PR がフィールドを持つ。ワークフロー PR が優先順位を解決するワークフロー PR #4732 P3
エージェントの isolation: 'worktree' フィールドデフォルト。呼び出しごとの opts.isolation がオーバーライドする(#4721  §3)本 PR がフィールドを持つ。ワークフロー PR がランタイムを所有するワークフロー PR #4732 P3+(現在は P1 でスローされる)
エージェントの initialPrompt フィールドワークフローはこれを使用しない(エージェントが --agent 経由でメインセッションの場合にのみ発火する)本 PR + CLIなし(独立)
memorymcpServershooksskillsワークフローはサブエージェントのランタイムに渡す以外に特別な処理を持たない本 PR がフィールドを持つ。ランタイムの組み込みは P4 / 将来将来の PR
EXCLUDED_TOOLS_FOR_SUBAGENTS の更新ワークフロー PR #4732 がセットに WORKFLOW を追加する(issue/PR コンテキストの発見に基づく — ただし、mainagent-core.ts にはまだ含まれておらず、worktree のみであるという反論もある)ワークフロー PR が所有。本 PR は変更なしなし
ワークフローの下限に対するツール名の正規形式(ToolNames.SEND_MESSAGE本 PR は下限の定数をインポートしない。作成された通りの disallowedTools 文字列を持つのみ。正規化はワークフロー PR が所有する。ワークフロー PRワークフロー PR #4732
リリース順序本 PR(P1+P2+P3)はワークフローとは独立してリリースされる。ワークフロー PR #4732 P3 は、本 PR の getAgentByName() のようなリゾルバーがインポート可能であることに依存する。ワークフローの P3 まで並行ワークフロー P3 が本 PR のエクスポートから読み取る
循環ブロックなし: このPRとworkflow PRは、P1/P2フェーズを通じて並行してマージできます。これらは、このPRのレジストリリゾルバを必要とするworkflow-P3で同期します。このPRが先にマージされた場合、workflow-P3はそこから読み取ります。workflow PRが先にマージされた場合、既存の subagent_type ルックアップ(ミス時にworkflowのデフォルトを返す)で出荷され、このPRがマージされた後にリッチなリゾルバに切り替わります。

フェーズ4 — リスクと未解決の質問

リスク

#リスク緩和策
R1Claude Codeのマイナーリリース間(2.1.168 → 2.1.x)でのスキーマのドリフトenum定数モジュールをドキュメントコメントで「2.1.168に対して検証済み」としてピン留めする。feature-reverse skillの一部として、新しいリリースに対してstrings-grepを再実行する
R2runConfig.max_turns からトップレベルの maxTurns への変更は、既存の .qwen/agents/*.md ファイルに対する破壊的なスキーマ変更である1サイクルの非推奨期間を設け、ネストされた形式を非推奨のエイリアスとして保持する。解析時に警告を発し、CHANGELOGに文書化する
R3permissionModeapprovalMode のラウンドトリップで情報が失われる(Claudeは6モード、qwenは4モード程度)D7に従って両方向を明示的にマッピングする。二重設定時にテレメトリを発行する。保存時にサイレントに書き換えてはならない
R4新しいフィールド(hooks, mcpServers, skills, memory)はレジストリに保持されるがv1にはランタイムがない → ユーザーがこれらを設定してもサイレントに効果がなくなる可能性があるv1のスコープを明確に文書化する。「保持されるがまだランタイムがない」フィールドが空でない場合、エージェントごとに1回限りのinfoログを発行する
R5Adversarial-verifyにより、EXCLUDED_TOOLS_FOR_SUBAGENTSmain 上で WORKFLOW を含んでいないことが指摘された — これはworkflowのポートがまだマージされていないか、recursive-fanoutガードが欠けていることを意味する可能性があるworkflow PRの作成者(LaZzyMan = 自身)と確認し、ガードがこのポートではなくPR #4732でマージされることを確認する
R6outer-tree-beats-inner-treeのprojectSettingsの動作(Q5)は、そのままミラーリングすると地雷になるqwen-codeは明示的にinnermost-winsを選択する。R5のフィクスチャを通じてテスト済み
R7フィールド color はバイナリのdescribeテキストで @internal として文書化されている — Anthropicが明示的にサポートしていないものをポートしている可能性があるポートするが、qwen-codeのドキュメントでも @internal としてマークする。UI専用として扱い、ユーザー向けのリファレンスドキュメントには掲載しない

未解決の質問 — 提案される解決策

#質問解決策
Q1#4821から color が意図的に省略されているか?意図的なものとして扱う。フィールドをポートする。「利用可能、内部用」としてを除き、ユーザー向けドキュメントには記載しない。
Q2寛容なDL7の動作: 文書化するか、ハックするか?そのままミラーリングする。文書化されていなくても、同等性のために background: "true", maxTurns: "5", effort: "med" を受け入れる。テストを追加する。
Q3agentスキーマとbackground-sessionスキーマでisolationのenumが異なるのはなぜか?コードコメントに相違を文書化する。「isolationなし」= enum値ではなく、フィールドが省略されていること。
Q4--agents <json>(複数形、flagSettings)はv1に含めるべきか?P4に延期する。パワーユーザー向けのCLIインターフェース。v1では #4821が対象としている --agent <name>(単数形)のみを出荷する。
Q5ネストされた .qwen/agents/ のinner-treeとouter-treeの優先順位は?Innermost-wins(最も内側が優先)。Claude Codeの意図しないouter-winsの動作を上書きする。P1でテストフィクスチャを作成。
Q6toolsdisallowedTools の優先順位: #4821は「toolsが設定されている場合は無視される」としている。#4721は「workflow floorとの和集合」としているレジストリは単なるデータ。パーサーは両方のフィールドを独立して保持する。優先順位ルールはディスパッチサイト(Agent tool / workflow)に存在する。矛盾を解決する。
Q7workflow disallowedTools floorのツール名の正規形 — PR #4732に対して ToolNames.SEND_MESSAGE, ToolNames.EXIT_PLAN_MODE として検証済みこのPRの関知するところではない — workflow PRが担当する。調整マトリックスにのみ文書化する。
Q8#2409のクローズ解決は何か影響するか?#2409の「model + maxTurnsをトップレベルに昇格させる」というガイダンスを継承する。すでにこの計画に組み込まれている。
Q9qwen-codeの既存の SubagentLevel 優先順位において、extension レベルのエージェントは builtin より上(現在)に留めるべきか、それとも下(Claude Codeに同等のものはない)にすべきか?extension > builtin を維持する。拡張機能はユーザーがインストールするもの。ビルトインはベンダーのデフォルト。ユーザーがインストールしたものが優先される。
Q10issue #4821, #4721, #4732は、このドキュメントが提案するコントラクトに対して完全に仕様化されているか?#4821に調整コメントを投稿する。このドキュメントへのリンクを貼り、フィールドごとの決定を要約し、メンテナーに以下の承認を求める: (a) Claude Code 2.1.168の16フィールドとのスキーマ同等性、(b) D7 permissionMode/approvalMode ブリッジ、(c) D2 優先順位、(d) tools/disallowedTools の矛盾に対するレジストリを単なるデータとする解決策。

調整アクションアイテム

#アクション場所
A1フィールドごとの要約 + 5つの決定を #4821 に投稿し、メンテナーの承認を得る#4821へのコメント
A2フェーズ3マトリックスに言及しつつ、#4721からこのドキュメントへ相互リンクを張る#4721へのコメント
A3このポートのP1がマージされたら、よりリッチなリゾルバに切り替えるよう #4732 に連絡するPR #4732へのコメント(準備ができ次第)
A4スキーマドリフト検出のため、次のClaude Codeマイナーリリースに対してstrings-grepを再実行するfeature-reverse skillのcronジョブ(それまでは手動)
Last updated on