跳到內容

任務代理探索與選擇

本文件描述任務子系統如何探索代理定義、合併多個來源,以及在執行時解析請求的代理。

內容涵蓋目前已實作的執行時期行為,包括優先順序、無效定義處理,以及可能使代理實際上不可用的生成/深度限制。


任務代理正規化為 AgentDefinitionsrc/task/types.ts):

  • namedescriptionsystemPrompt(載入有效代理時為必要欄位)
  • 選用的 toolsspawnsmodelthinkingLeveloutput
  • source"bundled" | "user" | "project"
  • 選用的 filePath

解析來自透過 parseAgentFields()src/discovery/helpers.ts)處理的 frontmatter:

  • 缺少 namedescription => 無效(null),呼叫端視為解析失敗
  • tools 接受 CSV 或陣列;若有提供,會自動加入 submit_result
  • spawns 接受 *、CSV 或陣列
  • 向後相容行為:若 spawns 缺失但 tools 包含 taskspawns 會變為 *
  • output 作為不透明的 schema 資料直接傳遞

內建代理在建置時嵌入(src/task/agents.ts),使用文字匯入。

EMBEDDED_AGENT_DEFS 定義:

  • exploreplandesignerreviewer 來自提示詞檔案
  • taskquick_task 來自共用的 task.md 本體加上注入的 frontmatter

載入路徑:

  1. loadBundledAgents() 使用 parseAgent(..., "bundled", "fatal") 解析嵌入的 markdown
  2. 結果快取在記憶體中(bundledAgentsCache
  3. clearBundledAgentsCache() 僅供測試用的快取重設

因為內建解析使用 level: "fatal",格式錯誤的內建 frontmatter 會拋出例外,可能導致整個探索程序失敗。

discoverAgents(cwd, home)src/task/discovery.ts)在附加內建定義之前,會合併來自多個位置的代理。

  1. 來自 getConfigDirs("agents", { project: false }) 的使用者設定代理目錄
  2. 來自 findAllNearestProjectConfigDirs("agents", cwd) 的最近專案代理目錄
  3. Claude 外掛根目錄(listClaudePluginRoots(home))及其 agents/ 子目錄
  4. 內建代理(loadBundledAgents()

來源家族順序來自 getConfigDirs("", { project: false }),其衍生自 src/config.ts 中的 priorityList

  1. .xcsh
  2. .claude
  3. .codex
  4. .gemini

對於每個來源家族,探索順序為:

  1. 該來源的最近專案目錄(若找到的話)
  2. 該來源的使用者目錄

在所有來源家族目錄之後,附加外掛 agents/ 目錄(專案範圍的外掛優先,然後是使用者範圍的)。

內建代理最後附加。

重要注意事項:過時的註解與目前程式碼

Section titled “重要注意事項:過時的註解與目前程式碼”

discovery.ts 的標頭註解仍然提到 .pi,且未提到 .codex/.gemini。實際執行時期順序由 src/config.ts 驅動,目前使用 .xcsh.claude.codex.gemini

探索使用依精確 agent.name 的先到先贏去重:

  • 一個 Set<string> 追蹤已見過的名稱。
  • 載入的代理按目錄順序展平,僅在名稱未見過時保留。
  • 內建代理對照同一集合過濾,僅在名稱仍未見過時加入。

影響:

  • 對於相同的來源家族,專案覆寫使用者。
  • 較高優先順序的來源家族覆寫較低的(.xcsh.claude 之前,依此類推)。
  • 非內建代理覆寫同名的內建代理。
  • 名稱比對區分大小寫(Tasktask 視為不同)。
  • 在同一目錄內,markdown 檔案在去重前按檔名字典序讀取。

按目錄(loadAgentsFromDir):

  • 無法讀取/缺失的目錄:視為空(readdir(...).catch(() => [])
  • 檔案讀取或解析失敗:記錄警告,跳過檔案
  • 解析路徑使用 parseAgent(..., level: "warn")

Frontmatter 失敗行為來自 parseFrontmatter

  • warn 層級的解析錯誤會記錄警告
  • 解析器退回至簡單的 key: value 逐行解析器
  • 若必要欄位仍然缺失,parseAgentFields 失敗,然後拋出 AgentParsingError 並由呼叫端捕獲(跳過檔案)

淨效果:一個有問題的自訂代理檔案不會中止其他檔案的探索。

查詢是精確名稱的線性搜尋:

  • getAgent(agents, name) => agents.find(a => a.name === name)

在任務執行中(TaskTool.execute):

  1. 在呼叫時重新探索代理(discoverAgents(this.session.cwd)
  2. 請求的 params.agent 透過 getAgent 解析
  3. 找不到代理時回傳立即的工具回應:
    • Unknown agent "...". Available: ...
    • 不執行子程序

TaskTool.create() 在初始化時從探索結果建置工具描述(buildDescription)。

execute() 會再次重新探索代理。因此若代理檔案在工作階段中途變更,執行時期的集合可能與先前工具描述中列出的不同。

結構化輸出防護機制與 schema 優先順序

Section titled “結構化輸出防護機制與 schema 優先順序”

TaskTool.execute 中的執行時期輸出 schema 優先順序:

  1. 代理 frontmatter 的 output
  2. 任務呼叫的 params.schema
  3. 父工作階段的 outputSchema

effectiveOutputSchema = effectiveAgent.output ?? outputSchema ?? this.session.outputSchema

src/prompts/tools/task.md 中的提示詞防護文字警告結構化輸出代理(explorereviewer)的不匹配行為:散文中的輸出格式指示可能與內建 schema 衝突,產生 null 輸出。

這是指導性質的,不是 discoverAgents 中的硬性執行時期驗證邏輯。

src/task/commands.ts 是工作流程命令(非代理定義)的平行基礎設施,但它遵循相同的整體模式:

  • 首先從能力提供者探索
  • 以先到先贏方式按名稱去重
  • 若仍未見過則附加內建命令
  • 透過 getCommand 進行精確名稱查詢

src/task/index.ts 中,命令輔助函式與代理探索輔助函式一起重新匯出。代理探索本身在執行時期不依賴命令探索。

代理可能是可探索的,但由於執行防護機制仍然無法執行。

TaskTool.execute 檢查 session.getSessionSpawns()

  • "*" => 允許任何
  • "" => 拒絕全部
  • CSV 列表 => 僅允許列出的名稱

若被拒絕:立即回應 Cannot spawn '...'. Allowed: ...

PI_BLOCKED_AGENT 在工具建構時讀取。若請求匹配,執行會被拒絕並回傳遞迴預防訊息。

遞迴深度閘控(子工作階段中的任務工具可用性)

Section titled “遞迴深度閘控(子工作階段中的任務工具可用性)”

runSubprocesssrc/task/executor.ts)中:

  • 深度從 taskDepth 計算
  • task.maxRecursionDepth 控制截止點
  • 達到最大深度時:
    • task 工具從子工具列表中移除
    • 子代的 spawns 環境變數設為空

因此更深層級無法生成進一步的任務,即使代理定義包含 spawns

計畫模式注意事項(目前實作)

Section titled “計畫模式注意事項(目前實作)”

TaskTool.execute 為計畫模式計算一個 effectiveAgent(前置計畫模式提示詞、強制唯讀工具子集、清除 spawns),但 runSubprocess 使用的是 agent 而非 effectiveAgent

目前的效果:

  • 模型覆寫 / 思考層級 / 輸出 schema 衍生自 effectiveAgent
  • 來自 effectiveAgent 的系統提示詞和工具/生成限制在此呼叫路徑中未被傳遞

這是閱讀計畫模式行為預期時值得了解的實作注意事項。