When AI Coding Tools Actually Load Your Context Files (And What It Costs You)

When AI Coding Tools Actually Load Your Context Files (And What It Costs You)

You Committed the Files. Do You Know When They Load?

You've set up CLAUDE.md, copilot-instructions.md, path-scoped instructions, prompt files, maybe a custom agent or two. Quick question: which of those files is sitting in your model's context window right now, on this exact request?

Most developers assume everything loads all the time. It doesn't. And the difference bites you twice: once on your token bill, and once in the model's attention. Every line injected into every request is a line the model has to weigh against your actual task. A bloated always-on context makes the AI dumber and more expensive at the same time. Lovely combo.

In my previous post on context files for AI coding tools I covered WHAT these files are and how to write them for a .NET project. This post answers the question everyone had next: WHEN does each one actually enter the model's context, WHO decides, and what does it cost you per request?


The Loading Spectrum

Every context mechanism sits somewhere between eager and lazy. I think of it as four tiers:

Mechanism When loaded Who decides Token cost
Root CLAUDE.md / AGENTS.md Session start, every turn Harness (fixed rule) Full file, every request
.github/copilot-instructions.md Every chat/agent request Harness (fixed rule) Full file, every request
.github/instructions/*.instructions.md When applyTo glob matches files in play Harness (glob match) Full file, matching requests only
Subfolder CLAUDE.md / AGENTS.md When agent works in that directory tree Harness (path match) Full file, only in that subtree
Agent Skills (SKILL.md) Frontmatter at start; body on demand The model itself ~2 lines always; full file only when triggered
.github/prompts/*.prompt.md Only when user types /prompt-name The human Zero until invoked
Custom agents / subagents Metadata at start; body when agent is selected Human or main agent ~2 lines always; body in a separate context

Same repo, seven mechanisms, four completely different loading strategies. I'll go through them from most eager to most lazy, because where a rule sits on this spectrum should decide where it lives in your repo.


Tier 1: Always Loaded - Instruction Files

The root CLAUDE.md (or AGENTS.md, which Claude Code, Copilot CLI, and most modern harnesses now read interchangeably) gets read at session start. From that moment it's injected into the system context for every single turn. Not once. Every turn.

.github/copilot-instructions.md works the same way for GitHub Copilot: it rides along with every Copilot Chat and agent request in that repo.

One nuance that trips people up: copilot-instructions.md applies to chat and agent requests, NOT to classic inline ghost-text completions. Inline completions come from the surrounding code in your open editor tabs, not your instruction files. If your inline suggestions ignore your conventions, that's why. The fix is good example code nearby, not a longer instructions file.

The cost of Tier 1 is simple and unforgiving: you pay for every word here on every single request. A 2,000-line CLAUDE.md is an anti-pattern, and now you know exactly why. It taxes every turn with tokens you're billed for, and it spreads the model's attention across hundreds of rules when maybe five matter for the task at hand.

My rule: the root instruction file stays under roughly 100 lines. If something only applies sometimes, it belongs in a lower tier. I covered lean instruction file structure in my Copilot .NET setup guide. The loading mechanics here are the reason that leanness matters.


Tier 2: Conditionally Loaded - Path-Scoped Instructions

Copilot's .github/instructions/*.instructions.md files carry an applyTo glob in their frontmatter:

---
applyTo: "tests/**/*.cs"
---

# Test Conventions
- xUnit only. No MSTest, no NUnit.
- Integration tests use Testcontainers with the PostgreSQL module.
- Follow Arrange-Act-Assert with blank lines between sections.
- Never mock DbContext. Use the Testcontainers database.

This file only gets injected when the files in play match the glob. Edit a file under tests/ and these rules ride along. Edit an endpoint in src/Api/ and they don't. Globs compose too: touching tests/Integration/OrderTests.cs can pull in a general **/*.cs C# rules file AND the test rules on top.

Claude Code's equivalent is subfolder CLAUDE.md / AGENTS.md files. The root file loads at session start, but a tests/CLAUDE.md loads lazily, only when Claude starts reading or editing files in that directory tree, and gets merged with the root context.

This is what keeps big monorepos sane. Your Angular rules never pollute a backend EF Core request, and vice versa. The harness still decides when to load (it's a fixed glob or path rule, not a judgment call), but the cost is scoped to the requests that actually need it.


Tier 3: Progressively Disclosed - Agent Skills

Here the decision maker changes, and that's the whole point of this tier. Agent Skills are folders containing a SKILL.md file: .claude/skills/<name>/SKILL.md in Claude Code, with the cross-tool convention .agents/skills/ emerging so the same skill folders work across harnesses (Copilot CLI discovers skills too).

Skills use three-level progressive disclosure:

  1. At session start, only the YAML frontmatter of every skill loads: the name and description, a few dozen tokens. It's an index, not the content.
  2. When the model decides the current task matches a skill's description, it reads the full SKILL.md body into context.
  3. Bundled files in the skill folder (reference docs, scripts, templates) only get read or executed if SKILL.md points to them and the task needs them.

Here's a concrete one for the CleanApi project from the previous post, .agents/skills/ef-migrations/SKILL.md:

---
name: ef-migrations
description: Use when adding, modifying, or troubleshooting EF Core
  migrations in this repo. Covers the exact CLI commands, project
  structure, and rules for safe schema changes against PostgreSQL.
---

# EF Core Migrations - CleanApi

## Commands
Always run from the repo root:

    dotnet ef migrations add <Name> -p src/Infrastructure -s src/Api
    dotnet ef database update -p src/Infrastructure -s src/Api
    dotnet ef migrations script --idempotent -p src/Infrastructure -s src/Api -o migrate.sql

## Rules
- Migration names in PascalCase describing the change: AddOrderStatusIndex.
- Never edit an applied migration. Add a new one.
- Column renames: verify EF generated RenameColumn, not Drop + Add.
- New required columns on existing tables need a default or a backfill step.
- Review the generated migration file before committing. Always.

## Verification
After adding a migration, run the idempotent script generation above
and check the SQL for destructive operations (DROP, data loss).

At session start, the context holds the name and the description. Roughly 40 tokens. That's it. The commands, the rules, the verification steps cost nothing until I ask Claude to "add a migration for the new OrderStatus column" and the model matches the task against the description and pulls in the body.

For level 3, here's a skill with a bundled file, .agents/skills/api-client-gen/:

.agents/skills/api-client-gen/
  SKILL.md          <- frontmatter indexed at start; body loaded on match
  generate.ps1      <- only executed when SKILL.md directs the agent to run it
  naming-rules.md   <- only read if the agent needs the detailed naming reference

The SKILL.md body says "run generate.ps1 to regenerate the typed HTTP client from the OpenAPI spec, and consult naming-rules.md if renaming operations." Those two files never touch the context window unless the task actually reaches them.

That's the token-economics win: you can ship 50 skills and pay roughly 50 lines of index at session start, not 50 full documents.

Two things follow from the mechanics:

The model decides, not the harness. Instructions load by fixed rules; skills load because the model judges the description matches the task. That's the real difference between Tier 2 and Tier 3.

The description is the single most important line of a skill. It's the trigger. Write it like a matching rule ("Use when adding, modifying, or troubleshooting EF Core migrations in this repo"), not like marketing copy. A vague description like "Helpful database utilities" will either never fire or fire constantly. Both waste the whole mechanism.


Tier 4: Explicitly Invoked - Prompt Files and Custom Agents

At the lazy end of the spectrum, nothing loads until a human (or a delegating agent) says so.

Prompt files (.github/prompts/*.prompt.md) load only when the user types /prompt-name in chat. Zero cost until invoked. In the previous post I showed a /new-endpoint prompt file that scaffolds an 11-file vertical slice: command, validator, handler, endpoint, entity config, tests, the lot. Carrying that recipe in every request would be criminal. As a prompt file it costs nothing 99% of the time and delivers the full template the moment I type /new-endpoint.

---
mode: agent
description: Scaffold a complete vertical slice for a new API endpoint
---

Create a new endpoint for {feature}. Generate all files following
the CleanApi conventions: Command/Query record, FluentValidation
validator, MediatR handler returning Result<T>, minimal API endpoint
in the route group, EF entity configuration if a new entity is
involved, and xUnit integration tests using Testcontainers.

Custom agents sit in .github/agents/*.agent.md for Copilot and .claude/agents/*.md for Claude Code subagents. Their loading is a hybrid: the metadata (name and description) is discovered at session start, like skill frontmatter, but the full instruction body only enters context when the agent is selected. That selection can be explicit (you pick the reviewer agent) or delegated (the main agent decides the task belongs to the test-writer).

Claude Code subagents have one more property that makes them special: they run in a separate context window entirely. The subagent's instructions, its tool calls, its intermediate reasoning, none of it pollutes the main conversation. The main agent gets back a summary, not the transcript. For long sessions, that isolation is worth as much as the lazy loading. I touched on subagent setup in my Claude Code .NET guide.


So Where Should Rule X Live?

Once you know the loading mechanics, placement stops being a debate. It's mechanical:

  • Applies to every file, every task (architecture, Result pattern, "no controllers") -> root CLAUDE.md / AGENTS.md / copilot-instructions.md. Keep it under ~100 lines. You pay for it on every request, so every line must earn its place.
  • Applies to a file type or folder (test conventions, frontend rules, a specific service in a monorepo) -> applyTo instruction file or subfolder AGENTS.md. Scoped cost, harness-decided.
  • A capability or procedure needed occasionally (migrations, release process, API client generation) -> a skill. You pay two lines of index; the model pulls the rest when the task matches.
  • A recipe the human triggers deliberately (scaffold an endpoint, write a changelog entry) -> a prompt file. Zero cost until /invoked.
  • A distinct role with its own workflow (code reviewer, test writer) -> a custom agent or subagent. Lazy body loading, and with subagents, full context isolation.

The pattern behind all five answers: push everything as far down the spectrum as it can go. The only things that belong in Tier 1 are rules that would be violated on literally any task. Everything else has a lazier home.


Smaller Always-On Context, Sharper Model

Understanding when context loads changed how I structure it. My root instruction files got shorter. My skill descriptions got sharper. My scaffolding recipes moved into prompt files where they cost nothing until needed.

The payoff shows up three ways at once: a smaller always-on context (lower token bill), sharper model attention (fewer irrelevant rules competing with the task), and better output on the first try (the right rules present at the right moment).

Your AI tooling isn't one big context file. It's a loading strategy. Audit yours this week: open your root instruction file and ask, for every section, "does this apply to every single request?" If the answer is no, you now know exactly where it belongs instead.