Files
qwen-code/docs/users/features/rules.md
易良andyiliang114 4f0277777f feat(core,docs): add conditional extension rules and cap context warnings (#12034)
* 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]>
2026-09-20 11:20:54 +00:00

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