Zed Agent Profiles 深度解析:用 Profile 控制 Zed Agent 的模型与工具集
Agent Profiles(代理配置档案)决定了 Zed Agent 在一条对话线程(thread)中的行为方式:它使用哪个默认模型、可以调用哪些内置工具、以及哪些 MCP(Context Server)工具对模型可见。本文以仓库文档 agent-profiles.md 为主体,结合 crates/agent_settings、crates/agent 与 assets/settings/default.json 中的真实实现,完整讲解 Profile 的配置语法、内置 Profile 的工具清单、创建与 fork 机制,以及 Profile 与工具权限(Tool Permissions)的分工边界,并剖析 Profile 在每轮模型请求中是如何把"工具可用性"落实到工具注册表与系统提示词的。
读完本文,你可以:
- 通过 Agent Panel 的命令式配置或直接在设置文件中声明自定义 Profile;
- 理解内置
write/ask/minimal三个 Profile 的确切工具清单与默认模型行为; - 掌握 MCP 工具在 Profile 中的三层开关逻辑(全局总开关、按服务器开关、按工具开关);
- 从源码层面理解 Profile 如何影响每一次 LLM 请求的可用工具集。
一、Profile 定位:它控制什么,不控制什么
官方文档对 Profile 的定义非常明确:
Agent profiles control how the Zed Agent behaves in a thread. A profile can set a default model and choose which built-in tools and MCP tools are available.
Profiles do not decide whether a tool call is allowed automatically. Use Tool Permissions to control allow, deny, and confirm behavior.
即 Profile 只回答一个问题:某个工具是否存在于当前线程的工具集合中。至于"工具存在了,但这次调用要不要先征求用户同意",那是 Tool Permissions 的职责。文档用一张对照表说明了二者的分工:
| 设置项 | 控制内容 | 示例 |
|---|---|---|
| Agent profile | 某个工具在 Profile 中是否可用 | 在只读 Profile 中禁用 terminal |
| Tool permissions | 带权限门槛的工具调用是允许、拒绝还是需确认 | 总是确认 terminal 命令 |
由此可以推出两条运行时规则(文档原文):
- 如果一个工具在当前激活的 Profile 中不可用,Zed Agent 根本不会把它交给模型——模型甚至"看不见"这个工具;
- 如果工具可用且带权限门槛,Tool Permissions 仍会决定该次调用是否需要审批。
从源码结构看,这一分工在类型上就是隔离的:Profile 的配置落在 AgentSettings.profiles(crates/agent_settings/src/agent_settings.rs 中 AgentSettings 结构体的 profiles: IndexMap<AgentProfileId, AgentProfileSettings> 字段),而权限规则落在独立的 ToolPermissions / SandboxPermissions 字段中,两者互不引用。
适用范围边界
文档最后专门强调了 Agent Path Boundaries:
Agent profiles apply to the Zed Agent. External Agents and Terminal Threads do not use Zed Agent profiles unless their integration explicitly supports similar behavior.
也就是说 Profile 只作用于 Zed 内置 Agent;外部 Agent(External Agents,见 external-agents.md)和 Terminal Threads(见 terminal-threads.md)不使用 Profile,除非其集成方自行实现了类似机制。
二、三个内置 Profile
Zed 内置了三个 Profile,其 id 常量定义在 agent_profile.rs:
pub mod builtin_profiles {
pub const WRITE: &str = "write";
pub const ASK: &str = "ask";
pub const MINIMAL: &str = "minimal";
pub fn is_builtin(profile_id: &AgentProfileId) -> bool {
profile_id.as_str() == WRITE || profile_id.as_str() == ASK || profile_id.as_str() == MINIMAL
}
}
文档对三者的描述是:
Write:启用读取、编辑、执行命令等工具,是全功能写作/修改型 Profile;Ask:聚焦于对代码库的只读问答;Minimal:不使用任何项目工具。
默认激活的 Profile 是 write:default.json 中设置了 "default_profile": "write",同时 AgentProfileId 的 Default 实现也回退到 "write"(见 agent_settings.rs)。
内置 Profile 的完整工具清单
仓库的默认配置 assets/settings/default.json 给出了三个 Profile 的权威工具清单,比文档的一句话描述要具体得多:
{
"agent": {
"default_profile": "write",
"profiles": {
"write": {
"name": "Write",
"enable_all_context_servers": true,
"tools": {
"copy_path": true,
"create_directory": true,
"create_thread": true,
"delete_path": true,
"diagnostics": true,
"apply_code_action": true,
"ask_user": false,
"edit_file": true,
"write_file": true,
"fetch": true,
"find_path": true,
"find_references": true,
"get_code_actions": true,
"go_to_definition": true,
"list_agents_and_models": true,
"list_directory": true,
"move_path": true,
"rename_symbol": true,
"read_file": true,
"grep": true,
"skill": true,
"spawn_agent": true,
"terminal": true,
"search_web": true
}
},
"ask": {
"name": "Ask",
// 由于无法确定哪些 context server 工具对只读场景安全,
// Ask 默认不启用全部 context servers。
"tools": {
"create_thread": true,
"diagnostics": true,
"ask_user": false,
"fetch": true,
"list_agents_and_models": true,
"list_directory": true,
"find_path": true,
"find_references": true,
"get_code_actions": true,
"go_to_definition": true,
"read_file": true,
"grep": true,
"skill": true,
"spawn_agent": true,
"search_web": true
}
},
"minimal": {
"name": "Minimal",
"enable_all_context_servers": false,
"tools": {}
}
}
}
}
对比清单可以看出几个设计取向:
write与ask的差异正是"写能力":write额外启用了edit_file、write_file、terminal、delete_path、move_path、create_directory、copy_path等修改型工具;ask只保留read_file、grep、list_directory、find_path等只读工具。ask_user在内置 Profile 中显式为false:注意 Profile 的工具判定是"未列出即视为关闭"(下文源码会证实),这里显式写false与默认行为一致,更多是文档化意图。- MCP 总开关的差异:
write设置了"enable_all_context_servers": true,即默认放行所有 MCP 服务器下的工具;ask中该行被注释掉,default.json 的注释解释了原因——"We don't know which of the context server tools are safe for the Ask profile",因此 Ask 下 MCP 工具默认关闭、需逐个显式开启;minimal则直接关闭总开关且tools为空。 skill、spawn_agent、search_web、fetch两个功能型 Profile 都开着:说明 Skill(见 skills.md)、子代理派生(见 parallel-agents.md)与联网检索被视为基础能力,而非"写"的专属能力。
三、在 UI 中配置 Profile
文档给出的操作路径是:
- 打开 Agent Panel 中的 Profile 选择器,点击
Configure; - 也可以直接在命令面板中执行
agent::ManageProfiles动作。
对应地,仓库中确实存在该动作的完整实现链:
- 动作定义:
ManageProfiles结构体定义在 agent_ui.rs; - 入口注册:Agent Panel 的弹出菜单中注册了
"Profiles"项(agent_panel.rs),Profile 选择器本身在 profile_selector.rs 中也提供ManageProfiles入口; - 管理弹窗:
ManageProfilesModal实现在 manage_profiles_modal.rs,它通过workspace.register_action全局注册了该动作。
从该弹窗你可以:
- 创建自定义 Profile(create a custom profile);
- fork 一个已有 Profile(fork an existing profile);
- 配置 Profile 的默认模型(configure a profile default model);
- 配置内置工具开关(configure built-in tools);
- 配置 MCP 工具开关(configure MCP tools);
- 删除自定义 Profile(delete custom profiles)。
其中"fork"行为在源码 agent_profile.rs 中有直接印证。AgentProfile::create 接收一个可选的 base_profile_id:
pub fn create(
name: String,
base_profile_id: Option<AgentProfileId>,
fs: Arc<dyn Fs>,
cx: &App,
) -> AgentProfileId {
let id = AgentProfileId(name.to_case(Case::Kebab).into());
// ...
// Copy toggles from the base profile so the new profile starts with familiar defaults.
let tools = base_profile
.as_ref()
.map(|profile| profile.tools.clone())
.unwrap_or_default();
let enable_all_context_servers = /* 复制自 base profile */;
let context_servers = /* 复制自 base profile */;
// Preserve the base profile's model preference when cloning into a new profile.
let default_model = base_profile
.as_ref()
.and_then(|profile| profile.default_model.clone());
// ... 通过 update_settings_file 写入设置文件
}
两个值得注意的实现细节:
- 新 Profile 的 id 是名称的 kebab-case 形式(
name.to_case(Case::Kebab)),所以名为Review Helper的 Profile 会落成"review-helper"键; - fork 会完整继承基础 Profile 的
tools、enable_all_context_servers、context_servers和default_model四项,保证新 Profile"从熟悉的默认值起步"。
四、在设置文件中声明 Profile
文档说明:Profile 存储于设置的 agent.profiles 键下,并给出如下示例:
{
"agent": {
"profiles": {
"ask": {
"name": "Ask",
"tools": {
"read_file": true,
"grep": true,
"terminal": false,
"edit_file": false
},
"enable_all_context_servers": false,
"context_servers": {},
"default_model": {
"provider": "zed.dev",
"model": "claude-sonnet-4-5"
}
}
}
}
}
文档同时提醒:provider 与 model 的具体取值取决于你配置的 LLM Providers,上例中的 "zed.dev" / "claude-sonnet-4-5" 只是示例。
字段级说明
对照设置内容的 Schema 定义 AgentProfileContent 与运行时结构 AgentProfileSettings,各字段含义如下:
| 字段 | 类型 | 说明 |
|---|---|---|
name |
string(必填) |
Profile 在 UI 中显示的名称。 |
tools |
map<string, bool> |
内置工具的显式开关表;键不存在与值为 false 等效,都视为不可用(见下文 is_tool_enabled)。 |
enable_all_context_servers |
bool(可选,默认 false) |
MCP(context server)工具的总开关;Some(false) 反序列化时按 false 处理。 |
context_servers |
map<string, {tools: map}> |
按 MCP 服务器 id 的细粒度预设,可覆盖总开关对单个工具的判定。 |
default_model |
{provider, model, ...}(可选) |
激活该 Profile 时自动切换的语言模型,结构为 LanguageModelSelection。 |
enable_all_context_servers 的可选性由 AgentProfileContent 上的 Option<bool> 类型和 From 实现 中的 unwrap_or_default() 共同保证:不写该字段时按 false 处理。
工具开关的判定逻辑
tools 表的判定函数只有一行,但它是理解整张表的钥匙(agent_profile.rs):
pub fn is_tool_enabled(&self, tool_name: &str) -> bool {
self.tools.get(tool_name) == Some(&true)
}
即白名单语义:只有显式写成 true 的工具才可用,false 与"未列出"完全等效。这与文档示例中 "terminal": false, "edit_file": false 的写法是一致的——写成 false 是显式声明意图,不写效果相同。
MCP 工具的三层开关
MCP 工具的可用性由 enable_all_context_servers 与 context_servers 两个字段联合决定,判定函数为 is_context_server_tool_enabled:
pub fn is_context_server_tool_enabled(&self, server_id: &str, tool_name: &str) -> bool {
self.context_servers
.get(server_id)
.and_then(|preset| preset.tools.get(tool_name).copied())
.unwrap_or(self.enable_all_context_servers)
}
其优先级可以归纳为三层:
- 工具级:若
context_servers[<server>].tools[<tool>]显式给出true/false,直接采用该值; - 服务器总开关:未显式给出时,回退到
enable_all_context_servers; - 兜底:总开关缺省时按
false,即"未配置 = 不可用"。
这个"显式值 > 总开关"的覆盖关系由 agent_profile.rs 中的两个单元测试精确固化:explicit_false_disables_tool_when_enable_all_is_true 验证"总开关为 true 时,某个工具的显式 false 仍能把它关死";explicit_true_enables_tool_when_enable_all_is_false 验证反向情形。因此你可以放心地在 write Profile 中做如下混合配置:全局放行 MCP,但单独封禁某个有副作用的服务器工具:
{
"agent": {
"profiles": {
"my-server": {
"name": "Server",
"enable_all_context_servers": true,
"context_servers": {
"my-mcp-server": {
"tools": {
"dangerous_write_tool": false
}
}
}
}
}
}
}
五、default_model:Profile 如何驱动模型切换
Profile 上的 default_model 是"激活该 Profile 时自动应用的模型偏好"(字段注释原文,见 agent_profile.rs)。它的运行时行为在 thread.rs 的 set_profile 中:
pub fn set_profile(&mut self, profile_id: AgentProfileId, cx: &mut Context<Self>) {
// ...
self.profile_id = profile_id.clone();
// Swap to the profile's preferred model when available.
if let Some(model) = Self::resolve_profile_model(&self.profile_id, cx) {
self.set_model(model, cx);
}
for subagent in &self.running_subagents {
subagent
.update(cx, |thread, cx| thread.set_profile(profile_id.clone(), cx))
.ok();
}
}
结合 resolve_profile_model 可以看出三点:
- 只有当 Profile 配置了
default_model且该模型在已配置的 provider 中可解析时,线程才会切换模型;未配置则保持当前模型; - Profile 的模型偏好会级联到该线程正在运行的所有子代理(
running_subagents),保证主线程与子代理的 Profile 一致; set_profile还会清除"受限工作区自动降级"标记(见下节),即用户的显式选择总是优先于自动降级。
这与文档"configure a profile default model"的能力描述完全对应:例如你可以让 ask Profile 固定使用某个快速低成本的模型,而 write Profile 使用能力更强的模型,切换 Profile 即切换"人格 + 工具 + 模型"的组合。
六、源码视角:Profile 如何过滤每轮请求的工具集
文档说"如果工具在激活的 Profile 中不可用,Zed Agent 无法使用它"。这句话在 thread.rs 的 enabled_tools 中兑现——每次构建 LLM 请求前,线程都会按当前 Profile 重新计算可用工具表:
fn enabled_tools(&self, cx: &App) -> BTreeMap<SharedString, Arc<dyn AnyAgentTool>> {
let Some(model) = self.model() else { return BTreeMap::new(); };
let Some(profile) = AgentSettings::get_global(cx).profiles.get(&self.profile_id) else {
return BTreeMap::new();
};
// ...
let mut tools = self
.tools
.iter()
.filter(|(_, tool)| !is_restricted || tool.allow_in_restricted_mode())
.filter_map(|(tool_name, tool)| {
// 沙箱版 terminal 与 plain terminal 在 profile 中统一以 "terminal" 判定
let profile_tool_name = if terminal_variant {
TerminalTool::NAME
} else {
tool_name.as_ref()
};
if tool.supports_provider(&model.provider_id())
&& profile.is_tool_enabled(profile_tool_name)
{
/* 按沙箱状态把 terminal 变体统一暴露为 "terminal" */
Some(...)
} else {
None
}
})
.filter(|(tool_name, _)| crate::tools::tool_feature_flag_enabled(tool_name, cx))
.collect();
// ... context server 工具再按 is_context_server_tool_enabled 过滤
}
由此得到 Profile 生效时的完整过滤链,一个工具要出现在发给模型的工具列表里,必须同时满足:
- 工具本身注册在线程中(
self.tools); - 工作区不受限或工具声明支持受限模式(
is_restricted/tool.allow_in_restricted_mode()); - 工具兼容当前模型的 provider(
supports_provider); - 当前 Profile 显式启用了它(
profile.is_tool_enabled); - Feature flag 未关闭该工具(
tool_feature_flag_enabled)。
对 MCP 工具则额外走 is_context_server_tool_enabled 的三层判定(第五节)。过滤结果随后进入 LanguageModelRequest 的 tools 字段(build_request),并且 available_tools 名单还会被渲染进系统提示词模板 system_prompt.hbs——模板中 {{#if (contains available_tools 'grep')}}、{{#if (contains available_tools 'terminal')}} 等条件块会根据名单动态增删提示词段落。这意味着 Profile 不仅裁剪工具列表,还会改变模型看到的"使用说明",两者一致地缩小模型的能力认知。
另一个实现细节:沙箱化终端与普通终端在 Profile 层面统一以 terminal 名义判定(profile_tool_name 归一化),用户只需在设置里维护一个 terminal 开关,运行时按项目的沙箱启用状态自动选用对应的工具变体。
七、受限工作区下的自动降级
源码中还有一处与 Profile 强相关、但文档未展开的安全机制:受限工作区(restricted workspace)会自动降级 Profile。profile_for_restricted_workspace 的注释与逻辑是:
/// Computes the profile a thread should start with, given the user's chosen
/// profile. In a restricted workspace, the built-in `write`/`ask` profiles
/// are downgraded to `minimal` — but only when both the chosen profile and
/// `minimal` are unmodified, shipped defaults, so we never override a user's
/// custom or customized profiles.
fn profile_for_restricted_workspace(
profile_id: AgentProfileId,
project: &Entity<Project>,
cx: &App,
) -> (AgentProfileId, bool) {
let is_write_or_ask = profile_id.as_str() == builtin_profiles::WRITE
|| profile_id.as_str() == builtin_profiles::ASK;
// 仅当所选 Profile 与 minimal 均为"未修改的出厂默认"时才降级
if is_write_or_ask
&& TrustedWorktrees::has_restricted_worktrees(...)
&& AgentProfileSettings::is_unmodified_default(&profile_id, cx)
&& AgentProfileSettings::is_unmodified_default(&minimal, cx)
{
(minimal, true)
} else {
(profile_id, false)
}
}
其中 is_unmodified_default(agent_profile.rs)通过比对合并后设置与出厂默认设置中该 Profile 的内容是否完全相等,来判断用户是否"动过"某个内置 Profile,并有单元测试 unmodified_default_detection 验证:用户一旦在设置中给 write Profile 添加任何字段(如 "tools": {"fetch": false}),它就不再是"未修改默认",自动降级随之失效。这体现了一个原则:自动安全降级绝不覆盖用户的显式意图——如果你基于 ask fork 了一个受限 Profile,受限工作区不会偷偷把它换成 minimal。
八、实践建议:何时用 Profile、何时用 Tool Permissions
结合文档的对照表与源码实现,可以给出清晰的选型原则:
- 想让模型"完全看不见"某类能力 → 用 Profile 的
tools开关。工具不进入请求的工具列表,模型不会尝试调用,也没有审批弹窗打扰你。典型场景:给纯问答线程切到ask,或自造一个readonly-reviewProfile,只保留read_file、grep、list_directory、find_references、diagnostics。 - 想让某类调用"保留但每次过问"或"部分放行" → 用 Tool Permissions。例如 Profile 里保留
terminal,但用权限规则对rm类命令always_confirm、对git status类只读命令always_allow。 - MCP 工具按"副作用面"分层管控:默认信任的服务器交给
enable_all_context_servers: true;有写副作用的服务器在context_servers下逐工具白名单。 - 不同工作流绑定不同模型:在 Profile 的
default_model中固定模型,切换 Profile 即完成"模型 + 工具集"的一键切换;未配置default_model的 Profile 不影响当前模型。 - 内置 Profile 是安全的"出厂预设":直接修改内置 Profile 会写进你的设置并使其脱离"未修改默认"状态(从而关闭受限工作区的自动降级保护);如果只想局部微调,建议 fork 出自定义 Profile 再改,保留内置项的默认行为与自动保护。
九、小结
Zed 的 Agent Profiles 是一套"能力面"配置:它通过 agent.profiles 下的声明式配置,为每条线程预先圈定默认模型、内置工具白名单与 MCP 工具的三层可见性;在请求构建时由 enabled_tools 逐工具裁决,并联动系统提示词模板与子代理。它与 Tool Permissions 构成"可用性 × 授权"的正交双闸:前者决定工具是否存在,后者决定已存在的工具调用何时需要审批。源码层面(crates/agent_settings/src/agent_profile.rs、crates/agent/src/thread.rs)与默认配置(assets/settings/default.json)共同保证了这套机制的可预测性:白名单语义、显式值覆盖总开关、以及"自动降级不覆盖用户意图",都是可以直接依赖的行为契约。
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 StartedRust0626
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