openinterpreter 中 opencode 搜索子代理提示词的设计与源码实现解析
本文以 openinterpreter 仓库中 codex-rs/core/src/harness/opencode_search_agent_prompt.md 这份搜索子代理(search agent)系统提示词为主体,完整解析其角色定义与行为约束,并结合 opencode harness 的请求构建、工具集裁剪和子代理派发生成链路,说明这段提示词在真实运行中如何被加载、如何决定子代理只能用哪些工具,以及主代理如何通过 task 工具触发这个只读搜索流程。
一、提示词全文与设计意图
opencode_search_agent_prompt.md 的全部正文如下(共 17 行,是完整的可独立成文内容):
You are a file search specialist. You excel at thoroughly navigating and exploring codebases.
Your strengths:
- Rapidly finding files using glob patterns
- Searching code and text with powerful regex patterns
- Reading and analyzing file contents
Guidelines:
- Use Glob for broad file pattern matching
- Use Grep for searching file contents with regex
- Use Read when you know the specific file path you need to read
- Use Bash for file operations like copying, moving, or listing directory contents
- Adapt your search approach based on the thoroughness level specified by the caller
- Return file paths as absolute paths in your final response
- For clear communication, avoid using emojis
- Do not create any files, or run bash commands that modify the user's system state in any way
Complete the user's search request efficiently and report your findings clearly.
这段提示词的技术要点可以拆解为四类约束:
- 角色定位(Role):开篇即声明 "You are a file search specialist",把子代理的职责收敛到"导航和探索代码库"一件事上,避免模型把它当作通用编码代理去尝试写代码。
- 能力声明(Strengths):明确列出三项核心能力——用 glob 模式快速找文件、用正则搜索代码与文本、读取并分析文件内容。这三项与后文(第三节)实际下发给该子代理的工具集一一对应:glob、grep、read。
- 工具使用准则(Guidelines):
- "Use Glob for broad file pattern matching"——宽泛的文件名匹配交给 Glob;
- "Use Grep for searching file contents with regex"——内容检索交给 Grep;
- "Use Read when you know the specific file path"——路径已知时直接 Read,不做无谓搜索;
- "Use Bash for file operations like copying, moving, or listing directory contents"——Bash 的定位是文件操作辅助(复制、移动、列目录),而不是主要搜索手段;
- "Adapt your search approach based on the thoroughness level specified by the caller"——这是全文最关键的一条:搜索深度不是子代理自己决定的,而是由调用方(主代理)在任务描述中指定的。对应 opencode.rs 中
task工具描述里的三档深度:"quick" for basic searches, "medium" for moderate exploration, or "very thorough" for comprehensive analysis。也就是说,主代理在调用task工具派发搜索任务时,需要在 prompt 里写明探查程度,子代理据此决定搜索的广度和轮次。 - "Return file paths as absolute paths in your final response"——最终结论必须回绝对路径。这样父线程拿到的搜索结果可以直接用于后续编辑或验证,无需再做相对路径消解。
- "Do not create any files, or run bash commands that modify the user's system state in any way"——只读红线。这条是安全边界:子代理虽然持有 bash 工具,但被提示词禁止执行任何改变系统状态的命令。
- 交付要求(Closing):"Complete the user's search request efficiently and report your findings clearly"——要求高效完成并清晰汇报,隐含"结论优先、不做多余动作"的风格。
值得注意的一个细节:提示词中"避免 emoji"的条款("avoid using emojis")是工程化的沟通约束。因为子代理的返回结果会被父线程作为工具输出消费,emoji 会增加无意义的 token 并干扰结果解析。
二、提示词的加载机制:编译期内嵌与身份判定
这段 Markdown 不是运行时读取的文件,而是在 Rust 编译期通过 include_str! 内嵌为静态字符串。在 opencode.rs 中:
const OPENCODE_MAX_TOKENS: u32 = 32_000;
pub(crate) const OPENCODE_SEARCH_AGENT_BASE_INSTRUCTIONS: &str =
include_str!("opencode_search_agent_prompt.md");
pub(crate) const OPENCODE_TASK_AGENT_BASE_INSTRUCTIONS: &str = "opencode-task-agent";
这里体现了 opencode harness 的一个关键设计:用提示词文本本身作为子代理身份的"指纹"。判定函数在 opencode.rs:
fn is_search_agent_prompt(prompt: &Prompt) -> bool {
prompt.base_instructions.text.trim_end() == OPENCODE_SEARCH_AGENT_BASE_INSTRUCTIONS.trim_end()
}
当某个会话(thread)的 base_instructions 与这份搜索提示词做 trim_end() 后精确相等时,请求构建器就将其识别为"搜索代理",并走一系列专属分支(下一节展开)。这种"提示词即身份"的做法意味着:修改 Markdown 文件的任何一个字符,都可能改变该会话在 harness 中的行为分支——它是把提示词当作配置键(config key)来使用的,而不只是给模型看的自然语言。
三、搜索代理专属的请求构建分支
识别出搜索代理后,build_request 与 build_system_prompt 会做出三处针对性调整,全部可以在 opencode.rs 中验证:
1. 系统提示词替换。主会话使用 opencode_system_prompt.md(OPENCODE_SYSTEM_PROMPT_PREFIX),而搜索代理直接使用这份 17 行的搜索提示词作为前缀:
let prompt_prefix = if is_search_agent_prompt(prompt) {
OPENCODE_SEARCH_AGENT_BASE_INSTRUCTIONS.trim_end()
} else {
OPENCODE_SYSTEM_PROMPT_PREFIX.trim_end()
};
随后统一追加环境块(工作目录、workspace 根、平台、日期):
format!(
"{prompt_prefix}\n\nYou are powered by the model named deepseek-chat. The exact model ID is deepseek/{model}\nHere is some useful information about the environment you are running in:\n<env>\n Working directory: {cwd}\n Workspace root folder: {workspace_root}\n Is directory a git repo: yes\n Platform: {platform}\n Today's date: {today}\n</env>{skills}",
...
)
可以观察到搜索代理的最终 system prompt = 搜索提示词 + 模型身份声明 + <env> 环境块,没有任何多余的技能说明。
2. 工具集裁剪为只读搜索子集。完整工具集由 build_tools() 定义,包含 bash、edit、glob、grep、read、skill、task、todowrite、webfetch、write 共 10 个工具;而搜索代理只保留 5 个(opencode.rs):
fn build_search_agent_tools() -> Vec<Value> {
build_tools()
.into_iter()
.filter(|tool| {
matches!(
tool.get("function").and_then(|function| function.get("name")).and_then(Value::as_str),
Some("bash" | "glob" | "grep" | "read" | "webfetch")
)
})
.collect()
}
这正好对应提示词 Strengths 中声明的 glob/grep/read 能力,外加 bash(文件操作辅助)与 webfetch(补充外部资料)。被裁掉的 edit、write、skill、task、todowrite 恰好覆盖了"写文件、递归派发、维护任务清单"这三类会产生状态或超出搜索职责的动作。也就是说,提示词中"Do not create any files"的红线不只是软约束,还有硬约束兜底:模型根本拿不到写文件的工具。这是"提示词约束 + 工具面裁剪"双重保障的典型设计。
3. 用户消息不做 JSON 引号化。build_messages 中对搜索代理与普通会话做了区分(opencode.rs):
"content": if search_agent {
message_content
} else {
quote_prompt_for_opencode(&message_content)
},
普通会话的用户内容会经过 quote_prompt_for_opencode 做 JSON 字符串化与换行规范化;搜索代理则原样传递。从源码结构看,这是因为搜索任务描述往往包含 glob 模式、正则表达式等多行内容,保持原始文本更利于子代理精确解析调用方意图(包括前文提到的 thoroughness 级别说明)。
此外,搜索代理的系统提示词中不会附加 <available_skills> 技能块(skills 变量在搜索分支下为空字符串),进一步缩小了上下文面。
四、触发链路:主代理通过 task 工具派发搜索子代理
这份提示词的实际使用入口是主代理的 task 工具。task 工具的描述(opencode.rs)明确列出了 explore 代理类型:
"explore: Fast agent specialized for exploring codebases. Use this when you need to quickly find files by patterns ... or answer questions about the codebase ... When calling this agent, specify the desired thoroughness level: "quick" ... "medium" ... or "very thorough" ..."
当主代理发出 task 调用时,handle_opencode_task(harness_aliases.rs)负责把这份提示词注入子代理配置并派发生成:
let base_instructions = BaseInstructions {
text: OPENCODE_SEARCH_AGENT_BASE_INSTRUCTIONS.to_string(),
};
let mut config = build_agent_spawn_config(
&base_instructions,
turn.as_ref(),
turn.environments.primary(),
)?;
config.base_instructions = Some(OPENCODE_SEARCH_AGENT_BASE_INSTRUCTIONS.to_string());
随后通过 agent_control.spawn_agent_with_metadata 以当前会话的 thread_id 为父级派生子线程,等待其完成并取回最终消息,最后以如下格式返回给主代理(harness_aliases.rs):
task_id: ses_{task_id} (for resuming to continue this task if needed)
<task_result>
{task_result}
</task_result>
几个实现事实值得记录:
- 子代理配置由 multi_agents_common.rs 的
build_agent_spawn_config构造,它会克隆父会话的有效配置,并从父 turn 中刷新模型、推理设置、审批策略、sandbox 与 cwd 等运行时字段,再叠加base_instructions覆盖——这正是第二节"提示词即身份"判定能生效的前提。 - 注意
handle_opencode_task中base_instructions被无条件设为搜索提示词,与模型传入的subagent_type参数无关;即从源码结构看,opencode harness 下经task工具派发的子代理一律以"文件搜索专家"身份运行,subagent_type只参与生成子线程的角色命名(opencode_task_name)。 - 返回的
task_id(形如ses_...)可用于恢复同一子代理会话继续任务,这与task工具参数 schema 中task_id字段"resume a previous task"的说明一致(opencode.rs)。
五、适用前提:opencode harness 与 chat 线协议
上述链路只在特定配置组合下生效。根据 routing.rs 的线协议路由表:
- opencode harness 要求
wire_api = "chat",此时走StreamTransportRoute::ChatHarness(ChatHarnessRoute::OpenCode); - 若配置
wire_api = "messages",则直接被拒绝并返回错误wire_api = "messages" is not supported by harness = "opencode"(routing.rs),并有对应单测opencode_chat_wire_uses_harness_native_chat_route固化该行为。
即本文描述的所有机制(搜索提示词内嵌、身份判定、工具裁剪、子代理派发)都以 harness = "opencode" + wire_api = "chat" 为前提。
六、小结:一份 17 行提示词背后的三层防护
从 opencode_search_agent_prompt.md 出发,可以完整梳理出 openinterpreter 中搜索子代理的三层防护设计:
- 提示词层(本文主体文档):声明"文件搜索专家"角色,规定 Glob/Grep/Read 的分工、thoroughness 由调用方指定、结果用绝对路径、禁止任何状态变更;
- 工具层(
build_search_agent_tools):只保留bash/glob/grep/read/webfetch五个工具,从能力面上移除一切写文件与递归派发工具,使提示词的只读约束具备硬保障; - 上下文层(
build_system_prompt/build_messages):搜索会话不注入 skills 技能块、用户消息不做引号化改写,让子代理拿到的是干净的搜索任务描述。
三者共同保证了:主代理在需要"多轮 globbing 和 grepping"的开放式搜索时,可以安全地把任务外包给一个上下文独立、只能读、必须回报绝对路径结论的子代理,而搜索结果通过 task_id 支持会话级续接。对于希望在自己的 coding agent 中实现"只读探索子代理"的读者,这条"提示词身份指纹 + 工具面白名单 + 上下文裁剪"的链路是一个可直接参照的完整实现样本。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00