Skip to content

feat(extensions): an extension's context file is unconditionally resident, with no path gating, budget, or attribution #12030

Description

@yiliang114

Part of #12028. Closely related to #11631, which asks for the same thing from the workflow side.

What happens today

Every active extension's context file is concatenated into the system prompt of every request, with no relevance gating of any kind:

  1. packages/core/src/extension/extensionManager.ts:1710-1714 — resolve contextFileName per extension, filtered only by fs.existsSync.
  2. packages/core/src/config/config.ts:8958-8966 — flat-map contextFiles over getActiveExtensions(). The only filter is "extension is active".
  3. packages/core/src/memory/memoryDiscovery.ts:201-204 — added unconditionally to the QWEN.md hierarchy.
  4. memoryDiscovery.ts:385-406 — concatenated verbatim, no size cap.
  5. packages/core/src/core/prompts.ts:698-706 — injected as the contextFiles layer.

There is no path gating, no per-extension budget, no truncation, no attribution, and no way to opt one extension's context out short of deactivating the extension entirely.

Why it matters

In a real session with nine active extensions, their context files totalled 9,989 tokens — 21% of all non-conversation context, present on every request regardless of what the session was doing. Sizes, largest first:

2,164  1,602  1,548  1,487  1,124  1,078  650  172  164

The distribution is the point: two extensions account for a third of the total, and nothing in the system tells their authors that. The remaining context-file budget in the same session was 4,021 tokens of project QWEN.md, 1,180 of auto-memory and 210 of an output-language file — so extensions were the majority of the always-on context layer, at 65% of it.

The same session listed 84 skills for a combined 4,620 tokens — about 55 tokens each, with bodies loaded on demand. Nine context files cost more than twice what eighty-four skills cost.

The same extensions already ship skills. A skill costs 100–200 tokens in the listing and loads its body only when invoked, and SkillConfig.paths (packages/core/src/skills/types.ts:155-157) gates it out of the listing entirely until a tool call touches a matching file (packages/core/src/skills/skill-activation.ts:42-56). So the eager path and the lazy path carry overlapping content, and the eager one is 10–20x more expensive per extension.

Two more asymmetries point the same direction:

  • .qwen/rules/ supports paths:-conditional context (packages/core/src/config/rulesDiscovery.ts:7-15), but loadRules() scans only <QWEN_HOME>/rules/ and <projectRoot>/.qwen/rules/ (:305-324) — extensions cannot contribute rules.
  • The newer agent-plugins-v1 format skips contextFileName altogether (extensionManager.ts:1702-1705) and ships skills only. The decision has effectively already been made for plugins; classic extensions never got it.

Suggestions

Any one of these would help; they are not exclusive.

  1. Let an extension's context file carry paths: frontmatter, reusing the rules/skill activation machinery, so it only enters the prompt when relevant.
  2. Let extensions contribute .qwen/rules/-style conditional rules.
  3. Document the migration — "put scenario-specific guidance in a paths:-gated skill, keep only always-true facts in the context file" — and say it in the extension authoring docs, since today nothing signals that a context file is the expensive option.
  4. Attribute and warn per extension. The existing warning (config.ts:4702-4727) is a single aggregate line that names no file; with nine extensions it cannot tell an author their extension is the problem. See Percentage-of-context-window budgets scale the wrong way: ToolSearch preload never engages, and the always-on context warning never fires, on large windows #12029 for why the threshold also never fires on large windows.
中文说明

属于 #12028。与 #11631 高度相关——那个 issue 从 workflow 的角度提出了同一件事。

现状

每个已启用 extension 的上下文文件都会被拼进每一轮请求的系统提示词,没有任何相关性门控:

  1. packages/core/src/extension/extensionManager.ts:1710-1714 —— 按 extension 解析 contextFileName,只用 fs.existsSync 过滤。
  2. packages/core/src/config/config.ts:8958-8966 —— 对 getActiveExtensions() 展平 contextFiles,唯一的过滤条件是"extension 处于启用状态"。
  3. packages/core/src/memory/memoryDiscovery.ts:201-204 —— 无条件加入 QWEN.md 层级。
  4. memoryDiscovery.ts:385-406 —— 原样拼接,无大小上限。
  5. packages/core/src/core/prompts.ts:698-706 —— 作为 contextFiles 层注入。

没有路径门控、没有单个 extension 的预算、不截断、不归因,除了停用整个 extension 之外也没有办法单独去掉某个 extension 的上下文。

为什么重要

在一个启用了 9 个 extension 的真实会话里,它们的上下文文件合计 9,989 token——占全部非对话上下文的 21%,无论会话在做什么都存在于每一轮请求中。各文件大小,由大到小:

2,164  1,602  1,548  1,487  1,124  1,078  650  172  164

分布本身就是问题所在:两个 extension 占掉了总量的三分之一,而系统没有任何机制告诉它们的作者这件事。同一会话中上下文文件的其余部分是:项目 QWEN.md 4,021、auto-memory 1,180、输出语言文件 210——也就是说 extension 占了整个常驻上下文层的 65%。

同一会话列出了 84 个 skill,合计 4,620 token,平均每个约 55 token,正文按需加载。9 个上下文文件的开销是 84 个 skill 的两倍多。

而这些 extension 本身已经带了 skill。一个 skill 在清单里只占 100–200 token,正文只在被调用时加载;SkillConfig.paths(packages/core/src/skills/types.ts:155-157)还能让它在有工具调用碰到匹配文件之前完全不出现在清单里(packages/core/src/skills/skill-activation.ts:42-56)。也就是说急加载和懒加载两条路承载着重叠的内容,而急加载那条每个 extension 贵 10–20 倍。

另外两处不对称指向同一个方向:

  • .qwen/rules/ 支持 paths: 条件上下文(packages/core/src/config/rulesDiscovery.ts:7-15),但 loadRules() 只扫描 <QWEN_HOME>/rules/ 和 <projectRoot>/.qwen/rules/(:305-324)——extension 无法提供 rules。
  • 更新的 agent-plugins-v1 格式完全跳过 contextFileName(extensionManager.ts:1702-1705),只带 skill。对 plugin 而言这个选择其实已经做了,经典 extension 没跟上。

建议

以下任意一条都有帮助,彼此不排斥:

  1. 让 extension 的上下文文件支持 paths: frontmatter,复用 rules / skill 的激活机制,只在相关时进入提示词。
  2. 允许 extension 提供 .qwen/rules/ 形式的条件规则。
  3. 在 extension 开发文档里写清迁移路径——"场景相关的指引放进 paths: 门控的 skill,上下文文件只保留永远成立的事实"。今天没有任何信号告诉作者"上下文文件是贵的那个选项"。
  4. 按 extension 归因并告警。 现有告警(config.ts:4702-4727)只有一行聚合信息、不指名文件;有 9 个 extension 时,它无法告诉作者问题出在自己身上。阈值在大窗口下同样不会触发,见 Percentage-of-context-window budgets scale the wrong way: ToolSearch preload never engages, and the always-on context warning never fires, on large windows #12029。

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions