Skip to content

fix(subagent): refuse the disabled backend tools instead of only hiding them - #1001

Open
rafaelasor wants to merge 1 commit into
xerrors:mainfrom
rafaelasor:fix/subagent-tool-enforcement
Open

fix(subagent): refuse the disabled backend tools instead of only hiding them#1001
rafaelasor wants to merge 1 commit into
xerrors:mainfrom
rafaelasor:fix/subagent-tool-enforcement

Conversation

@rafaelasor

Copy link
Copy Markdown

变更说明

先说明利益关系:我维护开源的智能体委派库 attenu-guard
(github.com/attenu-io/attenu-guard),是在为它测试 handoff 时发现这个问题的。本补丁是
原生实现,没有用到它。

默认审批模式把 write_fileedit_fileexecute 从子智能体隐藏,避免子智能体绕过主
线程的逐项审批。但隐藏只作用于 bind_toolscreate_agent_filesystem_middleware 仍把七个
文件工具全部注册进子智能体的 ToolNode,且子智能体不构造审批中间件,所以同名调用仍会执行。

本改动让“隐藏”等于“关闭”:

  • create_agent_filesystem_middleware 增加 disabled_tools 参数,被禁用的工具根本不注册。
  • _SubAgentToolFilterMiddleware 增加 wrap_tool_call / awrap_tool_call,在工具体执行前
    对被禁用的名字返回 status="error"ToolMessage,提示模型把结果交回主智能体。

本改动直接影响权限与模型可见输入,按决策记录规范属于非平凡变更。改动小而完整、没有待裁决
的替代方案,因此没有先提 proposed;如维护者认为需要,我补一份
docs/develop-guides/decisions/implemented/2026-09-07-subagent-tool-enforcement.md

(English) Interest first: I maintain attenu-guard, an open-source agent-delegation library
(github.com/attenu-io/attenu-guard), and found this while testing handoffs for it. This
patch is native and does not use it.

Default approval mode hides write_file, edit_file and execute from subagents so they
cannot bypass the main thread's per-call approval. The hide applies at bind_tools only,
while create_agent_filesystem_middleware registers all seven filesystem tools in the
subagent's ToolNode and the subagent builds no approval middleware, so a call naming one of
the three still runs.

This change makes the hide mean disabled. The middleware takes a disabled_tools set and
does not register those tools. _SubAgentToolFilterMiddleware gains wrap_tool_call /
awrap_tool_call, which return a ToolMessage with status="error" before the body runs.

This changes permissions and model-visible input, so it is non-trivial under the decision
record rules. It is small and self-contained with no open alternative, so I did not file a
proposed record first. I will add an implemented record if you want one.

验证情况

新增六个测试用例(test/unit/agents/test_subagent_tool_filter.py)。在我本机
pytest test/unit -m "not slow" --ignore=test/unit/test_live_api_cleanup.py 从 1800 通过
变为 1806 通过(在 fd0d9c4f 上;在当前 main e460fad6 上同样全部通过,排除了新增的 test/unit/performance 目录,它在我的环境中无法收集,与本改动无关)。排除那个文件是因为在我使用的 Homebrew Python 下,标准库的 test 包遮蔽了
backend/test,该文件收集失败,与本改动无关。

另外运行了 python3 scripts/verify_engineering_contracts.py(通过:77 decisions /
5 workflows / 4 agents files / 126 docs / 26 routers / 241 web sources)与
python3 -m unittest scripts.test_verify_engineering_contracts(61 tests, OK)。
integration 与 e2e 未运行:本机没有起 Docker Compose 拓扑。

(English) Six test cases are added. Locally
pytest test/unit -m "not slow" --ignore=test/unit/test_live_api_cleanup.py goes from 1800
to 1806 passed; I exclude that file because the stdlib test package shadows backend/test
under my Homebrew Python, which is unrelated to this change. I also ran
python3 scripts/verify_engineering_contracts.py (passes) and
python3 -m unittest scripts.test_verify_engineering_contracts (61 tests, OK). I did not
run integration or e2e; I had no Docker Compose topology up.

影响与风险

always_trust 行为不变:_disabled_tools_for("always_trust") 仍然只返回
present_artifactsask_user_questioninstall_skill,所以那些敏感 backend 工具在该
模式下照常注册、照常可调用。default 模式下的行为变化是刻意的:被隐藏的工具从“可执行”变为
“执行前被拒绝”。

(English) always_trust is unchanged: _disabled_tools_for("always_trust") still returns
only present_artifacts, ask_user_question and install_skill, so the sensitive backend
tools stay registered and callable there. The behaviour change in default mode is the
intended one: a hidden tool goes from executable to refused before its body runs.

界面与关联事项

不涉及界面。Closes #1000

(English) No UI. Closes #1000

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.

1 participant