指令从哪来 · AGENTS.md、系统提示词与协作模式

请求里的“系统提示词”只有基础指令一段,线程创建时定下,此后不变;开发者指令、权限说明、协作模式放在 developer 消息里,AGENTS.md 与环境信息放在 user 消息里,组织托管的开发者指令单独成条。AGENTS.md 由全局、线程与项目三部分拼成,全局部分每次请求前都重读,项目部分按执行环境与信任级别缓存。

作者 David更新于 第 17 篇(共 57 篇)

指令从哪来 · AGENTS.md、系统提示词与协作模式

上下文与历史讲了注入的上下文怎么“只追加”,这一篇换个角度:一次请求里的各种指令分别从哪里来、落在请求的哪个位置、什么时候会重新加载。

用户看到的样子

能影响指令的入口不少:项目与 CODEX_HOME 里的 AGENTS.md(发现规则、大小上限、备用文件名见 AGENTS.md 项目指令),配置里的 developer_instructions、官方明确不推荐的 model_instructions_file,组织在 requirements.toml 里下发的 additional_developer_instructions,以及 /plan 或 Shift+Tab 切换的协作模式。通过 app-server 起线程时,thread/start 也能直接带 baseInstructions 与 developerInstructions。

一次请求里的指令布局

图表加载中…

这张图对应 build_initial_context_with_world_state(codex-rs/core/src/session/mod.rs:4246)拼出的初始上下文。要点是:真正意义上的“系统提示词”只有基础指令这一段;其余指令都是历史开头的普通消息,按 developer 与 user 两种角色分组。对启用了 Responses Lite 的模型(随附目录里除 gpt-5.5 外都是),连基础指令也不放 instructions 字段,而是作为一条 developer 消息插到输入最前面(见模型客户端)。

基础指令:线程创建时定下

        // Resolve base instructions for the session. Priority order:
        // 1. config.base_instructions override
        // 2. conversation history => session_meta.base_instructions
        // 3. rendered instructions_template for current model
        // ...
        let base_instructions = config
            .base_instructions
            .clone()
            .or_else(|| conversation_history.get_base_instructions().map(|s| s.text))
            .unwrap_or_else(|| render_model_instructions(&model_info));

(codex-rs/core/src/session/mod.rs:707)

三个来源按优先级排:

  1. 显式覆盖:thread/start 的 baseInstructions,或配置里的 model_instructions_file(读文件)与顶层 instructions,来源会被标记为自定义。
  2. 会话元数据:恢复或分叉的线程沿用当初写进元数据的那一份,即使期间模型目录更新了。
  3. 模型目录:render_model_instructions 取模型元数据里的 model_messages.instructions_template。随附目录里每个模型都自带一份,长度在一万七千到两万一千多字符之间;目录里查不到的模型用回退元数据,模板是 codex-rs/models-manager/prompt.md(开头写着“You are a coding agent running in the Codex CLI”)。

模型目录还能影响模板本身:personality = "none" 会删掉模板里的 # Personality 一节。发请求时还有一步只作用于请求副本的加工:计划工具 update_plan 默认关闭(tools.update_plan.enabled),基础指令来自模型目录时,模板里讲它的几节会被 without_update_plan_instructions 去掉,但存下来、会被分叉继承的原文不变。基础指令一旦定下就不再改,中途换模型靠追加 <model_switch> 消息表达,原因见上一篇。

AGENTS.md:三部分拼成一条 user 消息

LoadedAgentsMd(codex-rs/core/src/agents_md.rs:301)由三部分按顺序拼接:

  • 全局:CodexHomeUserInstructionsProvider(codex-rs/codex-home/src/instructions/mod.rs)在 CODEX_HOME 下先找 AGENTS.override.md 再找 AGENTS.md,取第一个去掉首尾空白后非空的文件。
  • 线程级:宿主通过扩展接口提供的指令,超过 10,000 个估算 token 直接拒绝,不做截断。
  • 项目:对每个执行环境,从当前目录往上找项目根,再从根走回当前目录,每一级按候选文件名找第一个存在的文件。

候选文件名的顺序是:

fn candidate_filenames<'a>(config: &'a Config, cwd: &PathUri) -> Vec<&'a str> {
    let mut names: Vec<&str> = Vec::with_capacity(2 + config.project_doc_fallback_filenames.len());
    names.push(LOCAL_AGENTS_MD_FILENAME);
    names.push(DEFAULT_AGENTS_MD_FILENAME);
    for candidate in &config.project_doc_fallback_filenames {
        let candidate = candidate.as_str();
        if candidate.is_empty() {
            continue;
        }
        // Use the executor's path convention, not the host's: resolving a Windows
        // network path can send ambient credentials even during metadata probes.
        if matches!(candidate, "." | "..")
            || candidate.contains(['/', '\0'])
            || cwd.infer_path_convention() == Some(PathConvention::Windows)
                && candidate.contains(['\\', ':'])
        {
            tracing::warn!("ignoring project_doc_fallback_filenames entry that is not a filename");
            continue;
        }
        if !names.contains(&candidate) {
            names.push(candidate);
        }
    }
    names
}

(codex-rs/core/src/agents_md.rs:272)

