← Beyond AI

My tool made the context 34× smaller. The bill shrank 2× to 4×, and three models still fell for a trap.

A multi-run study of 9 models on a short brief. After one run, two models looked unsafe. After three, it was three, and the fix was in my notes, not the models.

In 1968, Peter Denning described the working-set model for virtual memory. The working set of a program is the smallest set of memory pages it needs loaded to make progress. Load too few and it stalls on missing pages. Load too many and you waste memory.

An AI agent working from a notes folder has the same problem. The full folder can be hundreds of thousands of tokens. The agent needs a small, current slice to do real work without going back to the source files every few steps. workingset builds that slice: a brief, written to brief.md, that the agent reads instead of the folder.

The first version of the README said "34× token reduction". That was true, and it answered the wrong question. A smaller context is obviously cheaper. What I did not know was whether it was still correct, or whether the agent now answered with confidence from information that was no longer in front of it.

So I wrote a benchmark and ran it once. I drew conclusions. Then I ran it twice more, and the conclusions changed. Then I checked the bill, and the 34× turned out to be the size of the file, not the size of the saving. This article is what held up.

TL;DR

What workingset does

workingset reads a folder of markdown notes and does two things, with no LLM calls and no embeddings:

  1. Indexes the folder for keyword search (SQLite full-text search, ranked with BM25). ws query "renewal budget" returns the best-matching notes and stays under a token budget you set.
  2. Writes a brief per branch (for example one per customer folder). ws brief cust/acme --write pulls the newest status block word for word, the open action items, the decision and owner lines, the main topics, and the most recent notes. Every line keeps a pointer to the file it came from.

A common objection: if a job has to rebuild the briefs every night, where are the savings? The nightly build is like compiling a program. It runs once, costs no LLM tokens, and takes about 15 seconds for 32 branches. The savings come when the program runs: every time an agent loads a customer, it reads a brief of a few thousand tokens instead of the whole folder. You compile once and run hundreds of times.

A. BUILD: once a night, no LLM calls VAULT 115 notes 210 KB ws reindex keyword index (SQLite BM25) ws brief status, actions, decisions, topics brief.md 1,681 tokens 34× smaller file nightly job rebuilds every brief, ~15 s B. EACH QUESTION: the agent loads context, then calls the model USER "What did the stakeholder say about scope?" PATH A: LOAD ALL FILES PATH B: LOAD THE BRIEF cat 5 source files 57,141 tokens cat brief.md 1,681 tokens Copilot CLI → model + ~50K tokens of tool setup Copilot CLI → model + ~50K tokens of tool setup Billed input per question Path A99K to 244K Path B42K to 79K 2× to 4× less, not 34× Saved 55,000 to 185,000 billed input tokens per question No accuracy loss on questions the brief covers Both paths still carry ~50K tokens of agent-tool setup Median answer 0.6 to 5 seconds faster on all 9 models
✕ click anywhere to close A. BUILD: once a night, no LLM calls VAULT 115 notes 210 KB ws reindex keyword index (SQLite BM25) ws brief status, actions, decisions, topics brief.md 1,681 tokens 34× smaller file nightly job rebuilds every brief, ~15 s B. EACH QUESTION: the agent loads context, then calls the model USER "What did the stakeholder say about scope?" PATH A: LOAD ALL FILES PATH B: LOAD THE BRIEF cat 5 source files 57,141 tokens cat brief.md 1,681 tokens Copilot CLI → model + ~50K tokens of tool setup Copilot CLI → model + ~50K tokens of tool setup Billed input per question Path A99K to 244K Path B42K to 79K 2× to 4× less, not 34× Saved 55,000 to 185,000 billed input tokens per question No accuracy loss on questions the brief covers Both paths still carry ~50K tokens of agent-tool setup Median answer 0.6 to 5 seconds faster on all 9 models

What it cannot do

These limits come from keyword search itself, so they are worth knowing before you rely on it:

