DeepSeek Harness 插件:dsh-plugin-subagent-bridge

前言

DeepSeek Harness(DSH)默認不支持把任務派給另一個智能體並等結果返回。如果想讓 Codex 把 DSH 當 subagent 調用,或者反過來,需要自己寫一個橋接層。dsh-plugin-subagent-bridge 插供了一個本機迴環 MCP 端點,讓 Codex 能通過標準 MCP 調用 DSH 的會話,DSH 也能調用 Codex。插件運行在 DSH 宿主進程內,直接使用公開的 sessionController 服務,不需要讀取憑證文件或僞造 cookie。

工具

插件在 127.0.0.1:47821 監聽,提供以下工具:

  • dsh_task:同步派活。開會話、投遞、阻塞到這一輪結束,只返回最終回答(最後一條 assistant 消息)。
  • dsh_spawn:異步派活。立刻返回 task_name + sessionId,可同時開多個 worker 並行跑。
  • dsh_followup:給已有 worker 再派一輪,複用它的上下文。
  • dsh_close:關閉 worker,釋放併發名額(不刪除會話)。
  • dsh_list_workers:列出本橋開的 worker 及其實時狀態。
  • dsh_wait:一次等多批 worker。參數 targets + timeoutMs,任一到達終態即返回 {completed} / {errored} / "running" / "not_found"。
  • dsh_dispatch:向指定會話投遞一條用戶消息並開始一輪,返回 requestId 與 seqAtDispatch。
  • dsh_read_result:長輪詢讀進度。返回該會話在 seqAtDispatch 之後的全部助手文本,用於看過程;要結果用 dsh_wait。
  • dsh_list_sessions:列出會話元數據(id、標題、工作目錄、是否運行、最後活動時間)。

反向:DSH → Codex

插件同時給本部署的 Agent 註冊了一個工具 codex_dispatch:

  • 參數:prompt(必填)、cwd、resume(線程 id)、sandbox。
  • 返回:{ ok, text, threadId, exitCode, durationMs, error? }。

它執行 codex exec [resume <id>] --json -o <臨時文件>,從事件流取 thread.started,從 -o 文件取最終消息;工具結果裏會帶 [codex thread <id> · 12s],把那個 id 傳給 resume 就能繼續同一個 Codex 線程。

注意:resume 時 sandbox 與 cwd 不生效 —— codex exec resume 子命令本身不接受 -s / -C,線程沿用它原來的設置。

安裝

git clone https://github.com/Dogtiti/dsh-plugin-subagent-bridge

Plugins → 添加 → 選該目錄。

啓動後插件在 127.0.0.1:47821 監聽,並寫入端點文件(權限 0700/0600):

{
  "url": "http://127.0.0.1:47821/mcp/<token>",
  "port": 47821,
  "token": "<token>",
  "pid": 12345,
  "startedAt": "2026-10-22T12:30:00.000Z"
}

路徑:~/.dsh/bridge/endpoint.json

接到 Codex

codex mcp add dsh-bridge --url "$(node -p "require(process.env.HOME+'/.dsh/bridge/endpoint.json').url")"

驗證:

codex mcp list
codex mcp get dsh-bridge

移除:

codex mcp remove dsh-bridge

配置

插件配置項如下:

鍵 默認 說明
enabled true 總開關;關閉後不監聽、不寫端點文件
port 47821 迴環監聽端口;固定端口是爲了讓 Codex 側那條 URL 在重啓後依然有效
endpointFile ~/.dsh/bridge/endpoint.json 端點與令牌的落盤位置
maxResultChars 20000 單次 dsh_read_result 返回的最大字符數(超出保留尾部)
taskTimeoutSeconds 300 dsh_task 等待一輪結束的默認上限(客戶端可用 timeoutSeconds 覆蓋,最大 600)
workersFile ~/.dsh/bridge/workers.json worker 註冊表與重基線的落盤位置(0600)
maxHops 1 跨工具委派層數上限(1 = 只允許一層,和 Codex / DSH 的默認 maxDepth: 1 一致)
dispatchPerMinute 10 滾動窗口內允許的派發次數(spawn / task / dispatch / followup / codex_dispatch 合計)
dispatchWindowMs 60000 上述窗口長度
maxWorkersPerRoot 4 每個工作目錄的 worker 上限(cwd 自動分桶,調用方不需要傳 owner)
maxWorkers 12 全局兜底上限,防止多個項目一起把機器壓滿
workerTtlMs 86400000 記錄超過這個時間沒更新就不再佔名額(過期自動釋放,無需手動清理)
waitTimeoutMs 240000 dsh_wait 的默認超時(1s ~ 240s,客戶端可用 timeoutMs 覆蓋)
readWaitSeconds 25 dsh_read_result 的默認長輪詢時長(上限 55,客戶端可用 waitSeconds 覆蓋)
codexBin codex Codex 可執行文件;含 / 時按路徑嚴格解析,找不到就報錯而不回退
codexSandbox workspace-write codex_dispatch 的默認 sandbox(resume 時無效)
codexTimeoutMs 300000 單次 codex_dispatch 超時,超時先 SIGTERM 再 SIGKILL

防環護欄

兩個 agent 都接了同一個橋時,存在真實的環路:DSH worker → codex_dispatch → 那次 Codex 運行也能看到 dsh-bridge → 又 dsh_spawn → …。兩邊各自進程內的深度規則都管不到這條鏈,所以插件自帶兩道防護:

