* docs(plans): add a plan for non-conversation context token governance Measures the non-conversation prefix (system prompt, built-in tool schemas, context files, skill listing) from a real /context sample, proposes an absolute "idle cost" metric in place of percentage-of-window, and lays out the configuration-only levers that reach it. Tracks the upstream gaps in #12028. * docs(verification): add a handoff brief for context token governance Companion to the plan: the verification steps with expected numbers, the mechanism facts with file:line citations, the impact checklist to walk before rollout, and the decisions that need a person rather than a run. Written for an agent on a build-capable machine to pick up by cloning the branch. * docs(plans): flag the safety clause keepCodingInstructions drops, and fill prompt and tool gaps keepCodingInstructions: false removes Software Engineering Tasks, which carries the Report outcomes faithfully clause the plan lists as a must-keep anchor; say where to restore it. Also add the per-section prompt sizes, what a full prompt replacement costs beyond drift (process-wide scope, QWEN_SYSTEM_MD pitfalls, lost dynamic layers), rollback per step, a tools.visible fallback instead of an intent classifier, resident tool description trimming, and the per-tool load-reason telemetry gap. Verified by reading main at b8def02aad; nothing was run. * docs(plans): point at #12054 and #12048, and flag the stale workflow baseline The workflow tool's 3,829-token figure predates moving its authoring contract into the workflow-authoring skill; note that and ask for a re-measure. Replace two "no issue yet" claims: description trimming is tracked in #12054 (re-scoped to agent), and telemetry correctness in #12048; only per-tool load-reason telemetry is still unfiled. * docs(vscode): document IPv6 webview guard * docs: split the VS Code remote-webview notes out of this PR They arrived here on a piggyback branch and are unrelated to context token governance; carrying both meant neither could be reviewed on its own terms. Moved verbatim to a separate PR. * docs: restore the VS Code notes to main's content The previous commit deleted these files outright. They predate this branch and live in main; the piggyback commit had only edited them. Restoring main's version leaves this PR's net diff to the two governance documents, and the edit itself is carried by #12140. * docs(plans): record what landed upstream against the token-governance plan The plan was written on 2026-09-16, before any of the umbrella's sub-issues had a result. Four now do, and two of them change how the document's own numbers should be read: #12119 gave `/context` an explicit residual, so the "categories do not add up to the total" gap in section 9 is no longer only a CJK-undercount story, and #12145 measured the prompt-gating saving at ~276 tokens, which confirms rather than revises section 4's "leave the system prompt alone" conclusion. Section 7's `tools.disabled` note is corrected in place: #11814 turned out to be two defects, and only the guidance half is fixed (#12271). What has not changed is the conclusion: steps 1 and 2 are still zero code, still the largest single saving, and still nobody has run them. Claude-Session: https://claude.ai/code/session_01P31kyHJf9VXo8w6h9uZetg * docs(users): a page on what every request pays for before the conversation Four settings already control how much of the prefix a session carries, and the settings reference documents each one precisely — but nothing tells a reader that the prefix is worth looking at, how to measure it, or in what order the knobs pay off. So the largest and cheapest saving available (an eager-tool allowlist, zero code) stays invisible unless someone already knows the key's name. Adds `docs/users/features/context-cost.md`: how to read `/context detail`, why idle cost is the metric rather than percent of window, the four levers in order of return with a copy-pasteable allowlist, and the traps that make a saving illusory — subagents receiving deferred schemas, the memory agent's six tools, tokens moving from a schema into shell output, a mid-session reveal invalidating the prefix cache, and prefix-caching models where the whole trade inverts. Every exemption, tool name and behaviour claim is taken from the code it describes rather than from the plan doc: the eager exemptions from `isExemptFromEagerAllowList`, the memory agent's tool list from `dreamAgentPlanner`/`extractionAgentPlanner`, the DeepSeek opt-out from the CLI config. Claude-Session: https://claude.ai/code/session_01P31kyHJf9VXo8w6h9uZetg * feat(core): bound the deferred-tool preload and context warning in tokens Local, unpushed: the two values still need a core owner's sign-off (#12029), and this is a behaviour change for large-window users, so it is not being folded into an unrelated PR. Claude-Session: https://claude.ai/code/session_01P31kyHJf9VXo8w6h9uZetg * feat(core): let an extension contribute conditional context rules An extension's `contextFileName` is concatenated into the system prompt of every request of every session the extension is active in, with no relevance gating and no size limit. Nine of them measured 9,989 tokens — 65% of one session's entire always-on context (#12030). The `agent-plugins-v1` format already dropped context files and ships skills only; classic extensions never got an equivalent. `.qwen/rules/` already has the mechanism: a rule with `paths:` stays out of the prompt until a tool call touches a matching file. So extensions may now contribute rules from `<extension>/rules/`, reusing that machinery rather than adding a second copy of the same gating — which is what the triage on #12030 asked for. Three decisions, each one line to change: - **Conditional only.** A baseline extension rule would be resident on every request, recreating the cost this exists to remove, so it is dropped and its author is told by name in a startup warning. Silence would leave them reading "extensions can contribute rules" while nothing happens. - **Not gated on folder trust**, unlike project rules. Installing an extension is already an explicit act, and the same extension can ship MCP servers, commands, skills and an ungated context file; requiring trust for the one mechanism that is strictly narrower would push authors back to the expensive path. - **Labelled by owner** (`charts:rules/charting.md`) rather than by a path relative to the project root, which for a rule outside the project is a stack of `../` segments. Also documents `.qwen/rules/` for everyone: it had no user documentation at all — one passing mention in auto-mode.md — which the triage called a prerequisite of shipping this, since authors cannot migrate to a mechanism nothing describes. Refs #12030, #12028 Claude-Session: https://claude.ai/code/session_01P31kyHJf9VXo8w6h9uZetg * fix(core): keep naming the context window when the token ceiling binds Simulating the two existing warning tests against the new message shape found one that would have failed: `getWarnings should use the model token limit when no contextWindowSize is configured` asserts the message names the fallback window, and 15% of that limit is far above the new 10,000-token ceiling, so the ceiling's wording replaced the window entirely. The bound that fired still leads — it is the number to act on — but the window clause follows it, so a reader can always see how the budget was derived and the fallback stays observable. Claude-Session: https://claude.ai/code/session_01P31kyHJf9VXo8w6h9uZetg * chore(config): regenerate the settings JSON Schema and document the new key `ci.yml` runs `npm run generate:settings-schema` and fails when the committed `packages/vscode-ide-companion/schemas/settings.schema.json` differs from what the schema definition produces, so adding a setting without regenerating it is a guaranteed red lane. The entry is written by hand to match the generator's output for an entry with a `jsonSchemaOverride` (override keys, then the description carried over from the definition) because regenerating needs a built core package, which is not available here. Also adds the `tools.toolSearch.maxPreloadTokens` row to the settings reference, which is where both new docs pages send the reader for exact semantics. Claude-Session: https://claude.ai/code/session_01P31kyHJf9VXo8w6h9uZetg * docs(extension): tell extension authors that rules exist An author reads the extension guide, not the features reference, so a mechanism documented only in `features/rules.md` is one they will never find. The subsection sits immediately before the `QWEN.md` step, which is where the expensive option is introduced. Note: #12318 also edits this file (the context-file cost note a few lines below); whichever merges second resolves a one-hunk conflict. Claude-Session: https://claude.ai/code/session_01P31kyHJf9VXo8w6h9uZetg * style: format the two governance docs and the CLI config edit Lint & Static went red on Prettier for three files, two of which were already unformatted before this PR touched them. They had never been checked: while this branch was docs-only the Lint lane's Prettier step was filtered out, so its green said nothing about them — the vacuous-green shape. Adding `packages/**` to the PR made the step run for real and surfaced the debt. The third, `packages/cli/src/config/config.ts`, is mine: I ran ESLint over the new `toolSearchMaxPreloadTokens` wiring but not Prettier. Claude-Session: https://claude.ai/code/session_01P31kyHJf9VXo8w6h9uZetg * revert(core): drop the preload-budget cap, which #10410 supersedes #10410 replaces "reveal a deferred tool by mutating the declared tool list" with a `tool_search` → `tool_call` bridge, so a mid-session discovery no longer rewrites the prompt prefix — and it therefore flips `tools.toolSearch.threshold` to default to 0. Two consequences for the cap added here: - It stops mattering by default. With no preloading, a ceiling on the preload budget only binds for someone who opts back in. - Its stated rationale is gone. The cap was justified by the cost of one mid-session reveal (¥0.28-0.40 against ¥0.01 saved per cached turn), which is exactly the premise that bridge removes. It also collides head-on: #10410 edits `DEFAULT_TOOL_SEARCH_THRESHOLD`, the same `toolSearch` schema block, the same generated `settings.schema.json` and the same `client.ts` preload call. Shipping both means a conflict for whoever merges second, over a knob that would then be nearly dead. Kept: the absolute ceiling on the always-on context warning (`min(floor(window × 0.15), 10_000)`). #10410 does not touch it, and the hole it closes is real either way — 15% of a 1M window is 150,000 tokens, so the ratio alone never fires where the cost is actually paid. #12029 is therefore half-implemented here and half-superseded, which is recorded on that issue and in the plan doc's status section. Refs #12029, #10410 Claude-Session: https://claude.ai/code/session_01P31kyHJf9VXo8w6h9uZetg * docs(plans): mark the preload cost model as superseded where it is stated Section 10 already records that #10410 invalidates it, but section 6 still asserted the break-even arithmetic as current fact — and that is the section a reader lands on when deciding whether `threshold: 0` is worth setting. Flag it in place rather than leaving the correction one section away. Claude-Session: https://claude.ai/code/session_01P31kyHJf9VXo8w6h9uZetg * fix(cli): retain extension rules across context refreshes * docs: correct context cache and token accounting claims * fix(core): report ignored extension rules only when there are some The Test lane went red on 13 assertions in `memoryDiscovery.test.ts`, all the same shape: they assert the whole `loadServerHierarchicalMemory` response with `toEqual`, so an added key turns a 6-key object into a 7-key one and every one of them fails. Those tests have no opinion about extension rules — they cover context-file discovery — so the fix is not to append a key to a dozen unrelated expectations. An empty list and an absent field already mean the same thing to the only consumer (`Config` reads it with `?? []`), so the field is now present only when at least one rule was actually dropped, and the response shape is unchanged for every session that ships no extension rules. The warning path is unaffected: its tests mock the response and supply the field explicitly. Claude-Session: https://claude.ai/code/session_01P31kyHJf9VXo8w6h9uZetg * test(core): pin the new loadRules field in the response-shape assertion Down to one failure from thirteen. `returns empty when no rules directory exists` asserts the whole `LoadRulesResponse` with `toEqual`, so the added `ignoredExtensionRules` made a 3-key object a 4-key one. Unlike the `memoryDiscovery` assertions — which cover context-file discovery and had no opinion about extension rules, so the field is reported there only when non-empty — this test exists to pin what `loadRules` returns. A field added to that contract belongs in it, so the expectation gains the key rather than the contract hiding from the test. Claude-Session: https://claude.ai/code/session_01P31kyHJf9VXo8w6h9uZetg --------- Co-authored-by: yiliang114 <[email protected]>
5.2 KiB
Rules
A rule is a Markdown file that reaches the model either at the start of a session or the moment the work touches a file it applies to. Rules live in .qwen/rules/, and the paths: field is what makes the second kind possible: guidance about your React components does not have to be in the prompt while you are editing a Makefile.
They are the cheap counterpart to a context file (QWEN.md), which is carried on every request of every session — see Resident Context Cost.
Where rules live
| Location | Loaded |
|---|---|
~/.qwen/rules/ (or $QWEN_HOME/rules/) |
always |
<project>/.qwen/rules/ |
when the workspace is trusted |
an active extension's rules/ |
always, conditional rules only — see below |
Every .md file under those directories is discovered, including in subdirectories, in a deterministic order.
Baseline and conditional rules
---
description: How we write React components
paths:
- 'src/**/*.tsx'
- 'src/**/*.jsx'
---
Components are function components. Co-locate the test beside the component.
Never reach for a global store for state one screen owns.
- With
paths:— a conditional rule. It stays out of the prompt until a tool call reads or edits a file matching one of its globs, and is then injected once for the rest of the session. - Without
paths:— a baseline rule. It is part of the system prompt from the first request, exactly like a context file, and costs the same on every turn.
Both fields are optional, and a rule with no frontmatter at all is a baseline rule.
Details worth knowing:
- Globs are matched against the path relative to the project root, with forward slashes on every platform, and they match dotfiles.
- Symlinks are resolved, so a rule matches whether the tool call used the link or the real path.
- A conditional rule is injected once per session — the second matching file does not repeat it.
- HTML comments are stripped from a rule's body before it is sent.
Rules from extensions
An extension can ship a rules/ directory, and its rules must be conditional: a rule with no paths: is skipped, with a startup warning naming it. That restriction is the entire point. An extension's context file (contextFileName) is concatenated into every request of every session the extension is active in, with no relevance gating — in one measured session, nine extensions' context files came to 9,989 tokens, 65% of all the always-on context that session carried. A baseline extension rule would recreate exactly that, one mechanism over.
Extension rules are labelled by their owner in the prompt — charts:rules/charting.md, not a path climbing out of the project — so a transcript shows whose rule fired.
They are not gated on workspace trust, unlike project rules: installing an extension is already an explicit act, and the same extension can contribute MCP servers, commands, skills and an ungated context file. Requiring trust for the one mechanism that is narrower and cheaper than a context file would only push authors back to the expensive option.
If you author an extension, this is the migration to make:
| Content | Put it in |
|---|---|
| Always-true facts — the extension's identity, its vocabulary, one hard constraint | the context file |
| "When working on X, do Y" | a paths:-gated rule, or a skill |
| A procedure the model runs on request | a skill |
Rules, skills and context files
| In the prompt from the start | Loaded on demand | |
|---|---|---|
Context file (QWEN.md) |
always, in full | — |
| Baseline rule | always, in full | — |
Conditional rule (paths:) |
nothing | when a matching file is touched |
| Skill | name + description only | body, when the model invokes it |
A skill is the right home for a procedure the model chooses to follow; a conditional rule is the right home for a constraint that applies to a region of the codebase whether or not the model thought to look for it. Skills can also be gated on paths:, which keeps even their listing entry out of the prompt until it is relevant.
See also
- Resident Context Cost — how to measure what your prefix costs, and the other levers.
- Skills
- Memory