Skip to content

feat(subagent): 支持配置原生子代理运行时策略 - #1144

Open
TWT233 wants to merge 47 commits into
deepcoldy:masterfrom
TWT233:feat/subagent-runtime-policy
Open

feat(subagent): 支持配置原生子代理运行时策略#1144
TWT233 wants to merge 47 commits into
deepcoldy:masterfrom
TWT233:feat/subagent-runtime-policy

Conversation

@TWT233

@TWT233 TWT233 commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

改了什么

  • 为 Trae 原生 spawn_agent 增加 Bot 级运行时策略;模型与思考强度可独立选择“透传请求”或固定为自定义值。
  • 在 Dashboard 增加策略配置、保存、清理和克隆支持,并补充中英文 bots.json 文档。当前协议不提供 inherit;旧 mode: "inherit" 作为无效配置整段丢弃。
  • 通过进程级 PreToolUse hook 在调用时读取当前策略,只改写原生子代理请求,不改变父代理。
  • 加固策略 IPC:请求绑定 session、turn、attempt、端口与 daemon boot;不传输 raw capability;限制重放、响应大小、超时和 pre-auth 资源;sandbox 响应只接受宿主写入的只读 proof。
  • 修正父会话运行时展示,避免子代理 spawn/end 元数据覆盖 streaming card 上的父模型与思考强度。

为什么

Bot 的父代理与原生子代理可能需要不同的模型和思考强度。此前原生子代理完全依赖单次 spawn_agent 请求,Bot 管理员无法统一固定其运行时;子代理元数据也可能污染父会话卡片展示。

本改动提供显式、可热更新的 Bot 级约束,同时让未配置维度继续保持现有透传行为。

影响面

  • 仅作用于 Botmux 管理的 Trae 原生 spawn_agent
  • 不改变父代理运行时、Workflow 子代理、跨 Bot dispatch 或其它 CLI。
  • model 与 effort 独立配置;自定义 model 时同时写入 model_provider=trae
  • 策略查询使用独立的 worker-generation policy capability,不延长逐 turn 的发送权限。
  • Dashboard Bot 克隆保留策略;公开 Bot 摘要和可移植 Agent preset 不暴露该配置。
  • 全局和项目级 hook 文件不被修改;持久 pane 通过稳定 wrapper 获取当前 hook 实现。
  • Linux/macOS 的完整沙盒和 credential-only 路径都保留策略 claim 与响应 proof 的只读可达性。

测试

  • bunx vitest run --project unit <16 个相关测试文件>:837 passed,1 skipped。
  • 合入最新 origin/master 后:
    • Dashboard 冲突相关 4 个文件:92 passed。
    • Hook / IPC 冲突相关 3 个文件:219 passed,1 skipped。
    • 核心 Hook / IPC / worker launch:225 passed,1 skipped。
  • bunx tsc --noEmit --pretty false:通过。
  • bun run build:通过。
  • bun run verify:binary:compiled binary smoke 全部通过。
  • git diff --check:通过。
  • 完整 bun run test 执行到 20,305 passed、19 skipped;剩余 9 个测试 / 1 个 suite 失败均为当前宿主环境问题(/home/data00/home 路径别名、/proc comm、bwrap 临时目录清理/MCP/DSH sandbox),相关功能测试单独运行通过。

实机验证

使用 fresh Trae 进程和真实原生 spawn_agent 验证:

  • 配置 custom 后,父 rollout 的 collab_agent_spawn_end 与 child turn_context 都记录 GPT-5.4 / high,覆盖了请求中的另一组 runtime。
  • 清空策略后,同两处记录保留请求值 DeepSeek-V4-Flash / low,child 正常完成。
  • 临时 Bot 配置、全局 hook 与 wrapper 已恢复到测试前状态。

UI

Dashboard 的 Trae Bot「Agent 配置」中增加“原生子代理运行时”区块,模型和思考强度各自提供“透传子代理请求 / 自定义”两态;非 Trae Bot 不显示该区块。

TWT233 and others added 29 commits August 31, 2026 21:49
Co-authored-by: TRAE CLI <noreply@bytedance.com>
Co-authored-by: TRAE CLI <traecli@bytedance.com>
Co-authored-by: TRAE CLI <noreply@bytedance.com>
Co-authored-by: TRAE CLI <traecli@bytedance.com>
Co-authored-by: TRAE CLI <noreply@bytedance.com>
Co-authored-by: TRAE CLI <traecli@bytedance.com>
Co-authored-by: TRAE CLI <traecli@bytedance.com>
Co-authored-by: TRAE CLI <traecli@bytedance.com>
Co-authored-by: TRAE CLI <traecli@bytedance.com>
Co-authored-by: TRAE CLI <traecli@bytedance.com>
Co-authored-by: TRAE CLI <noreply@bytedance.com>
Co-authored-by: TRAE CLI <traecli@bytedance.com>
Co-authored-by: TRAE CLI <traecli@bytedance.com>
Co-authored-by: TRAE CLI <traecli@bytedance.com>
Co-authored-by: TRAE CLI <noreply@bytedance.com>
Co-authored-by: TRAE CLI <traecli@bytedance.com>
Co-authored-by: TRAE CLI <noreply@bytedance.com>
Co-authored-by: TRAE CLI <traecli@bytedance.com>
Co-authored-by: TRAE CLI <noreply@bytedance.com>
Co-authored-by: TRAE CLI <traecli@bytedance.com>
Co-authored-by: TRAE CLI <traecli@bytedance.com>
Co-authored-by: TRAE CLI <traecli@bytedance.com>
Co-authored-by: TRAE CLI <traecli@bytedance.com>
Co-authored-by: TRAE CLI <traecli@bytedance.com>
Co-authored-by: TRAE CLI <traecli@bytedance.com>
Co-authored-by: TRAE CLI <traecli@bytedance.com>
Co-authored-by: TRAE CLI <traecli@bytedance.com>
Co-authored-by: TRAE CLI <traecli@bytedance.com>
Co-authored-by: TRAE CLI <noreply@bytedance.com>
Co-authored-by: TRAE CLI <traecli@bytedance.com>
Co-authored-by: TRAE CLI <traecli@bytedance.com>
Co-authored-by: TRAE CLI <noreply@bytedance.com>
Co-authored-by: TRAE CLI <traecli@bytedance.com>
Co-authored-by: TRAE CLI <traecli@bytedance.com>
Co-authored-by: TRAE CLI <traecli@bytedance.com>
Co-authored-by: TRAE CLI <traecli@bytedance.com>
Co-authored-by: TRAE CLI <traecli@bytedance.com>
Co-authored-by: TRAE CLI <noreply@bytedance.com>
Co-authored-by: TRAE CLI <traecli@bytedance.com>
@TWT233
TWT233 requested a review from deepcoldy as a code owner September 1, 2026 02:00
@deepcoldy

Copy link
Copy Markdown
Owner

感谢这个 PR,设计和文档都很扎实——IPC 鉴权(域分离 HMAC、nonce 重放、时间窗、响应 proof、有界读取)、invalid 策略状态的显式建模、以及中英文文档都写得很完整。我在最新 origin/master 的本地合并树上做了自动化评审,先说结论:有 1 个建议修改的阻断项,其余是测试有效性的小建议。

以下为自动评审的初步意见,最终以维护者审阅为准。


🔴 1(建议修改):沙箱里 MCP gateway 的 shim 覆盖被误伤,plugin-mcp-sandbox 两条用例转红

src/adapters/backend/sandbox.ts 新增的稳定 wrapper 排除:

if (resolvedTarget === stableWrapperTarget) continue;

路径判定,而 stableBotmuxWrapperPathMCP gateway 的生产默认入口是同一个路径

  • src/core/plugins/mcp/gateway-installer.ts:40<realpath(home)>/.botmux/bin/botmux
  • src/core/botmux-wrapper.ts resolveStableBotmuxWrapperPath()<realpath(home)>/.botmux/bin/botmux

src/worker.ts:14909 正是把前者作为 trustedBotmuxCommandPaths 传进来的,于是该路径不再被 overlay 成 relay shim。沙箱内以绝对路径调用 gateway(cliBin 就是绝对路径,不走 PATH)时会执行宿主 wrapper,拿不到 relay 模式。

证据(都在最新 master 合并树上跑的):

环境 结果
canonical origin/master test/plugin-mcp-sandbox.test.ts 3 passed
本 PR 合并树(本地) 2 failed / 1 passedMcpError: Connection closed
本 PR 的 CI build 同样这 2 条 failed

直接对比两棵树产出的 bwrap argv,差异恰好是少了这 3 个参数

--ro-bind
<home>/.botmux/data/sandboxes/<sid>/shimbin/botmux
<home>/.botmux/bin/botmux

注意紧邻的既有守卫 if (selfExec !== undefined && resolvedTarget === selfExec) continue; 已经覆盖了注释里描述的"编译版二进制自己"那种场景,且只在该路径就是当前运行的可执行文件时才跳过;新加的这条是无条件按路径匹配,范围明显更宽。

