首页
/ Zed Agent Profiles 深度解析:用 Profile 控制 Zed Agent 的模型与工具集

Zed Agent Profiles 深度解析:用 Profile 控制 Zed Agent 的模型与工具集

2026-09-06 13:53:24作者:江焘钦

Agent Profiles(代理配置档案)决定了 Zed Agent 在一条对话线程(thread)中的行为方式:它使用哪个默认模型、可以调用哪些内置工具、以及哪些 MCP(Context Server)工具对模型可见。本文以仓库文档 agent-profiles.md 为主体,结合 crates/agent_settingscrates/agentassets/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.profilescrates/agent_settings/src/agent_settings.rsAgentSettings 结构体的 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 是 writedefault.json 中设置了 "default_profile": "write",同时 AgentProfileIdDefault 实现也回退到 "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": {}
      }
    }
  }
}

对比清单可以看出几个设计取向:

  1. writeask 的差异正是"写能力"write 额外启用了 edit_filewrite_fileterminaldelete_pathmove_pathcreate_directorycopy_path 等修改型工具;ask 只保留 read_filegreplist_directoryfind_path 等只读工具。
  2. ask_user 在内置 Profile 中显式为 false:注意 Profile 的工具判定是"未列出即视为关闭"(下文源码会证实),这里显式写 false 与默认行为一致,更多是文档化意图。
  3. 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 为空。
  4. skillspawn_agentsearch_webfetch 两个功能型 Profile 都开着:说明 Skill(见 skills.md)、子代理派生(见 parallel-agents.md)与联网检索被视为基础能力,而非"写"的专属能力。

三、在 UI 中配置 Profile

文档给出的操作路径是:

  1. 打开 Agent Panel 中的 Profile 选择器,点击 Configure
  2. 也可以直接在命令面板中执行 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 的 toolsenable_all_context_serverscontext_serversdefault_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"
        }
      }
    }
  }
}

文档同时提醒:providermodel 的具体取值取决于你配置的 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_serverscontext_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)
}

其优先级可以归纳为三层:

  1. 工具级:若 context_servers[<server>].tools[<tool>] 显式给出 true/false,直接采用该值;
  2. 服务器总开关:未显式给出时,回退到 enable_all_context_servers
  3. 兜底:总开关缺省时按 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.rsset_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 可以看出三点:

  1. 只有当 Profile 配置了 default_model 且该模型在已配置的 provider 中可解析时,线程才会切换模型;未配置则保持当前模型;
  2. Profile 的模型偏好会级联到该线程正在运行的所有子代理running_subagents),保证主线程与子代理的 Profile 一致;
  3. set_profile 还会清除"受限工作区自动降级"标记(见下节),即用户的显式选择总是优先于自动降级。

这与文档"configure a profile default model"的能力描述完全对应:例如你可以让 ask Profile 固定使用某个快速低成本的模型,而 write Profile 使用能力更强的模型,切换 Profile 即切换"人格 + 工具 + 模型"的组合。

六、源码视角:Profile 如何过滤每轮请求的工具集

文档说"如果工具在激活的 Profile 中不可用,Zed Agent 无法使用它"。这句话在 thread.rsenabled_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 生效时的完整过滤链,一个工具要出现在发给模型的工具列表里,必须同时满足:

  1. 工具本身注册在线程中self.tools);
  2. 工作区不受限或工具声明支持受限模式is_restricted / tool.allow_in_restricted_mode());
  3. 工具兼容当前模型的 providersupports_provider);
  4. 当前 Profile 显式启用了它profile.is_tool_enabled);
  5. Feature flag 未关闭该工具tool_feature_flag_enabled)。

对 MCP 工具则额外走 is_context_server_tool_enabled 的三层判定(第五节)。过滤结果随后进入 LanguageModelRequesttools 字段(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_defaultagent_profile.rs)通过比对合并后设置与出厂默认设置中该 Profile 的内容是否完全相等,来判断用户是否"动过"某个内置 Profile,并有单元测试 unmodified_default_detection 验证:用户一旦在设置中给 write Profile 添加任何字段(如 "tools": {"fetch": false}),它就不再是"未修改默认",自动降级随之失效。这体现了一个原则:自动安全降级绝不覆盖用户的显式意图——如果你基于 ask fork 了一个受限 Profile,受限工作区不会偷偷把它换成 minimal

八、实践建议:何时用 Profile、何时用 Tool Permissions

结合文档的对照表与源码实现,可以给出清晰的选型原则:

  1. 想让模型"完全看不见"某类能力 → 用 Profile 的 tools 开关。工具不进入请求的工具列表,模型不会尝试调用,也没有审批弹窗打扰你。典型场景:给纯问答线程切到 ask,或自造一个 readonly-review Profile,只保留 read_filegreplist_directoryfind_referencesdiagnostics
  2. 想让某类调用"保留但每次过问"或"部分放行" → 用 Tool Permissions。例如 Profile 里保留 terminal,但用权限规则对 rm 类命令 always_confirm、对 git status 类只读命令 always_allow
  3. MCP 工具按"副作用面"分层管控:默认信任的服务器交给 enable_all_context_servers: true;有写副作用的服务器在 context_servers 下逐工具白名单。
  4. 不同工作流绑定不同模型:在 Profile 的 default_model 中固定模型,切换 Profile 即完成"模型 + 工具集"的一键切换;未配置 default_model 的 Profile 不影响当前模型。
  5. 内置 Profile 是安全的"出厂预设":直接修改内置 Profile 会写进你的设置并使其脱离"未修改默认"状态(从而关闭受限工作区的自动降级保护);如果只想局部微调,建议 fork 出自定义 Profile 再改,保留内置项的默认行为与自动保护。

九、小结

Zed 的 Agent Profiles 是一套"能力面"配置:它通过 agent.profiles 下的声明式配置,为每条线程预先圈定默认模型、内置工具白名单与 MCP 工具的三层可见性;在请求构建时由 enabled_tools 逐工具裁决,并联动系统提示词模板与子代理。它与 Tool Permissions 构成"可用性 × 授权"的正交双闸:前者决定工具是否存在,后者决定已存在的工具调用何时需要审批。源码层面(crates/agent_settings/src/agent_profile.rscrates/agent/src/thread.rs)与默认配置(assets/settings/default.json)共同保证了这套机制的可预测性:白名单语义、显式值覆盖总开关、以及"自动降级不覆盖用户意图",都是可以直接依赖的行为契约。

登录后查看全文
热门项目推荐
相关项目推荐