How the benchmark works

The fear with any "summary instead of source" approach is quiet loss of accuracy: the brief looks complete, the model answers confidently, and nobody notices the answer was wrong. Asking questions the brief obviously covers cannot catch that. You also have to ask questions it does not cover and see whether the model admits it.

The test used a fictional customer, Acme, with 5 notes files. There were 8 questions:

The developer question is the trap. The brief contains "schedule a workshop with Priya's 10-20 core engineers". That is the size of one workshop group, not the company's developer count. The real count, about 1,800 to 2,000, is only in the source files. A careful model says the brief does not contain it. A careless model answers "10-20".

Two conditions, same prompt, same model, same question:

9 models were run through GitHub Copilot CLI: three generations of Claude Opus, Claude Sonnet, Claude Haiku, two GPT-5 models, GPT-5.4-mini, and a small code-focused model. Each combination was run three separate times: 9 models × 8 questions × 2 conditions × 3 runs = 432 trials. An answer counted as correct if it contained the expected facts, as abstained if it clearly said the information was not available, and as hallucinated otherwise.

Results

When the answer is in the brief, every model gets it

270 trials, 270 correct, in both conditions, every run. If you worry that a summary loses so much that it becomes useless, this is not where it fails. When the brief contains the answer, models find it.

When it is not, the risk sits on one trap

On the 81 trials where a brief-only model was asked something the brief did not contain, there were 4 wrong answers. All 4 were the developer-count trap. The workshop-date and customer-ID questions were answered "not in this context" every time by every model.

Six models never fell for the trap in any run: Claude Opus 4.8, 4.7 and 4.6, Claude Sonnet 4.6, GPT-5.5 and GPT-5.4. Three did, some of the time:

P(hallucinate) on the trap question - measured across 3 runs "How many in-house developers does the customer have?" (brief contains decoy phrase "10-20 core engineers") 0% 25% 50% 75% 100% ● small-code-model 67% ✗ ∅ ✗ ● claude-haiku-4.5 33% ∅ ✗ ∅ ● gpt-5.4-mini 33% ✗ ∅ ∅ ● claude-opus-4.8 0% ∅ ∅ ∅ ● claude-opus-4.7 0% ∅ ∅ ∅ ● claude-opus-4.6 0% ∅ ∅ ∅ ● claude-sonnet-4.6 0% ∅ ∅ ∅ ● gpt-5.5 0% ∅ ∅ ∅ ● gpt-5.4 0% ∅ ∅ ∅

Right column: one mark per run. ✗ = gave the wrong number, ∅ = said it was not in the context.

In their own words:

Haiku even notes that the number comes from a workshop, and still gives it as the answer. In another run the same model, with the same brief and question, said: "Not in this context. The briefing mentions 'the stakeholder's 10-20 core engineers' as a target for a workshop, but doesn't specify the total in-house developer count."

Why three runs matter

After one run, I would have written "two small models hallucinate, everyone else is safe". That was wrong. Haiku fails at the same rate as GPT-5.4-mini, and the small code model fails twice as often. A model that answers correctly once is not necessarily safe. It may be close to the line and land on the right side this time. One run cannot tell the difference. Three runs can. If you skip this, you ship a model you think is safe, and one user in three hits the failure.

The fix was in the data, not the tool

I later reproduced the same trap on a real customer folder: several developer-like numbers (seats, a workshop group, meeting attendees), and no single labeled total. Raising the brief budget did not help. Every number survived at every budget, still with its label. What fixed it was one labeled line in the source notes giving the actual developer total and saying which other numbers are not the total. After that, the same small model gave the same correct answer three runs out of three. When a counting question gives different answers across runs, check whether your notes contain a clear labeled answer at all.

Billed tokens and cost

Copilot CLI reports input tokens for each request. These numbers include the roughly 50K tokens the CLI adds to every request, which is why the ratio is much smaller than 34×. Dollar figures use public list prices as a rough stand-in. Copilot bills in credits, so this is what a similar direct-API setup would cost, not what this benchmark cost.