① 深度(maxHops,默認 1)

  • 每次都計算這次派發會產生的深度:max(當前在飛的鏈路深度, 調用方深度) + 1。
  • 調用方是橋建的 worker 時按其深度 1 起算。
  • codex_dispatch 運行期間會把鏈路標記爲”在飛”,嵌套回來的調用繼承該深度。
  • 超限直接報錯,措辭可執行:
    codex_dispatch would create delegation depth 2, but maxHops is 1. Carry the work out directly instead of delegating again.

默認 1 的含義:Codex 可以派給 DSH,但 DSH 不能再用橋派回去 —— 與兩個產品各自的 maxDepth: 1 語義對齊。

② 速率(dispatchPerMinute,默認 10 / 60s)

攔截廣度型失控(一次開很多 worker)和客戶端緊循環重試,與提示詞無關,是純兜底。

名額怎麼算

worker 名額按工作目錄分桶,調用方不用傳任何 owner:

  • 每個 cwd 默認 4 個(maxWorkersPerRoot)。
  • 全局默認 12 個(maxWorkers),跨項目兜底。
  • 完成但沒 dsh_close 的仍佔名額(與 Codex 一致)。
  • 記錄 24 小時沒更新就自動不再佔名額(workerTtlMs),不需要手動清理。
  • 阻塞式 dsh_task 不計入名額,只登記。

爲什麼不是”每個會話 4 個”:MCP 端點是共享的,請求裏沒有會話或線程身份(Codex 的 CODEX_THREAD_ID 只存在於 shell 環境)。cwd 是唯一由調用方穩定提供、且語義上對應”一個項目”的維度。

跨重啓

worker 註冊表(task_name / sessionId / 終態結果)和每輪的時間基線都落在 ~/.dsh/bridge/workers.json:

  • 派發、完成、關閉都會更新它。
  • 重啓後 dsh_list_workers 仍能看到之前的 worker。
  • 已經結束的 worker,dsh_wait 直接返回緩存裏的最終回答(不必重新打開會話)。
  • 重啓時還在跑的 worker,會話未打開時狀態是 offline(結果未知,去 DSH 側邊欄打開那個會話即可查看)。
  • dsh_followup 會給 worker 開新一輪,同時清掉緩存的舊回答。
  • 文件最多保留 200 條,超出時先淘汰最舊的已關閉 worker。

安全模型

  • 只監聽 127.0.0.1,不對外暴露。
  • 請求必須攜帶令牌:路徑 /mcp/<token>,或 Authorization: Bearer <token>;比較使用定長時間比較。
  • 令牌首次啓動時生成並持久化(重啓後複用),因此能力 URL 穩定。
  • 能力等價於”向指定會話投遞消息”:本機任意進程只要能讀到端點文件就能派活。文件權限 0600、目錄 0700;不要把它複製到共享位置。
  • DSH 側的權限策略不受影響:派發進去的消息仍按會話自身的 sandbox / 審批策略執行。

實現方式

Codex / 任意 MCP 客戶端
  │  POST /mcp/<token>   JSON-RPC(stateless,單次 JSON 響應)
  ▼
dsh-plugin-subagent-bridge(宿主進程內)
  · node:http 監聽 127.0.0.1:47821
  · initialize / tools/list / tools/call / ping
  · sessionController.prompt({sessionId, requestId, mode, content})
  · sessions.get(id).snapshotEvents() 讀回助手文本

協議實現爲 streamable HTTP 的無狀態子集:每個 POST 返回一段 application/json,不做服務端推送,因此取結果用長輪詢——dsh_read_result 在服務端等,客戶端一次調用即可拿到答案;客戶端斷開時服務端立即停止等待。

配套 skill

skills/ 下有兩份可直接安裝的 skill,把”什麼時候該派活、怎麼派、怎麼收尾”寫成模型能讀的規則:

Skill 裝給誰 作用
deepseek-worker Codex 讓 Codex 把任務當 subagent 派給 DSH(dsh_task),一次調用等結果
delegate-to-codex DSH 讓 DSH 的 agent 在需要獨立視角/複覈時派給 Codex(codex_dispatch)
# Codex 側
mkdir -p ~/.codex/skills && cp -R skills/deepseek-worker ~/.codex/skills/

# DSH 側
mkdir -p ~/.dsh/skills && cp -R skills/delegate-to-codex ~/.dsh/skills/

兩份都是標準 SKILL.md:YAML frontmatter(name + description)+ 正文。觸發靠 description——模型平時只看到名字和描述,判斷相關了纔讀正文,所以描述裏寫滿了中英觸發語和”不要用”的邊界。改完立刻生效,不用重啓:Codex 下一次會話、DSH 下一輪就能看到。

兼容性

  • DSH ^0.2.1-0(engines.dsh)。
  • MCP 協議版本:2025-06-18、2025-03-26、2024-11-05(按客戶端請求回顯,未知版本回落到最新)。
  • 依賴宿主公開服務 sessionController(list / prompt)與 sessions.get();兩者不可用時工具會返回明確錯誤。
羽毛球分组比赛记分
小程序二维码

欢迎使用《羽毛球分组比赛记分》微信小程序

小夜