开发工具 (adk-devtools)
adk-devtools 是编码 agent 所需的内循环工具集——读取、编辑、
搜索和运行——并且每个操作都限定在工作区目录内。它是一个独立、可发布的 crate,只依赖于 adk-core,因此它可以与任何 LlmAgent 组合使用(CodingAgent harness 会为你把它接好)。
这些工具
DevToolset 是一个 Toolset,捆绑了六个工具:
| 工具 | 参数 | 行为 |
|---|---|---|
read_file | path, offset?, limit? | 返回文件内容,带行号 |
write_file | path, content | 创建/覆盖文件(创建父目录) |
edit_file | path, old_string, new_string, replace_all? | 精确字符串替换 |
glob | pattern, path? | 列出与 glob 匹配的文件(例如 src/**/*.rs) |
grep | pattern, path?, glob?, case_insensitive? | 正则内容搜索 |
bash | command, timeout_secs? | 在工作区根目录运行 shell 命令 |
值得了解的两个安全行为:
edit_file需要在会话中先对该文件进行一次read_file,并且 默认情况下目标字符串必须恰好出现一次(可用replace_all覆盖)。这可防止盲目覆盖。grep会跳过常见的构建/VCS 目录(target、.git、node_modules,…) 以及二进制/过大的文件。
bash 工具会在命令运行时通过 ToolContext::emit_progress 按行流式输出其 stdout/stderr,
因此 UI 可以显示实时终端。每个 chunk 都会作为代理的 EventStream 上的部分事件到达
(可通过 event.tool_progress_stream() 检测);完整输出仍会作为工具的最终结果返回。另见
streaming_bash 示例 和
来自工具的流式进度。
Workspace
Workspace 会把每次操作都限定在某个目录下,并强制执行一小组策略:
use adk_devtools::Workspace;
use std::time::Duration;
let ws = Workspace::new("./my-repo"); // read-write, bash enabled
let ws = Workspace::read_only("./my-repo"); // explore/plan: no writes, no bash
let ws = Workspace::new("./my-repo")
.allow_bash(false) // file edits, but no shell
.bash_timeout(Duration::from_secs(60))
.max_output_bytes(512 * 1024);
-
路径包含性 — 任何解析到根目录之外的路径都会被拒绝,因此 代理无法读取或写入
../../etc/...。包含性是基于解析后的路径来强制执行的,而不只是字面路径: 指向根目录外部的符号链接即使在字面上位于根目录内也会被拒绝。该规则同时覆盖 作为最终组件的符号链接和作为父目录的符号链接,因此通过重定向目录进行创建也会被拒绝。 如果符号链接的目标仍位于工作区内,则会继续工作,因为仓库中合法地会包含内部链接。该检查不是锁。若在检查与随后 open 之间植入一个符号链接,系统仍会跟随它; 要关闭这个窗口,需要使用相对文件描述符的遍历,并结合平台的 no-follow 语义。 应把这些文件工具视为对游走中的代理的包含性约束,而不是对能够并发写入工作区的对手的隔离。
-
只读模式 —
Workspace::read_only(..)会完全隐藏会修改内容的工具 (模型只会看到read_file/glob/grep)。 -
bash环境已清空 — 命令只会收到PATH、HOME、LANG、LC_ALL、TMPDIR、TERM、USER和SHELL,因此代理进程持有的提供方 API 密钥 不能通过env读取。Workspace::inherit_env(true)会恢复旧的全量传递行为,而env_allowlist会替换这组环境变量。 -
bash超时 + 输出上限 — 过长或输出过多的命令会被限制。超时的命令会作为进程组被杀掉, 因此它启动的任何进程也会被杀掉;此前只有直接子进程会收到信号,而后代进程会幸存。
直接使用它
将工具集附加到任意代理:
use adk_devtools::{DevToolset, Workspace};
use adk_agent::LlmAgentBuilder;
use std::sync::Arc;
let agent = LlmAgentBuilder::new("coder")
.model(model)
.toolset(Arc::new(DevToolset::new(Workspace::new("./my-repo"))))
.build()?;
DevToolset 只会暴露工作区允许的工具,因此只读
工作区会自动得到一个只读代理。
沙箱模型
第 1 阶段在 bash 上以宿主机本地方式运行(sh -c,工作目录固定到根目录),并带有
超时和清空后的环境。这样做以及不能做到什么:
| 强制 | 不强制 |
|---|---|
| 文件工具无法解析根目录之外的路径,包括通过符号链接 | bash 仍然可以使用绝对路径——工作目录不是操作系统边界 |
| 命令无法读取 agent 的环境变量 | 命令可以访问网络 |
| 超时会终止命令及其子进程 | 没有任何机制限制内存或 CPU |
因此,它是路径内含、环境隔离且有边界的,但不是操作系统隔离的。该策略词汇与 adk-code 的 SandboxPolicy 一致;要实现强隔离,请将 bash 运行在容器化执行器后面(见设计文档)。
将其与 adk-guardrail(命令白名单、密钥脱敏)以及用于带令牌工具的 adk-auth 配合使用(例如 GitHub)。
下一步:测试执行器 →