建议:把排除条件收窄到真正需要豁免的情形(例如仅在 isStandaloneBinary() / 该路径确实等于当前执行体时跳过),或反过来让 native hook 走一个不与 gateway 入口冲突的路径;无论哪种,都请让 plugin-mcp-sandbox 恢复绿。

另外 resolveStableBotmuxWrapperPath(process.env, ...) 直接读 process.env,而 prepareDirectSandbox 本身已经有显式的 home 参数(sandbox.ts:663,且 847 行用它设置子进程 HOME)。同一函数里两个 home 来源不一致,建议统一从 opts.home 推导,stableBotmuxWrapperPath 测试缝可以保留。


🟠 2(建议补测):hook 的 tool_name 匹配没有被真实覆盖

src/cli.ts 里这行是"只改写 spawn_agent"的唯一保证:

if (payload.hook_event_name !== 'PreToolUse' || payload.tool_name !== 'spawn_agent') return;

|| payload.tool_name !== 'spawn_agent' 删掉后,PR 新增的 21 个测试文件全绿

现有的否定用例(native-subagent-runtime-hook.test.ts:252tool_name: 'Bash')是用 startServer: false 跑的——此时 hook 在拿到策略前就 fail-open 返回了,所以它无论匹配器在不在都会通过

我另写了一个探针确认这个变异不是惰性的:接上真实 policy server 后,删掉匹配器会让 Bash 调用被注入 model / model_provider

{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"allow",
 "updatedInput":{"command":"ls","model_provider":"trae","model":"POLICY-MODEL"}}}

同一探针的阳性对照(spawn_agent 走同一条 live server 路径确实被改写)是通过的,所以这不是探针接线问题。建议把那条 Bash 否定用例改成挂真实 server 跑,断言 stdout 为空。


🟡 3(小建议):源码文本断言可以被注释满足

test/read-isolation.test.ts:216expect(workerSource).toContain('[canonical(originDirectory), canonical(attestationDirectory)]') 来守 Seatbelt 授权。我把 src/worker.ts 里真实那行注释掉、只让注释里保留同样的字面量,测试依然全绿(88 passed)。建议断言前先剥注释,或改成断言 buildSeatbeltProfile 的实际入参。


