返回《DeepSeek Harness 架构设计指南》

作用域与同名遮蔽

IT周瑜

到现在为止,我们默认整个 dsh 里每个名字只有一份注册。真实的使用不是这样。一个会话里可能同时跑着几个 Agent,主 Agent 有全部工具,一个专职写代码的子 Agent 只该看到编辑类工具,一个负责和用户确认的操作员 Agent 需要额外的审批面板。这些 Agent 共享同一个进程、同一份全局注册表,又各自需要一份自己的定制。dsh 用作用域机制解决这个问题,本章讲它的两个层次,分层注册和同名遮蔽。

先看全局层。插件装载时注册的服务和工具,落在全局层,对上下文里所有使用者可见。dsh-base 提供的那一整套能力,模型适配、工具、持久化,都在这一层。这是默认世界。

再看专属层。每个 Agent 出生时得到自己的 agent.ctx,一个只属于它的注册上下文。在专属层上注册的工具和服务,只有这个 Agent 看得见。实现上,作用域的键是一个不透明的对象身份,当前实现直接用 Agent 对象本身充当,底层机制不检查它的内容,只做身份比较。这个设计让作用域原语保持纯粹,它是核心包里少数不走 ctx 服务注册的东西之一,就是一个库原语。

两层之间有一条查找规则,也是本章标题的后半部分。Agent 查找注册时,先看专属层,再看全局层。专属层里注册了同名条目,就把全局层那份挡在后面,这就是遮蔽。注意被遮蔽不等于被删掉,其他 Agent 看到的仍是全局层那份原装注册。遮蔽是视角性的,不是破坏性的。

global 全局注册 工具 A 工具 B 服务 X 某个 Agent 的专属作用域 agent.ctx 工具 A* 工具 C Agent 就近查找 同名被遮蔽 专属注册先被看见,同名全局注册被挡在后面,其余照常可用
图 8-1 Agent 就近先看专属层,同名专属注册遮蔽全局注册,其余照常可用

遮蔽能做什么,两个例子足够。想让某个 Agent 的 Bash 工具改走沙箱执行,就在它的专属层注册一个同名的沙箱版工具,全局层原封不动。想让某个 Agent 干脆没有某个工具,工具集过滤同样在专属层生效,能力子集由此而来。两种定制都不触碰任何现有插件。

在作用域之上,dsh 又叠了一层组合便利,preset,预设。一个预设是一份组合配置,声明这个 Agent 由哪些插件构成、哪些服务行指向哪个提供者。想给一个会话配一个只读的检索 Agent,写一份预设,列出它需要的工具和模型适配,装载即生效。预设里如果需要服务级别的隔离,配置行上标一个 isolate 领域声明,框架会为这个 Agent 单独立起一份服务实例,而不是与别人共享全局那台。

这套机制的边界也值得说清楚。作用域只有两层,专属与全局,没有更深的嵌套,子代理的专属层并不会嵌套在父 Agent 的专属层里,每个 Agent 平等拥有自己的一层。设计者显然在克制,层次越深,理解负担越重,两层的表达力对当前所有需求已经够用。这是整个项目风格的一个缩影,机制给到刚好,多余的自由度不给。

作用域的影响面比工具集更宽。系统提示的各个分节同样按作用域解析,一个 Agent 的专属层可以注册一段自己的提示分节,模型每次请求看到的自我介绍因此不同。会话标题、身份、命令面板,凡是挂在注册表上的东西都遵守同一套两层查找。设计的一致性在这里体现得很充分,学会了遮蔽这一条规则,就等于学会了全部注册项的定制方式,不需要为每类东西单学一套机制。

举一个收束的例子。给一个代码评审 Agent 配上被遮蔽的只读 Bash 工具、一段评审人设的提示分节、一个只含必要工具的预设,三层配置各自独立维护,组合起来就是这个 Agent 的完整性格。想再要一个性格不同的 Agent,复制这套结构换掉内容,两者共存互不干扰。所谓一千个 Agent 一千种性格,机制上只花了两个层次和一条查找规则。

回头看第 5 章周期图里装配工具清单那一步,现在可以补全它的含义。所谓装配,就是在那一刻按 Agent 的视角解析两层注册,得到它自己的工具集。周期、日志、接缝、作用域,到这里已经拼成一张完整的网。

还剩最后一块拼图,Agent 生出 Agent。下一章讲子代理与任务委托。