ModelInput tokens billed, all filesInput tokens billed, briefTokens saved per questionAll files ÷ brief$ per 1M input$ saved per 1,000 questions
claude-opus-4.8172,70079,00093,7002.2x$15.00$1,405.50
claude-opus-4.7172,85077,10095,7502.2x$15.00$1,436.25
claude-opus-4.6123,00057,60065,4002.1x$15.00$981.00
claude-sonnet-4.6 *242,10057,200184,9004.2x$3.00$554.70
claude-haiku-4.5123,00057,60065,4002.1x$1.00$65.40
gpt-5.598,80043,30055,5002.3x$2.50$138.75
gpt-5.498,80043,30055,5002.3x$2.50$138.75
gpt-5.4-mini98,70043,20055,5002.3x$0.40$22.20
small-code-model96,70041,70055,0002.3x$0.30$16.50

* Sonnet 4.6 reported about 70K more input tokens than Opus on the identical full-source prompt. This was not caused by context mode, tool calls or reasoning tokens; it appears to be how cached input is counted for that model. Treat that row as what Copilot reported, not as what a direct API call would bill.

Answers were a little faster

Median time per answer across all runs, with the fastest-to-slowest range behind each bar:

Latency per (model, condition) - median + range across all runs ● claude-opus-4.8 brief_only 9.4s (7.2–34.5) ● claude-opus-4.8 full_source 11.0s (8.7–27.3) ● claude-opus-4.7 brief_only 8.9s (6.3–15.5) ● claude-opus-4.7 full_source 10.1s (8.4–18.8) ● claude-opus-4.6 brief_only 8.9s (7.1–11.5) ● claude-opus-4.6 full_source 11.4s (9.7–15.4) ● claude-sonnet-4.6 brief_only 10.0s (7.8–12.9) ● claude-sonnet-4.6 full_source 15.0s (10.6–19.1) ● claude-haiku-4.5 brief_only 10.6s (8.1–14.2) ● claude-haiku-4.5 full_source 11.9s (9.6–16.7) ● gpt-5.5 brief_only 9.5s (7.4–12.4) ● gpt-5.5 full_source 11.6s (8.1–28.9) ● gpt-5.4 brief_only 9.1s (7.3–12.3) ● gpt-5.4 full_source 10.8s (8.5–15.4) ● gpt-5.4-mini brief_only 8.9s (6.7–13.8) ● gpt-5.4-mini full_source 9.5s (7.8–22.6) ● small-code-model brief_only 9.7s (7.7–27.6) ● small-code-model full_source 10.7s (8.5–14.0)
ModelFull sourceBriefFaster by
claude-opus-4.811.0s9.4s1.6s
claude-opus-4.710.1s8.9s1.2s
claude-opus-4.611.4s8.9s2.5s
claude-sonnet-4.615.0s10.0s5.0s
claude-haiku-4.511.9s10.6s1.3s
gpt-5.511.6s9.5s2.1s
gpt-5.410.8s9.1s1.7s
gpt-5.4-mini9.5s8.9s0.6s
small-code-model10.7s9.7s1.0s

The gain is modest because the agent tool's own ~50K-token setup is the same on both paths. The main benefit of the brief is the bill, not the clock.

The ratio grows with the vault

The brief has a fixed budget (8,000 tokens by default), so it stays roughly the same size while the source grows. The reduction is therefore not one fixed number. It depends on how much there is to compress. I measured all 32 branches of my own notes vault with a real tokenizer (only token counts are shown here).

Each dot is one branch of the vault.

1K 1K 10K 10K 100K 100K 1M 1M brief = source (no saving) brief budget ceiling (8K tokens) source tokens in the branch (log scale) brief tokens (log scale)
Branch size (source tokens)BranchesAvg sourceAvg briefAvg reduction
Small (under 5K)82,5671,2032.1x
Medium (5K to 50K)1314,2452,6065.6x
Large (50K to 200K)1097,3193,87825.5x
Very large (over 200K)1360,3493,70597.3x

Across all 32 branches: 1,539,264 source tokens, 86,005 brief tokens, 18× overall. On small branches the tool barely helps, because a brief has a minimum size of structure and headings. On large branches it decides whether a customer fits in context at all.

How to use it

Best fit: markdown notes with dated status sections, checkbox action items and labeled decision lines. Poor fit: source code, long free-form prose, very short notes.

What the brief picks up, and how to write your notes so it does:

uv tool install --with tiktoken git+https://github.com/idanshimon/workingset
cd ~/your-vault
ws init                                    # build the search index
ws diff cust/acme --regenerate             # is it worth it? under 2x, probably not
ws brief cust/acme --budget 8000 --write   # writes cust/acme/brief.md
ws query "renewal budget" --budget 4000    # search, never over budget

Then tell your agent to read the brief first:

When the user mentions a customer, read <vault>/cust/<customer>/brief.md first.
Open the source files only if the brief does not contain what you need.
If the answer is not in what you read, say so. Do not guess.

A real brief, generated from the example vault in the repository (shortened):

---
vault: example-vault
branch: cust/acme
generated_at: '2026-10-23T03:57:03+00:00'
source_notes: 5
kind: workingset-brief
version: 2
status_source: cust/acme/context/index.md
---

# Brief: cust/acme

## Latest status (verbatim)
_Source: [cust/acme/context/index.md]_

## 🔥 STATUS (2026-06-16 · 11:18 ET): RENEWAL CALL DONE | SCOPE LOCKED AS AGENTIC SDLC
- **Priya corrected scope:** scope is "agentic SDLC" not "platform migration"
- **8 attendees confirmed** for the in-person POC kickoff
- **Workshop dates locked:** July 14-15 (Atlanta office)

## Open action items (top 18)
- [ ] **[Vendor, URGENT]** Schedule POC kickoff workshop with Priya's 10-20 core engineers  _[cust/acme/context/open-items.md]_
- [ ] **[Acme, Priya]** Confirm 8 attendee list for the workshop, Due: 2026-06-22  _[cust/acme/context/open-items.md]_
...

## Recent decisions / owners / blockers
## Topics covered
## Most recent notes

Run the brief from a nightly job. Use 8,000 tokens for most branches and 12,000 for very large ones. Smaller briefs save more but make the agent go back to the source files more often, which cancels the saving. If you route through a smaller model, give the agent a tool to open source files and tell it to use the tool whenever the brief does not clearly answer.

Changed in v0.4.1 ws query now never returns more than its token budget. A note too large to fit comes back as a short matching excerpt. Searches with several words also return notes that match only some of them, ranked after full matches. "Latest status" is chosen by the date in the status heading, not by when a file was last edited. Indexing no longer fails on notes whose front matter uses dates as keys. Full list in the release notes.

Where it came from

The working-set framing, the layered model (full store, search index, active slice, pre-computed summary) and the 3,000-token compaction threshold come from Derek Thomas's ContextForge. ContextForge is a Python SDK that works with hosted APIs (OpenAI, Azure OpenAI, Anthropic) and local servers. It is built for code and multi-turn agent sessions; workingset applies the same ideas to a folder of markdown notes, before the API call. I wrote a separate review: ContextForge: how to give a coding agent a codebase without drowning it in tokens. For the smallest version of the pattern, retrieval that says "not found" instead of guessing, see groundedwork.

The full per-model, per-question results are in the benchmark dashboard. A small benchmark you can run yourself against the example vault is in the repository under bench/. If you are building agents over large markdown notes, try workingset, and open an issue if it gives a wrong answer on your own trap questions.

If this article was useful to you and you want to support more writing like it, you can sponsor my work on GitHub. 100% goes to me, no platform fees.

← More writing at Beyond AI