项目根由 project_root_markers 判定,默认是 .git;读这个配置时特意跳过了项目级配置层,项目自己的配置文件改不了“哪里算根”。项目部分受 project_doc_max_bytes 约束(默认 32 KiB),预算跨所有执行环境累计,超出的文件被截断;未受信任的项目不加载任何项目级文件。拼接时,从全局或线程部分过渡到项目部分,中间插一行 --- project-doc ---;多个执行环境各自贡献了文件时,每组前面标上 “for <环境 ID> with root …”。

拼好的文本装进 UserInstructions 片段,角色是 user,渲染成 # AGENTS.md instructions for <目录> 开头、正文包在 <INSTRUCTIONS> 标签里的一条消息,和环境上下文合在同一条 user 消息里。

什么时候重新读

AgentsMdManager::refresh 不止在会话启动时调用:每次捕获 step(也就是每次采样请求之前)都会调用一次。

        let (mut instructions, cached, refresh_repository) = {
            let mut state = self.state.lock().await;
            let refresh_repository = state.cache.selections.as_ref() != Some(&selections)
                || state.cache.active_project_trust_level != active_project_trust_level;
            if refresh_repository {
                // Tightened read permissions must not leave inaccessible instructions visible,
                // even if discovery fails or the caller cancels the refresh.
                state.cache = AgentsMdCache::default();
            }
            (
                state.instructions.clone(),
                state.cache.loaded.clone(),
                refresh_repository,
            )
        };
        // ...
        if let Some(provider) = &instructions.user_provider {
            let loaded = provider.load_user_instructions().await;
            instructions.user = normalize_instructions(loaded.instructions);
            warnings.extend(loaded.warnings);
        }

(codex-rs/core/src/agents_md_manager.rs:84)

项目部分只在执行环境的选择(包括工作目录)或项目信任级别变了时才重新扫描,否则用缓存;全局部分由提供者负责,CodexHomeUserInstructionsProvider 每次都重新读文件。读到的内容一变,世界状态的 AGENTS.md 分区就追加一条替换声明(见上一篇)。子代理不自己读全局文件,而是继承父线程已经生效的那一份。

《Codex 中文手册》依据官方文档写着 AGENTS.md “每次运行构建一次”。按 0.158.0 的代码,根线程在每次请求前都会重读 CODEX_HOME 下的全局文件,改动会在下一次请求时以一条替换声明的形式生效;项目里的文件则要等工作目录、执行环境或信任级别变化后才会重新扫描。

开发者指令与托管指令

developer_instructions(配置或 thread/start 的 developerInstructions)渲染成 DeveloperInstructions 片段,放在初始上下文那条 developer 消息里,没有标记。唯一的例外是 Guardian 审查子代理:它的开发者指令被包成 GuardianPolicy,单独成条,便于审计。

组织托管的 additional_developer_instructions 来自 requirements.toml,由世界状态里的 ManagedDeveloperInstructionsState 负责,渲染成 <managed_developer_instructions> 包裹的独立 developer 消息,排在初始上下文的最后。它有 10,000 个估算 token 的上限(MAX_MANAGED_DEVELOPER_INSTRUCTIONS_TOKENS),超了在加载配置时就报错;中途变更同样追加一条替换声明。

协作模式的指令

协作模式只有两种:Default 与 Plan。它们的说明文字在 codex-collaboration-mode-templates crate 的 default.md 与 plan.md 里,由 builtin_collaboration_mode_presets(codex-rs/models-manager/src/collaboration_mode_presets.rs)组装成预设:Plan 预设把推理强度设为 medium,开发者指令设为 plan.md 的全文。app-server 收到不带说明文字的协作模式时,会用预设补上。

世界状态里的 CollaborationModeState 决定最终渲染什么:模型目录若为该模式提供了覆盖文本,优先用目录里的(随附目录中 GPT-6 系列覆盖了 Default 模式的说明),否则用协作模式设置里带的说明;计划工具关闭时,还会从内置文本里去掉讲 update_plan 的段落。结果放进 <collaboration_mode> 包裹的 developer 片段。切换模式时同样只追加:Default 模式的说明第一句就是 “You are now in Default mode. Any previous instructions for other modes (e.g. Plan mode) are no longer active.”。Plan 模式在一轮里具体改变了什么,见任务类型与 Plan 模式。

和《从 LLM 到 Coding Agent》对照

系统提示与上下文注入把 prompt 分成系统提示、环境信息、项目记忆、动态附件几层,并建议“整场会话不变”的环境信息可以拼进系统提示。Codex 更激进:系统提示只放基础指令,环境、权限、AGENTS.md 全部作为历史开头的消息注入,之后的变化再以消息追加,系统提示这一段因此几乎永远不变。对照 OpenCode 的文件工具:OpenCode 在 read 工具读到某个文件时,才把沿途目录里尚未加载的 AGENTS.md 附在输出末尾;Codex 只加载项目根到当前目录这一条路径上的文件,更深子目录里的 AGENTS.md 不会被自动带上。


上一篇:上下文压缩 · 窗口快满时怎么办 · 下一篇:模型目录与提供方 · 一个 CLI 接多家后端

本页目录