✅ 已验证没问题的部分

  • bwrap 分组绑定确实是必要修复:我用真实 bwrap 实测,旧写法(同一 parent 两次 --tmpfs第二次会把第一个 bind 抹掉(capability 文件读不到);新写法两个目录都可读、兄弟会话目录不可见、只读生效。
  • managedTurnOrigin 在 turn terminal 后保留 policy 授权不削弱鉴权:占位 capability 是新的随机值,没有任何一方持有,所有按 capability 比对的路径仍然失败;测试也显式断言了它 ≠ 被吊销的那个。
  • daemon boot id 从 randomUUID() 改为 43 字符 base64url:pane marker 把它当不透明字符串,无格式校验;且与 B64URL_32_BYTES_RE 一致。
  • 反变异:对核心守卫打了 10 个变异,9 个转红(响应鉴权 fail-closed、capability 签名校验、重放保护、loopback、策略 mode 校验、429 fail-closed、wrapper 排除、invalid 状态追踪、transcript 类型守卫)。
  • worker 侧 !remoteWsUrl 判断看似无覆盖,但实为惰性变异——traex.tsbuildArgs 在 remote 分支提前 return,根本读不到该参数,属于冗余纵深防御,不是缺口。
  • bunx tsc --noEmit 通过;bun run build 通过;PR 涉及的 21 个测试文件在最新 master 合并树上 1360 passed / 1 skipped
  • 相邻回归面(vc-meeting / managed-origin / worker / session / codex-app / current-actor / daemon-ipc / workflow-daemon 共 127 文件)在 dist/ 构建后 1999 passed,与 master 逐项一致。

CI 里另一条红 mojo-close-worker-journal 与本 PR 无关(已知异步 IPC 竞态 flake,见 issue #1151),本 PR 未触碰任何 mojo 文件。


以上为自动评审的初步意见,最终以维护者审阅为准。第 1 条建议先修,其余可按需处理。

TWT233 and others added 5 commits September 1, 2026 17:10
Co-authored-by: TRAE CLI <noreply@bytedance.com>
Co-authored-by: TRAE CLI <traecli@bytedance.com>
Co-authored-by: TRAE CLI <traecli@bytedance.com>
Co-authored-by: TRAE CLI <noreply@bytedance.com>
Co-authored-by: TRAE CLI <traecli@bytedance.com>
Co-authored-by: TRAE CLI <noreply@bytedance.com>
Co-authored-by: TRAE CLI <traecli@bytedance.com>
Co-authored-by: TRAE CLI <traecli@bytedance.com>
@TWT233
TWT233 force-pushed the feat/subagent-runtime-policy branch from b46dd13 to 12deaac Compare September 1, 2026 10:15
TWT233 and others added 9 commits September 1, 2026 18:16
…me-policy

Co-authored-by: TRAE CLI <traecli@bytedance.com>
Co-authored-by: TRAE CLI <traecli@bytedance.com>
Co-authored-by: TRAE CLI <traecli@bytedance.com>
Co-authored-by: TRAE CLI <traecli@bytedance.com>
Co-authored-by: TRAE CLI <traecli@bytedance.com>
Co-authored-by: TRAE CLI <traecli@bytedance.com>
Co-authored-by: TRAE CLI <traecli@bytedance.com>
Co-authored-by: TRAE CLI <traecli@bytedance.com>
Co-authored-by: TRAE CLI <traecli@bytedance.com>
@deepcoldy

Copy link
Copy Markdown
Owner

再次感谢快速跟进。这轮(f511882ed068dc6693,共 8 个 commit)我在最新 origin/master 的本地合并树上做了增量复审:此前三条意见已全部修复且都验证过"有牙",新增改动我又打了 9 枪变异,8 红 1 绿,那 1 绿是一处很小的覆盖空档(非阻断)。

以下为自动评审的初步意见,最终以维护者审阅为准。


✅ 三条旧意见逐条复验(都在干净 checkout里做的,见文末说明)

# 意见 修法 复验判据
🔴 1 wrapper 排除误伤 MCP gateway 撤掉按路径的排除,改为独立入口 botmux-native-subagent-runtime-hook 旧 head bfb1512f8 在同一干净环境 2/3 失败;新 head 3/3 通过。sandbox argv 与 master 逐字一致
🟠 2 tool_name 匹配器零覆盖 新增挂真实 serverBash 否定用例(断言 stdout/stderr 空且 capturedRequests 为 0) 删匹配器:修前 17/17 全绿 → 现在转红
🟡 3 源码文本断言可被注释满足 引入 stripComments(),并且顺带加固了本不属于本 PR 的既有断言 注释掉真实行、注释里留同字面量:修前 88/88 照过 → 现在转红

独立入口这个修法我额外验了两点:

  • 四种形态的 wrapper 都正确生成(posix/win32 × node/standalone),路径分别落到 botmux-native-subagent-runtime-hook.cmd
  • isMainWrapper 收窄自覆盖守卫是安全的:install.sh 只会把编译二进制装到 bin/botmux(install.sh:118 mv "$candidate" "$INSTALL_DIR/botmux"),新入口名永远不可能等于正在运行的可执行文件,所以守卫收窄不会让 daemon 覆盖自己。

✅ 这轮新增逻辑(白话)

前两轮只解决"hook 怎么被安全地执行",这轮解决的是"策略查询权限该活多久":

CLI 崩溃后 botmux 会在同一个 Node worker 内自动重启 CLI。之前 CLI 一退出就把 policy capability 一起吊销了,于是重启后的第一个子代理查不到策略。这轮把生命周期精确化成:live 发送权限跟着 backend 世代走(一退就废),policy 查询权限跟着 Node worker 世代走——只在这个 worker 还真有资格原地重启时才保留(排除 codex-app 活跃写入方、shared-adopt、远端后端、worker 已 killed),崩溃循环放弃重启时立即清掉。revoke 消息也拆成两条独立通道,各自精确 token 匹配,避免一个陈旧 token 挡掉另一个的正常吊销。

另外补上了 Dashboard 侧校验:选了"自定义"却留空不再允许保存(这正是我上一轮提到的空值路径)。

🔬 新增逻辑的反变异(9 枪,8 红)

转红(守卫有效):崩溃退出的资格判定、崩溃循环放弃时清权限、revoke 的 live/policy 两侧 token 精确匹配、killClipreservePolicyCapability、Dashboard 空模型/空强度两条校验,以及最关键的一条——

  • V3-M9:把 worker exit 处理里的 ds.managedTurnOrigin = undefined 删掉会转红。这是"policy 权限绝不能比 worker 活得久"的核心不变量,确认被钉住了。

唯一转绿(🟡 建议补测,非阻断)worker-pool.ts 崩溃分支末尾 else { ds.managedTurnOrigin = undefined; }(即 !ds.worker || ds.worker.killed)删掉后全绿。

我确认它不是惰性代码:入口处 mayRestartLocalWorker 与这个 else 之间隔着多个 awaitcb.onCliExitscopedReplycloseWithdrawnSessionIfLedgerEmpty),期间 ds.worker 可能被置空或 killed,所以这条分支真的可达且带着仍然存活的 policy 权限。我加了个抛异常探针挂在该分支上,跑完 167 条用例一次都没打响——说明现有用例根本没走到这个状态,是真空档而非无效变异。风险很低(兜底清理),但既然这轮的主题就是权限存续期,建议补一条覆盖它。

✅ 其它已验证

  • bunx tsc --noEmit 通过;bun run build 通过。
  • PR 涉及的 27 个测试文件:1414 passed / 1 skipped
  • 更宽回归面(vc-meeting / managed-origin / worker / session / codex-app / current-actor / daemon-ipc / dashboard / plugin-mcp,共 230 文件):3929 passed / 1 skipped,零失败
  • Bun 跳过那条(12deaaccd)我按"跳过是否掩盖真实失败"查了:它只在 Bun 下跳过,vitest 下 6/6 照跑;注释里点名的三条兜底用例确实存在,且我在 Bun 下实跑确认 3 pass,再打变异(删掉适配器里注入 hook 参数那几行)→ Bun 下 3 条里 2 条转红,兜底是真有牙的,不是空头承诺。
  • cli-adapters.test.ts 在 Bun 下有 1 条 hermes buildArgs 失败,我核对过 master 自己同样失败(master 418 pass/1 fail,PR 423 pass/1 fail),属预存在,与本 PR 无关。

⚠️ 一个我这边的环境坑,写出来免得误导

我本地 review worktree 的 node_modulessymlink 到主 checkout 的,这会让 plugin-mcp-sandbox 的 bwrap 用例假红——我实测把 master 自己的源码放进同一个 worktree 也同样 2/3 失败,所以那是环境产物,不是代码问题。本轮所有结论都改在 /tmp全新 clone + 真实 node_modules 目录里重跑得到的。上一轮我报的阻断项我也回到这个干净环境复核过:旧 head 确实 2/3 失败、新 head 3/3 通过,结论成立,没有冤枉这个 PR

目前 CI 的 build 门禁在这个 head 上还在跑(上一个 head f511882ed 已经是 pass,而报告问题的那个 head 是 FAILURE)。建议等 CI 跑完再合

以上为自动评审的初步意见,最终以维护者审阅为准。就我这边看,剩下的只有那一条建议补测,不构成阻断。

TWT233 and others added 4 commits September 1, 2026 20:30
…me-policy

Co-authored-by: TRAE CLI <traecli@bytedance.com>
Co-authored-by: TRAE CLI <traecli@bytedance.com>
…me-policy

Co-authored-by: TRAE CLI <traecli@bytedance.com>
…me-policy

Co-authored-by: TRAE CLI <traecli@bytedance.com>
@TWT233

TWT233 commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

已按本轮反馈完成并推送修正:

  • MCP gateway 与 native hook 拆成不同稳定入口;通用 botmux wrapper 恢复 sandbox relay overlay,native hook 使用 botmux-native-subagent-runtime-hook
  • spawn_agent 负例改为连接真实 policy server,并通过 guard mutation 验证会 RED。
  • Seatbelt 接线断言先去注释,避免注释文本造成假绿。
  • Dashboard 增加 custom model/effort 保存前必填校验;用户切换模型后才归一不兼容 effort,初始 hydration 不改写已存配置;补充两组 mutation RED。
  • policy capability 与逐 turn live-send capability 分离;intentional restart、自然 crash 后的本地 same-worker auto-restart保留 policy token,remote/no-restart、crash-loop 停驻和真实 worker teardown 清理它;daemon revoke 按 live/policy token 独立精确匹配。
  • Bun 1.4 下只跳过 direct node-pty 无法稳定启动 fake CLI 的用例,真实 tmux/RPC 和其余 lifecycle 覆盖继续执行。

最新分支已合入 origin/master@242d386c。验证:

  • bun run build 通过;GitHub build 通过。
  • bun run verify:binary 全部 smoke 通过;GitHub glibc/musl binary 均通过。
  • 核心 16 文件:697 passed / 1 skipped。
  • lifecycle:179 passed。
  • native hook:18 passed。
  • Bun feature 子集:hook wiring 3 passed / 3 skipped,lifecycle 3 passed / 1 skipped。
  • 当前 bun-test 的 30 个失败文件与同一 origin/master@242d386c 的 Bun CI 失败文件集合完全一致;本 PR 没有新增 Bun 失败文件。
  • 本机 Dsh sandbox 单跑在把 worktree 的两级 node_modules symlink 改为直接指向 canonical 后通过,确认本地红项是 worktree 依赖链接拓扑,不是产品 diff。

完整最终审查未发现 Critical / Important / Minor 问题。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants