# Context Selection

Course: [Context Engineering](https://www.devtutorials.ai/learn/context-engineering) · Section: Foundations
Source: https://www.devtutorials.ai/learn/context-engineering/foundations/context-selection

> How to decide what goes into a context: include, exclude, summarize, or retrieve on demand. The minimum context principle, evidence sufficiency, speed and cost, and stable versus dynamic context.

Every school morning, you pack your bag. You don't carry every book you own. You look at today's timetable. You pack only what today needs. A book you might need later stays home. A thick folder of old notes becomes one summary page.

Packing a context works the same way. You have far more information than the model needs. Your job is to choose.

**The main idea:** give the model the smallest context that is still enough to answer well.

## A Real-World Example

Imagine asking a librarian, "When does the library close on Saturday?"

- A good librarian tells you the Saturday hours.
- A bad librarian hands you the whole rule book. Then the history of the building. Then last year's events list. And says "it's in there somewhere."

The second librarian gave you more information. But the answer is harder to find. It takes longer to read. You might read the wrong line.

**In simple words:** more information is not better information. The right information is better.

## What You Will Learn

In this lesson, you will learn:

1. The four decisions: include, exclude, summarize, and retrieve on demand.
2. The minimum context principle and why it helps.
3. How to check that the context has enough evidence.
4. How context size affects speed and cost.
5. The difference between stable and dynamic context.

Use the **Builder** and **Compare** tabs of the Context sandbox in this lesson. Switch parts on and off. Watch the token count. See if the answer gets better or worse.

## Selection Decisions

For every piece of information you could send, you make one of four decisions.

### Include

**Include** a part when this question needs it and it is short enough to send as it is.

For Asha's failed export, the customer record and the export limits article are needed. They are short. They go in.

### Exclude

**Exclude** a part when this question doesn't need it. Also exclude parts that are old, wrong, or private.

The theme guide has nothing to do with exports. Leaving it out saves tokens and removes a distraction. Another customer's data must never go in at all.

### Summarize

**Summarize** a part when the question needs what it says. But the full text is too long.

A chat with 40 earlier messages may hold two important facts. Send the two facts. Not the 40 messages. The next box keeps only the lines that record a decision or a constraint.

```js runnable title="40 messages can become 2 notes"
const chat = [
  "User: Hi, I am exporting a big report.",
  "Assistant: Happy to help. Which format?",
  "User: Excel, please. Decision: use Excel format.",
  "Assistant: Great. How many rows?",
  "User: About 80,000. Constraint: must finish today.",
  "Assistant: Thanks, checking your plan now.",
];

const summary = chat
  .filter((line) => line.includes("Decision:") || line.includes("Constraint:"))
  .map((line) => line.slice(line.search(/Decision:|Constraint:/)));

console.log("Before:", chat.join(" ").length, "characters");
console.log("After:", summary.join(" ").length, "characters");
console.log(summary.join("\n"));
```

Expected output:

```text
Before: 254 characters
After: 58 characters
Decision: use Excel format.
Constraint: must finish today.
```

The summary is less than a quarter of the size. It keeps what matters. Real apps usually ask a model to write the summary. Lesson 7 covers that.

Be careful with summaries. Some text needs its **exact words**. Like a legal clause, a price, or an error message. Don't summarize those. Send them as they are.

### Retrieve On Demand

**Retrieve on demand** means you don't send a part up front. You fetch it only if the question needs it.

Asha's invoice history is long. Most questions don't need it. So the app leaves it out. If she asks about an invoice, the app looks it up then.

This is also called **just-in-time context**: the right information arrives when it is needed. Not before.

### Making the Decision

The next box turns the four decisions into simple rules. It applies them to five parts.

```js runnable title="One rule for each decision"
function decide(part) {
  if (!part.needed) return "exclude";
  if (part.onlyWhenAsked) return "retrieve on demand";
  if (part.tokens > 500) return "summarize";
  return "include";
}

const parts = [
  { name: "Customer record", needed: true, tokens: 40 },
  { name: "Export limits article", needed: true, tokens: 60 },
  { name: "Theme guide", needed: false, tokens: 300 },
  { name: "Last 40 chat messages", needed: true, tokens: 2400 },
  { name: "Invoice history", needed: true, onlyWhenAsked: true, tokens: 900 },
];

for (const p of parts) console.log(p.name + " -> " + decide(p));
```

Expected output:

```text
Customer record -> include
Export limits article -> include
Theme guide -> exclude
Last 40 chat messages -> summarize
Invoice history -> retrieve on demand
```

Real apps use more careful rules. But the four choices are always the same.

| Decision | Ask | Example |
|---|---|---|
| Include | Is it needed now and short? | Current customer record |
| Exclude | Is it unneeded, old, or risky? | Unrelated chat from March |
| Summarize | Is it needed but too long? | Earlier project decisions |
| Retrieve on demand | Is it needed only sometimes? | Latest invoice status |

## Minimum Context Principle

The **minimum context principle** says: give the model the smallest amount of information that is enough for the task.

This is not about starving the model. A context with a missing fact fails. The goal is **enough and no more**.

### Prompt Simplification

When the context holds only what is needed, the prompt gets simpler too.

With a messy context, you write extra rules: "Ignore the theme guide. Don't use the March chat. The pricing page is old." With a clean context, none of those rules are needed. Every rule you don't write is one less thing for the model to follow or get wrong.

### Evidence Sufficiency

**Evidence sufficiency** means the context has every fact the answer needs.

Before you send a context, list the facts the answer depends on. Then check that each one is there.

```js runnable title="Is the evidence enough?"
const context = "Customer: Asha Rao. Plan: Free. Free plan: export up to 5,000 rows.";

const required = ["Plan: Free", "5,000 rows", "Rows requested"];

const missing = required.filter((fact) => !context.includes(fact));

console.log("Required facts:", required.length);
console.log("Missing:", missing.length ? missing.join(", ") : "none");
console.log(missing.length ? "Not enough evidence. Add the missing part or say 'not sure'." : "Enough evidence to answer.");
```

Expected output:

```text
Required facts: 3
Missing: Rows requested
Not enough evidence. Add the missing part or say 'not sure'.
```

Without the number of rows Asha asked for, the model can't explain why the export failed. It would have to guess. The fix is to add the error file. Not to reword the question.

### Context Balance

**Context balance** is the space between too little and too much.

- **Too little:** facts are missing. The model guesses.
- **Too much:** the key fact is buried. The reply is slower. Every request costs more.
- **Balanced:** every needed fact is there. Little else is there.

Try it. Context A sends everything the sandbox has. Context B sends only the parts this question needs.

Look at the token counts on the card first. Then compare the replies. If B is as good as A or better, every extra token in A was wasted. Everything also includes an untrusted forum post that tries to change the rules. Lesson 4 shows how to handle that.

### Latency Control

**Latency** is the time you wait for a reply. Before the model writes its first word, it must read the whole context. A larger context means a longer wait before anything appears.

For a chat app, this wait is what users notice most. Trimming the context is often the easiest way to make an assistant feel faster. Open the Builder. Run the "Everything" set and the "Just right" set. Compare the times shown under each reply.

### Token Cost Control

Most AI services charge for every input token on every request. Small savings add up fast.

The next box uses an **example price** of $3 per million input tokens. Real prices differ between models and providers.

```js runnable title="The same app, two context sizes"
const pricePerMillion = 3;
const requestsPerDay = 10000;

function monthlyCost(tokensPerRequest) {
  const tokens = tokensPerRequest * requestsPerDay * 30;
  return "$" + ((tokens / 1000000) * pricePerMillion).toFixed(2);
}

console.log("Everything (2,400 tokens):", monthlyCost(2400));
console.log("Just right (600 tokens):", monthlyCost(600));
```

Expected output:

```text
Everything (2,400 tokens): $2160.00
Just right (600 tokens): $540.00
```

Same app, same users, same questions. Picking the context carefully cut the bill to a quarter.

**Easy Way to Remember:** pack like a school bag. **Today's books in. Others stay home. Thick folders become one page. Fetch the rest if you need it.**

## Context Categories

Context parts fall into two groups. It depends on how often they change.

### Stable Context

**Stable context** changes rarely. It is the same for many requests.

Examples:

- System instructions.
- Safety rules.
- Output schemas.
- Examples of good answers.
- Tool definitions.

Write stable context carefully once. Test it. Then reuse it. Keep it word-for-word the same between requests. Many AI services can then reuse their work on it and charge less. This is called **prompt caching**. Lesson 12 covers it.

### Dynamic Context

**Dynamic context** changes with every request.

Examples:

- The user's question.
- The customer record for this user.
- Retrieved documents for this question.
- Tool outputs and fresh data.
- Recent conversation history.

Dynamic context must be chosen again for every request. This is where the four decisions happen.

```js runnable title="Split the parts into stable and dynamic"
const parts = [
  { name: "System instructions", changes: "rarely" },
  { name: "Safety rules", changes: "rarely" },
  { name: "Output schema", changes: "rarely" },
  { name: "Customer record", changes: "every request" },
  { name: "Retrieved article", changes: "every request" },
  { name: "User question", changes: "every request" },
];

const stable = parts.filter((p) => p.changes === "rarely").map((p) => p.name);
const dynamic = parts.filter((p) => p.changes !== "rarely").map((p) => p.name);

console.log("Stable (put first):", stable.join(", "));
console.log("Dynamic (put after):", dynamic.join(", "));
```

Expected output:

```text
Stable (put first): System instructions, Safety rules, Output schema
Dynamic (put after): Customer record, Retrieved article, User question
```

Stable parts go first. Dynamic parts go after them. That order also matches the recall zones from Lesson 2: rules at the start, fresh evidence and the question at the end.

| | Stable context | Dynamic context |
|---|---|---|
| Changes | Rarely | Every request |
| Examples | Rules, schemas, examples, tool definitions | Question, records, documents, tool outputs |
| Where it goes | First | After the stable part |
| Main job | Write it well once, keep it identical | Pick it fresh, keep it small |

Cutting too much is as bad as sending too much. If you remove a part, check that the answer still has every fact it needs.

## Common mistakes

| Mistake | Wrong idea | Correct idea | What goes wrong |
|---|---|---|---|
| Dumping everything | "More context can only help." | Send the smallest context that is enough. | Slower, more costly replies. The key fact gets lost. |
| Cutting too much | "Smaller is always better." | Check that every needed fact is still there. | The model guesses and makes up an answer. |
| Summarizing exact text | "A summary of the error message is fine." | Keep exact words for errors, prices, and legal text. | The summary changes a number or a meaning. |
| Loading rare data every time | "Invoice history might be useful." | Fetch it only when a question needs it. | Every request pays for data that is almost never used. |
| Changing stable text often | "I'll tweak the system prompt for each request." | Keep stable parts the same. Put them first. | Results change from run to run. Caching can't help. |
| Fixing the wording, not the evidence | "If I ask better, it will know the row count." | A missing fact must be added to the context. | The same wrong answer, no matter how you ask. |

## Exercises

Each exercise ends with a value on the last line. Press **Check** to compare it with the expected answer.

## What to Remember

- For every part, choose: **include**, **exclude**, **summarize**, or **retrieve on demand**.
- The **minimum context principle**: the smallest context that is still enough.
- Check **evidence sufficiency**: every fact the answer needs must be in the context.
- A smaller context gives **faster** and **cheaper** replies. It also gives simpler prompts.
- **Stable context** changes rarely and goes first. **Dynamic context** changes every request and goes after it.
- Keep exact words for errors, prices, and legal text.

Next: **Context Formatting**, how to label, separate, and order the parts. So the model reads them correctly.
