系统提示词、上下文与提醒
每次模型请求前的那段“前缀”怎样拼出:十几个段落按静态、动态分进三条 system 消息,AGENTS.md 与记忆索引走 meta user 块;二十多种 system reminder 何时产生、落在请求哪里、是否落库;中途 system 消息投影,以及上下文用量拆解给谁看。
模型每次请求看到的消息序列,开头都是运行时拼出来的一段“前缀”:三条 system 消息,外加一两条包在 <system-reminder> 里的 user 消息;对话中间还散落着各种带来源标签的提醒。这一篇讲前缀由哪些段落组成、按什么顺序排、什么时候重建,提醒在什么条件下产生、最后落在请求的哪个位置。回合循环在哪一步调用这些逻辑,见回合循环与 TurnMachine;对话本体怎样被摘要替换,留给下一篇。
以下目录都在 apps/zcode-cli/packages 下:
| 位置 | 职责 |
|---|---|
core/src/context/ | ContextBuilder 与各段落构造器,纯函数,不碰文件系统 |
core/src/system-reminder/ | 提醒来源登记表、包装与转义、附件与中途消息的正文格式 |
core/src/runtime/methods/context*.ts | 首次初始化、每回合重建前缀、上下文用量快照 |
core/src/runtime/helpers/runtime-reminders.ts | Plan 模式、Todo、日期等运行时提醒的正文与节奏 |
core/src/runtime/helpers/provider-*.ts | 把历史投影成 provider 请求:提醒排位、中途 system 消息、缓存标记 |
adapters/src/context/ | NodeContextSourceAdapter:探测环境与 git、查找 AGENTS.md |
前缀怎样拼出来
第一次模型请求前,运行时走一次 ensureContextInitialized(apps/zcode-cli/packages/core/src/runtime/methods/context.ts:31):向 ContextSourcePort 要一份上下文源快照,顺带启动 MCP、发现技能、定位项目记忆目录并读入 MEMORY.md,然后用快照构造 ContextBuilder,把 build() 的产物装进 MessageHistory 的开头(context.ts:273)。
每个段落是一个 ContextSection,除正文外还带两个标记:注入位置 injectionTarget(system 或 meta_user)与缓存提示 cacheHint(stable 或 dynamic)(apps/zcode-cli/packages/core/src/context/types.ts:52)。build() 收齐段落后,先按“system 稳定、system 动态、meta user 稳定、meta user 动态”重排(apps/zcode-cli/packages/core/src/context/builder.ts:310),再拼成三条 system 消息,每条都带 ephemeral 缓存标记(builder.ts:230):
private assembleSystemMessages(sections: ContextSection[]): ModelInputMessage[] {
const messages: ModelInputMessage[] = [];
const cliPrefixContent = buildSectionContent(
sections.filter(
(section) => section.injectionTarget === "system" && section.source === "cli_prefix",
),
);
if (cliPrefixContent) {
messages.push({
role: "system",
content: cliPrefixContent,
cacheControl: EPHEMERAL_CACHE_CONTROL,
});
}
// ...
const dynamicSystemContent = buildSectionContent(
sections.filter(
(section) => section.injectionTarget === "system" && section.cacheHint === "dynamic",
),
);
if (dynamicSystemContent) {
messages.push({
role: "system",
// ZCode by design:Main Agent 的 dynamic system block 自带左边界,所有 provider 保持一致。
content: `\n\n${dynamicSystemContent}`,
cacheControl: EPHEMERAL_CACHE_CONTROL,
});
}省略的中间一条收的是除 CLI 前缀以外的全部稳定段(builder.ts:246)。meta user 段拼成两条附件:技能清单单独一条,来源 skills_listing;其余段落合成一条,来源 context_prefix,首尾各加一句固定的话(builder.ts:327),开头一句是:
As you answer the user's questions, you can use the following context:
附件在历史里以 kind: "attachment" 存放(apps/zcode-cli/packages/core/src/runtime/methods/context-history-entries.ts:7),发请求时才包进 <system-reminder> 标签。历史开头哪些条目算“前缀”,由 countContextPrefixMessages 判定:连续的 system 消息,加上来源为 context_prefix 或 skills_listing 的附件(apps/zcode-cli/packages/core/src/agent/message-history.ts:269)。重建前缀、压缩、回退都靠它把“前缀”和“对话”切开。
git 快照那一段还有一层约束:代码注释说 git status 只按 2000 字符截断、不按条目数截断,是为了“与上游 CLI 保持一致”,避免 provider 可见的提示词形状漂移(apps/zcode-cli/packages/adapters/src/context/git-snapshot.ts:168)。至少这部分提示词的形状是有意对齐某个上游 CLI 的,代码没有点名是哪一个。
段落清单
按 build() 的构造顺序(重排后次序不变):
| 次序 | 段落(source) | 注入与缓存 | 出现条件 | 内容要点 |
|---|---|---|---|---|
| 1 | CLI Prefix(cli_prefix) | system,稳定,独占第一条 | 不是工作流子代理(builder.ts:103) | 一句身份 |
| 2 | Agent Identity(identity) | system,稳定 | 默认(builder.ts:121) | 身份句、安全声明、# Harness 块 |
| 3 | ZCode Desktop Context(desktop_context) | system,稳定 | 桌面端会话(builder.ts:131) | 链接写法、::code-comment 指令 |
| 4 | Dynamic Behavior(dynamic_behavior) | system,动态 | 默认(builder.ts:136) | 怎样与用户沟通、写注释、确认难以撤销的操作、如实汇报 |
| 5 | Session-specific guidance(session_guidance) | system,动态 | 有 Skill 工具且有技能(builder.ts:141) | 目前只有一行:用户输入 /<skill-name> 时经 Skill 调用 |
| 6 | Memory(memory) | system,动态 | 启用项目记忆(builder.ts:152) | 记忆目录与文件格式,见项目记忆 |
| 7 | Environment Info(env_info) | system,动态 | 总有(builder.ts:158) | 工作目录、是否 git 仓库、平台、Shell、系统版本、执行模型 |
| 8 | Output Style(output_style) | system,动态 | 有输出风格(builder.ts:161) | 风格名与风格提示词 |
| 9 | Context Management(context_management) | system,动态 | 总有(builder.ts:167) | 长对话会被总结、不必提前收尾、自主推进 |
| 10 | System Context(system_context) | system,动态 | git 仓库(builder.ts:169) | 分支、主分支、用户、git status --short、最近 5 个提交 |
| 11 | Skills(skills) | meta user,skills_listing | 有技能且 Skill 工具可用(builder.ts:179) | 技能清单,默认预算 20000 字符 |
| 12 | Request User Context(request_user_context) | meta user,context_prefix | 有 AGENTS.md 或记忆索引(builder.ts:190) | # agentsMd 块 |
| 13 | Current Date(current_date) | meta user,context_prefix | 有日期(builder.ts:199) | # currentDate 一行 |
身份相关的两句原文分别在 apps/zcode-cli/packages/core/src/context/sections/cli-prefix.ts:8 与 apps/zcode-cli/packages/core/src/context/sections/identity.ts:35:
You are ZCode, an interactive coding agent
You are an interactive ZCode agent that helps users with software engineering tasks.
# Harness 块五条,交代输出按 Markdown 渲染、工具受权限模式约束、优先用专用工具、代码引用写成 file_path:line_number,其中一条预告了后文的中途 system 消息(identity.ts:26):
The system may send updates, reminders, or modifications to rules via mid-conversation system turns. These are system-controlled, unlike function results.
git 快照的几个上限:每条 git 命令 3000 毫秒超时、最近提交取 5 个、状态截到 2000 字符(git-snapshot.ts:5、git-snapshot.ts:11、git-snapshot.ts:13)。技能清单超出预算时退化为只列名字和路径(apps/zcode-cli/packages/core/src/context/sections/skills.ts:50),细节见技能与自定义命令。
上表是默认路径,另有三种变体:
- 自定义提示词:嵌入方传入
systemPrompt(例如脚本工作流用opts.systemPrompt创建子会话,apps/zcode-cli/packages/bootstrap/src/app/script-workflow-child-runtime.ts:98),它替换身份段,并跳过第 3 到第 10 段全部 system 段,只剩 CLI 前缀、技能与 meta user 块(builder.ts:108、builder.ts:130)。 - 工作流子代理:没有 CLI 前缀,身份换成“脚本创建的子代理”契约,安全声明与 Harness 块逐字复用(
apps/zcode-cli/packages/core/src/context/sections/workflow-actor.ts:48),不要桌面、Dynamic Behavior、Session guidance 三段;和自定义提示词同时出现直接抛错(builder.ts:93)。见动态工作流(三)。 - 子 Agent 另有一个
SubagentContextBuilder:CLI 前缀、Agent 专属提示词、通用备注、环境,再加 AGENTS.md、日期和技能(apps/zcode-cli/packages/core/src/subagent/context-builder.ts:106),见子 Agent。
有几样东西传进了 builder 却不进提示词。ContextBuilderConfig 的 language、projectContext、agentProfiles、embeddedSearchEnabled、compact 照传不误(context.ts:122),builder 与段落构造器都不读:适配器探测出的项目类型、包管理器与 scripts(apps/zcode-cli/packages/adapters/src/context/index.ts:250)不会让模型看到。工具说明也不再镜像进系统提示词,由请求的 tools 字段承载(builder.ts:47)。
输出风格在 core 里是一整套:第 8 段、身份句的替换(identity.ts:33)、每回合首步的 output_style 提醒。但仓库里 core 之外没有任何地方设置 outputStyle,插件清单里的 outputStyles 也只做诊断(apps/zcode-cli/packages/bootstrap/src/zcode-protocol/plugins.ts:481)。从代码看,这条链路目前不会被触发。
指令文件:只认 AGENTS.md
NodeContextSourceAdapter 查找指令文件的默认文件名只有 AGENTS.md,单个文件最多读 100 KiB(apps/zcode-cli/packages/adapters/src/context/index.ts:24)。候选只有两个,用户级在前、工作区在后(index.ts:99):
const priorityFiles = options.priorityFiles ?? DEFAULT_PRIORITY_FILES;
const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
const projectRoot = options.projectRoot ?? (await findProjectRoot(options.workingDirectory));
const defaultUserInstructionFile = await findDefaultUserInstructionFile(priorityFiles, env);
const workspaceInstructionFile = await findInstructionFile(
options.workingDirectory,
projectRoot,
priorityFiles,
);
const candidates = dedupeInstructionFileCandidates([
defaultUserInstructionFile
? { ...defaultUserInstructionFile, scope: "user" as const }
: undefined,
workspaceInstructionFile
? { ...workspaceInstructionFile, scope: "workspace" as const }
: undefined,
]);- 用户级:
~/.zcode/AGENTS.md,主目录取环境变量HOME或USERPROFILE,都没有才用os.homedir()(index.ts:229、index.ts:245)。 - 工作区级:从工作目录逐级向上,第一个含
AGENTS.md的目录即止,最远到 git 根(向上第一个有.git的目录,index.ts:320);不在 git 仓库里就一直找到文件系统根(index.ts:219)。所以只取离工作目录最近的那一份,子目录与仓库根的两份不会叠加。 - 读取与合并:两个候选按绝对路径去重后依次读(
index.ts:142),超过 100 KiB 截断并记下truncated,读失败只记一条诊断(index.ts:159)。渲染时每份单独成节,标题是Contents of <路径> (user default instructions):或(workspace instructions):,截断的文件末尾补一行[File truncated: AGENTS.md](apps/zcode-cli/packages/core/src/context/sections/request-user-context.ts:116)。项目记忆的MEMORY.md索引接在后面,整块以# agentsMd开头(request-user-context.ts:63):
Codebase and user instructions are shown below. Be sure to adhere to these instructions. IMPORTANT: These instructions OVERRIDE any default behavior and you MUST follow them exactly as written.
代码里没有读取 CLAUDE.md 的地方。桌面端的新手引导提供一次迁移,把 ~/.claude/CLAUDE.md 复制成 ~/.zcode/AGENTS.md,目标已存在时默认跳过(packages/services/src/settings-sync/settingsSyncService.ts:2221)。子 Agent 默认继承父会话已解析的指令快照,配置里写了 injectAgentsMd: false 才不带(apps/zcode-cli/packages/core/src/runtime/methods/subagent.ts:96)。
前缀什么时候重建
上下文源快照在一个运行时里只取一次(context.ts:36):AGENTS.md、git 状态、会话日期都定格在开始那一刻,中途改了 AGENTS.md 或提交了代码,模型看到的仍是旧的。冷恢复会话时运行时丢掉旧历史、重新初始化上下文(apps/zcode-cli/packages/core/src/runtime/methods/resume.ts:147),AGENTS.md 与记忆索引会重新读取、日期取当天;环境与 git 快照却不重新探测,而是沿用第一条用户消息落库时一并保存的那份(resume.ts:114、apps/zcode-cli/packages/core/src/runtime/methods/message-persistence.ts:77)。
前缀本身却每个回合都重算。首轮之后,每个回合开始都按这一步实际拿到的模型重建一次(apps/zcode-cli/packages/core/src/runtime/methods/turn.ts:215),做法是用同一份快照重新 build(),保留对话部分、只换前缀(apps/zcode-cli/packages/core/src/runtime/methods/context-refresh.ts:37):
const effectiveContextResult = runtime.contextBuilder.build();
const contextEntries = buildContextHistoryEntries(effectiveContextResult);
const canonicalEntries = runtime.messageHistory.borrowReadOnlyRuntimeEntries();
const canonicalConversationEntries = canonicalEntries.slice(
countContextPrefixMessages(canonicalEntries),
);
runtime.latestContextBuildResult = effectiveContextResult;
runtime.messageHistory.replaceMessages([...contextEntries, ...canonicalConversationEntries]);
const turnEntries = options.turnRequestEntries;
if (!turnEntries) return runtime.messageHistory.borrowReadOnlyRuntimeEntries();
return [...contextEntries, ...turnEntries.slice(countContextPrefixMessages(turnEntries))];快照不变,那每回合重建改的是什么?主要是模型:# Environment 最后一行写的是本步骤的执行模型(apps/zcode-cli/packages/core/src/context/sections/env-info.ts:77),Session guidance 依据的也是这个模型可见的工具表(context.ts:141),二者都在第三条动态 system 消息里,前两条不受影响。这就是“中途变更系统提示词”的第一种方式:直接替换历史开头的前缀。另外几处也会触发重建:空闲时改语言或输出风格(apps/zcode-cli/packages/core/src/runtime/methods/config.ts:59)、回合中途经引导换模型(apps/zcode-cli/packages/core/src/runtime/methods/turn-guide-drain.ts:45)、回退消息之后(apps/zcode-cli/packages/core/src/runtime/methods/rewind-message.ts:706)、首发前刷新 Shell(apps/zcode-cli/packages/core/src/runtime/methods/session-shell-environment.ts:225)。
日期是个例外。# currentDate 用的是快照里的日期,跨过午夜不会变;运行时改在每个回合开头比对本地日期,变了就追加一条 date_change 提醒,并要求模型不要特意向用户提起(turn.ts:848、apps/zcode-cli/packages/core/src/runtime/helpers/runtime-reminders.ts:91)。
提醒:带来源标签的附件
提醒在历史里是带 source 的附件条目(message-history.ts:59)。全部来源登记在 apps/zcode-cli/packages/core/src/system-reminder/source.ts,分三组:前缀两种(source.ts:20);可以随会话落库、冷恢复时按原来源重建的 15 种(source.ts:22);只活在进程内存历史里的 10 种(source.ts:40)。每个来源有一张描述符,记投递通道与生命周期(source.ts:88)。包装时,正文里出现的 <system-reminder 或 </system-reminder 会把起始尖括号转义成 <,防止提醒正文伪造标签边界(source.ts:233)。
回合循环每一步请求前依次检查几种运行时提醒,写进本回合的请求状态(apps/zcode-cli/packages/core/src/runtime/methods/turn-loop.ts:120):
if (!outputTokenRecoveryActive && this.needsPlanModeExitReminder) {
this.needsPlanModeExitReminder = false;
commitTurnRequestEntries(this, state.turnRequestState, [
systemReminderAttachmentEntry("plan_mode_exit", buildPlanModeExitReminderBody()),
]);
}
const runtimeModeReminderBody = outputTokenRecoveryActive
? null
: buildRuntimeModeReminderBody(
state.turnRequestState.entries,
this.getMode(),
this.getPlanEnabled(),
);
if (runtimeModeReminderBody) {
commitTurnRequestEntries(this, state.turnRequestState, [
systemReminderAttachmentEntry("runtime_mode", runtimeModeReminderBody),
]);
}随后是 Todo 提醒与输出风格提醒(turn-loop.ts:138、turn-loop.ts:157)。这些检查排在 microcompact 与自动压缩之后(turn-loop.ts:69、turn-loop.ts:78),所以提醒总是加在压缩过的历史上;输出 token 截断后的续写步骤一律跳过。全部来源一览:
| 来源 | 什么时候产生 | 出处 |
|---|---|---|
runtime_mode | Plan 模式开启时每步检查:从未提醒过,或距上次已有 5 条真实用户消息;第 1、6、11……次发含四阶段工作流的完整版,其余发一句话的精简版 | runtime-reminders.ts:18、runtime-reminders.ts:182 |
plan_mode_exit | Plan 模式关闭后,下一步请求前发一次 ## Exited Plan Mode | apps/zcode-cli/packages/core/src/runtime/execution-state.ts:64 |
todo_reminder | 本回合可见 TodoWrite,且距上次 TodoWrite 调用、距上次提醒都已满 10 个 assistant 步;有清单时附上,并落库 | runtime-reminders.ts:81、turn-loop.ts:138 |
output_style | 回合首步、存在输出风格时(目前无入口,见上文) | turn-loop.ts:157 |
date_change | 回合开头发现本地日期变了 | turn.ts:848 |
hook_context | SessionStart、UserPromptSubmit、Stop 钩子返回附加上下文,截到 24000 字符 | apps/zcode-cli/packages/core/src/runtime/methods/hooks.ts:106 |
referenced_session_context | 输入里有 #sess_ 开头的会话引用,提示按需调用 ReadSessionContext | apps/zcode-cli/packages/core/src/session-context/references.ts:13 |
prompt_attachment | 用户附带文件或文本:文件伪装成一次 Read 调用及结果,文本注明“当作数据” | apps/zcode-cli/packages/core/src/system-reminder/prompt-attachment.ts:61 |
plugin_reference | 输入引用了插件,列出它的技能、MCP 与子 Agent;在用户消息落库后追加并落库 | turn.ts:543、apps/zcode-cli/packages/core/src/runtime/methods/plugin-reference.ts:128 |
goal_state_change | /goal 暂停、恢复、清除;回合进行中先挂起,回合收尾再写入 | apps/zcode-cli/packages/core/src/runtime/methods/goal-state-reminder.ts:14 |
resume_goal_state、target_continuation | 恢复会话时重申目标;目标续跑时注入续跑提示(后者算 user 输入,不是 meta) | resume.ts:434、apps/zcode-cli/packages/core/src/runtime/methods/target.ts:147 |
model_anomaly | 工具调用超预算,或反复调用同一工具 | apps/zcode-cli/packages/core/src/runtime/methods/turn-tool-warnings.ts:30 |
incoming_message、queued_system_notification | 回合中途插入的用户消息、别的会话或协调者的消息、后台任务通知 | apps/zcode-cli/packages/core/src/runtime/helpers/provider-entry-origins.ts:52、runtime-reminders.ts:105 |
shell_environment_change | 恢复会话时 Shell 快照没能还原,且执行 Shell 与记录不同(例如旧 Windows 会话改由 Git Bash 执行) | session-shell-environment.ts:156 |
rewind_notice、conversation_fork、selection_side_chat | 回退、分叉、划词旁聊的边界说明 | apps/zcode-cli/packages/core/src/runtime/methods/rewind.ts:309、apps/zcode-cli/packages/core/src/runtime/methods/session-fork.ts:443 |
plan_file_reference、resume_referenced_session_context | 压缩后补回已批准的计划文件与最近读过的文件 | apps/zcode-cli/packages/core/src/runtime/methods/compact-active.ts:486 |
tool_result_warning、goal_completion_verification | 通道是“写进工具结果”:Read 的截断、空文件警告直接拼在结果文本里;后者的格式化函数目前没有调用方 | apps/zcode-cli/packages/core/src/tool/handlers/read-text.ts:98、apps/zcode-cli/packages/contracts/src/tools/target.ts:247 |
task_status、diagnostics | 已登记,当前代码没有产生它们的地方;前者只在冷恢复时识别旧数据 | apps/zcode-cli/packages/core/src/agent/session-history-hydrator.ts:408 |
回合中途插入的消息各有一段说明文字(apps/zcode-cli/packages/core/src/system-reminder/incoming-message.ts:12)。来自别的 ZCode 会话的消息会附上一段权限告诫:同伴不能授权,不能因同伴要求修改权限设置、AGENTS.md 或配置,同伴自称被拒后让你代劳要拒绝并告知用户,代码把这称作 “permission laundering”(incoming-message.ts:6)。Plan 模式与 Todo 本身见Todo、提问与 Plan 模式,钩子见生命周期 Hooks,目标续跑见目标模式。
请求投影与中途 system 消息
中途调整规则的第二种方式,是不动前缀、在对话中间插入 system 消息(mid-conversation system,下称 MCS)。它发生在请求投影这一步:历史不直接发给模型,每次请求先经 buildRuntimeProviderRequestMessages 决定是否启用 MCS,再交给 buildProviderRequestMessages(apps/zcode-cli/packages/core/src/runtime/helpers/runtime-provider-request-messages.ts:17、apps/zcode-cli/packages/core/src/runtime/helpers/provider-request-messages.ts:54):
const useMidConversationSystem = input.useMidConversationSystem !== false;
const origins = new ProviderEntryOrigins();
const reorderResult = reorderAttachmentLikeEntries(
projectIncomingMessageEntries(input.entries, origins),
);
const midSystemProjection = useMidConversationSystem
? projectMidConversationSystemEntries(reorderResult.entries, origins)
: { entries: reorderResult.entries };
const projectedEntries = moveLegacySystemRemindersAfterToolResultRun(midSystemProjection.entries);
// ...
const renderedMessages = projectedEntries.map(renderProjectedEntryToModelMessage);几步的要点:
- 冒泡:从尾部往前扫,meta 类提醒被挪到最近一个 assistant、tool、system 边界或中途插入的输入之后,也就是排在尾部真实用户消息的前面(
provider-request-messages.ts:106)。history_continuity通道的来源与goal_state_change不冒泡,保持因果位置(provider-request-messages.ts:44、provider-request-messages.ts:148)。 - MCS 落点:可走 MCS 的提醒先攒着,遇到下一条 assistant 或 system 才落下,落点须紧跟 user 或 tool 消息,连续几条合成一条 system 消息;回合中途插入的输入是因果边界,普通提醒不能越过它(
apps/zcode-cli/packages/core/src/runtime/helpers/provider-mid-conversation-system.ts:36、provider-mid-conversation-system.ts:79)。最后校验一遍:前面不是 user 或 tool,或者后面跟的既不是 assistant 也不是请求结尾,这样的 system 消息降级为<system-reminder>user 文本(provider-mid-conversation-system.ts:128)。 - 结果:从代码看,围绕一条新用户消息产生的提醒(日期、钩子、插件引用等,不论写在用户消息之前还是之后),开启 MCS 时落成“用户消息,接一条 system 消息”,与
turn.ts:544注释所说的 “user → system” 一致;关闭时是排在用户消息之前的几条<system-reminder>user 消息。 - 缓存:渲染后先清掉所有非 system 消息上的缓存标记,只给最后一条非 system 消息打
ephemeral(provider-request-messages.ts:292)。加上前缀三条 system 消息,默认路径下一次请求共四个缓存标记;它们怎样变成各家协议的缓存参数见模型适配层。相邻 user 消息的合并开关是关着的,注释说这是 Anthropic 序列化层的职责(provider-request-messages.ts:39)。
有 8 种来源永远不走 MCS:context_prefix、两类压缩后提醒、分叉与旁聊边界、目标续跑、两种工具结果内联提醒(source.ts:76)。注释解释了分叉边界的理由:走 MCS 会被挪到新问题之后,改变上下文顺序(source.ts:79)。技能清单 skills_listing 不在其列,从代码看,开启 MCS 时它会离开前缀,以一条 system 消息落在第一条用户消息之后(provider-mid-conversation-system.ts:84)。
MCS 是否启用取决于模型属性 supportsMidConversationSystem,或运行时配置 midConversationSystem.mode 为 force(runtime-provider-request-messages.ts:17)。内置 Provider 配置里这个属性默认关(config/provider/zcode-builtin.json:885),对 Anthropic Messages 形态的 api.z.ai、open.bigmodel.cn、ZCode Coding Plan 与闲时端点、DeepSeek 的 Anthropic 端点打开(zcode-builtin.json:4090、zcode-builtin.json:4067),另对部分 Claude 模型打开(zcode-builtin.json:2486)。桌面端的模型设置里对应“对话中系统消息”开关(packages/ui/src/i18n/locales/zh-CN.ts:2867);命令行的 --force-mcs 强制开启,只能与 --prompt、--target 或 tui 一起用(apps/zcode-cli/packages/cli/src/run.ts:43)。规则怎样匹配到模型见Provider 规则与模型目录。
上下文用量拆解
每次模型请求前,runModelTextRequest 都算一份上下文用量快照(apps/zcode-cli/packages/core/src/runtime/methods/model.ts:137),分七类:System prompt、Meta user context、Skills、Tool prompt、System tool schemas、MCP tool schemas、Messages(apps/zcode-cli/packages/core/src/runtime/methods/context-usage.ts:158)。
- 前四类直接用
build()时每个段落记下的字符数与估算 token(context-usage.ts:123)。估算把中文字符按两个字符计,再除以 3(apps/zcode-cli/packages/core/src/context/utils.ts:12,除数见packages/shared/src/usage-stats.ts:9)。 - 工具 schema 把名称、描述、输入输出 schema、权限等字段序列化后估算,MCP 工具按
mcp__前缀区分(context-usage.ts:310)。 - Messages 按角色汇总,排除 system 消息与以
<system-reminder>开头的 user 消息(后台任务通知除外)(context-usage.ts:147)。快照自带两条警告:token 是本地估算,Messages 不重复计 system 与 meta 内容(context-usage.ts:248)。从代码看,对话中途的提醒无论渲染成 system 还是<system-reminder>文本,两边都没算进去;Tool prompt 一类也恒为 0,因为已经没有tools来源的段落。
这份快照有两个去处。一是 debug 日志 context_usage_snapshot:每类下按段落、工具、技能或消息角色列出贡献者(apps/zcode-cli/packages/core/src/runtime/helpers/context-usage-breakdown.ts:39),写日志时每类只留前 5 个(apps/zcode-cli/packages/core/src/runtime/methods/context-usage-log-compact.ts:1);初始化时的 context.built 日志更是带着每段全文(context.ts:282),调试工具据此画时间线(apps/zcode-cli/packages/debug/server/analyzer.ts:1385),见遥测、调试与提示词轨迹。二是界面,给的只是按类汇总的字符数(context-usage.ts:255),而且只挂在主回合的 ModelComplete 事件上(apps/zcode-cli/packages/core/src/runtime/methods/turn-model-step.ts:610),经协议投影进 usage.contextWindow.breakdown(apps/zcode-cli/packages/bootstrap/src/zcode-protocol-v4/product-projection.ts:4525)。桌面与 Web 端输入栏的上下文用量浮层据此画分段进度条,只显示各类占比,注释说分项 token 数会和顶部总量的口径混淆(packages/ui/src/chat-input-toolbar/contextUsage.tsx:961)。TUI 的状态栏与侧边栏只显示已用 token 与占窗口的比例(apps/zcode-cli/packages/tui/src/app-input-status.tsx:177),不展示拆解。
下一篇:上下文压缩——上下文快满时,microcompact、自动压缩与手动 /compact 各自怎样把对话本体换成摘要,又保留了什么。