This post is part of the Agentic Developer: Zero to Advanced series. It stands on its own: it uses the synthetic void data from Part 2 and the reports the pipeline from Part 7 wrote, but you do not need to have read either. The earlier Retrieval and RAG post is about what to embed. This one is about what happens to an agent’s context window once retrieval is a tool.
LangChain published Context Engineering Starts with Retrieval (Jess Weng, 23 October 2026). Its claim: an agent cannot hold everything in its head, so the harness (the code around the model) has to bring “the right context at the right time”. It names three ways retrieval goes wrong, and a five-item checklist:
- Inefficient searching. Use the cheapest tool that works: grep beats embeddings when you know the words.
- Too much context at once. Page through files, and when a tool result is huge, save it to a file and give the model a path and a short preview.
- Irrelevant or stale context. Hand retrieved files to a sub-agent that reads one and reports only the facts.
I wanted to know what each item is worth on a real archive, on a local model, with exact answers to check against. So I built a small harness with each idea as a switch, and ran it. The headline is not “the checklist wins”. It is “the checklist wins when the archive does not fit, and it loses when it does.” Details below, including a bug in my own first run that flipped one of the results.
A bug in my first pass, and what it changed. My first run capped the model’s answer at 1,200 tokens. When a model that has all 200 reports in front of it counts by writing out “report 1: no match, report 2: no match…” it needs far more than that, and it was cut off mid-list. That scored the “stuff everything in the prompt” arm 0 of 3 on the counting task and 1 of 3 on the find-by-meaning task. With the cap at 8,000 tokens it scores 3 of 3 and 3 of 3. I noticed because the saved answers all ended mid-sentence at exactly 1,200 completion tokens. I reran both tasks for every strategy, and kept the old results in out/old_ctx_eval_runs_maxtok1200.json. The other arms moved by noise only (one went from 2 of 3 to 1 of 3 with the identical setup), which is a reminder of how small these samples are. Lesson, again: look at the individual saved answers, not just the score.
Every section of this post has a literate notebook next to the code: the idea in plain words with a daily-life picture, the real source, and cells you can run. Outputs are saved from a real run, and GitHub shows them without installing anything.
| Notebook | Covers | Needs the model server? |
|---|---|---|
14a_desk_and_archive |
the desk and the library, the archive | no |
14b_harness_switches |
the five switches, the offload rule, three live runs | yes (about 15 s) |
14c_results_t1_to_t5 |
the results table, charts, the 1,200-token bug | no, reads the saved runs |
14d_subagent_brief |
T6, the sub-agent brief | yes (2 calls) |
14e_two_frameworks |
Deep Agents and Google ADK | no, reads the saved runs |
14f_loops_guard_sandbox |
the loops, the repeat guard, the shell sandbox | Docker only |
The framework runs took hours, so 14c and 14e read their saved results instead of repeating them. Each notebook says which parts are live and which are read from files.
0.1 The desk and the library
Explain like I’m five. You are doing an open-book exam, and the library has ten thousand books. Your desk fits three. You cannot carry the library to your desk. So the skill is not “know everything”, it is “fetch the right three pages”. Three things go wrong: you look in the wrong place, you drag back a whole encyclopedia when you needed one paragraph, or you fetch an old edition that contradicts the new one.

Explain like I’m a data scientist. The context window is a fixed token budget (this server’s is 65,536). Retrieval is a set of tools that move data from a store into that budget. Each tool decides how much of the store crosses over, so the design question is the size and the relevance of what a tool returns. Everything the model sees is re-sent on every step of the loop, so a big tool result is paid for again on each later call.
The desk, the library, and what the harness decides: 26 seconds.
0.2 Jargon, explained simply
| Term | What it means | Daily-life version |
|---|---|---|
| Context window | The most tokens the model can read in one go. | The desk. |
| Harness | The code around the model that decides which tools it has and what they return. | The librarian’s rules: what you may carry, and in what size. |
| Payload | The full content a tool returns. | Carrying the whole book. |
| Pointer / path | A file path instead of the content. | A sticky note saying “shelf 4, third book”. |
| Offloading | When a result is too big, save it to a file and give the model the path plus a preview. | “This box is too heavy; it is in the hallway. Here is the label on the lid.” |
| Paged read | Read 100 lines at a time with an offset. | Reading a chapter, not the library. |
| grep / glob | Search file contents / file names without opening the files. | The index at the back of a book. |
| Semantic search | Find things by meaning, using embeddings (vectors). | Asking the librarian “something about unsure inspectors”, not for an exact title. |
| Sub-agent | A fresh model call that gets one small job and a small brief, and returns a short answer. | An assistant who reads one report and tells you the two facts that matter. |
| Context rot | A model getting worse as its context gets longer or noisier. | Studying with the TV on. |
| Stuffing | Pasting everything into the prompt and skipping retrieval. | Carrying the whole shelf to the desk, if the shelf fits. |
0.3 The archive
Everything the agent searches is a real file from this series, in out/ctx_archive/:
exports/inspection_log.csv: the 4,000-row table of the synthetic void dataset. About 454 KB, about 113,500 tokens (characters divided by four), so it does not fit in a 65,536-token window. This stands in for the article’s 40,000-row market export.reports/test_00000.mdtotest_00199.md: the 200 per-image reports that the Part 7 pipeline actually wrote (verdict, kept and rejected candidates, and the model-written note). About 91 KB, roughly 23,000 tokens by that estimate (the server counted 28,300 with a question attached), so it does fit.policy/: two dated policy files. The current one holds the constants frompipeline_graph.py. The other is planted by me: a “superseded” policy with a different minimum area (35 px instead of 20). It exists only to test whether an agent reads an old document. It is not a real earlier policy.- Later,
cases/: 600 per-image records for the visual-search experiment.
notebooks/agentic-zero-to-advanced/ctx_corpus.py
def report_markdown(r: dict) -> str:
rep = r["report"]
lines = [f"# Report {r['image']}", "",
f"verdict: {rep['verdict']}",
f"candidates_examined: {r['n_candidates']}",
f"candidates_kept: {r['kept']}",
f"reason: {rep['reason']}", ""]
for k in rep["kept"]:
lines.append(f"- kept candidate {k['id']}: severity {k['severity']}, confidence {k['confidence']}, "
f"uncertain {str(k['uncertain']).lower()}, contrast {k['contrast']}, area_px {k['area_px']}")
...0.4 The harness: five ideas, five switches
I wrote about 150 lines of Python in the style of the Part 5 loop (ctx_harness.py). It is not the Deep Agents library the article uses; it is the smallest thing that lets me turn each idea on and off. Later in the post (the same tasks through Deep Agents and Google ADK) I run the same questions through the real libraries. The model is a local Qwen3.8-27B (FP8) behind vLLM, temperature 0, thinking off.
grep/glob(switchgrep): search names and contents without loading files.- Paged
read_file(paged): 100 lines at a time, withoffset. - Offload (
offload): any tool result over 2,000 estimated tokens is written tolarge_tool_results/, and the model gets the path plus the first and last five lines. - Paths, not payloads (
search_returns_paths): the semantic search tool returns file paths, not the reports. - Delegate (
delegate): ananalyze(path, question)tool that runs a fresh model call that sees only that one file and the question.
notebooks/agentic-zero-to-advanced/ctx_harness.py
@dataclass(frozen=True)
class Config:
name: str
grep: bool = False
paged: bool = False
offload: bool = False
search_returns_paths: bool = False
delegate: bool = False
visual: bool = False # adds similar_cases (DINOv3 + LanceDB), see ctx_visual.py
grep_tools: bool = True # False = search-only arm: the model may not use grep/globThe offload rule is eight lines, and it has one trap that I walked into: the offloaded file is itself read back with read_file, so if a paged read of an offloaded file were offloaded again, the loop would never end. Paged reads are already bounded, so they are exempt.
notebooks/agentic-zero-to-advanced/ctx_harness.py
# a paged read is already bounded (100 lines), so it is never offloaded again (that would loop forever)
if cfg.offload and est_tokens(out) > OFFLOAD_TOKENS and not (name == "read_file" and cfg.paged):
off_dir.mkdir(exist_ok=True)
f = off_dir / f"{call.id}.txt"
f.write_text(out)
lines = out.splitlines()
return (f"[result too large (~{est_tokens(out)} tokens); saved to {_rel(f)}. Use grep or read_file on it.]\n"
+ "\n".join(lines[:5]) + "\n...\n" + "\n".join(lines[-5:]))The article offloads at about 20,000 tokens. Mine is 2,000, because my window is 65,536 and I wanted the rule to trigger on this data. That is a deliberate scaling, not a copy of their number.
The six strategies:
| Strategy | What the agent gets |
|---|---|
| stuff | Everything the question is about, pasted into the prompt, no tools. This is the best case for stuffing: it is handed the right folder. |
| payload | Tools that return whole things: whole files, whole documents from search. |
| harness | grep, glob, paged reads, offloading, search returns paths. |
| harness + delegate | The same, plus the analyze sub-agent. |
| search-paths (T4 only) | Semantic search returning paths and paged reads, with grep and glob taken away. |
| search-paths + delegate (T4 only) | The same, plus the sub-agent. |
Every question has an exact answer computed from the same files, so a run is simply right or wrong. Counts: 6 needle lookups, 3 counts, 2 policy questions, 3 find-by-meaning, 3 visual-neighbour questions.
0.5 Results
One table, every run. “Overflow” means the request exceeded the 65,536-token window and the server refused it. “Cap” means the agent used all 14 steps without answering. “Mean total tokens” is the sum of prompt tokens over all steps (plus sub-agent tokens), so it is what the loop actually re-reads.
| Task | Strategy | Correct | Overflow | Step cap | Largest single prompt | Mean total tokens |
|---|---|---|---|---|---|---|
| T1 needle in 4,000-row CSV | stuff | 0 / 6 | 6 | 0 | n/a | n/a |
| payload | 0 / 6 | 6 | 0 | 543 | 543 | |
| harness | 6 / 6 | 0 | 0 | 1,270 | 2,834 | |
| harness + delegate | 3 / 6 | 0 | 3 | 3,343 | 16,535 | |
| T2 count across 200 reports | stuff | 3 / 3 | 0 | 0 | 28,300 | 28,300 |
| payload | 0 / 3 | 2 | 1 | 14,879 | 34,771 | |
| harness | 0 / 3 | 0 | 3 | 29,514 | 111,476 | |
| harness + delegate | 0 / 3 | 0 | 3 | 8,118 | 65,201 | |
| T3 current vs stale policy | stuff | 2 / 2 | 0 | 0 | 453 | 452 |
| payload | 1 / 2 | 1 | 0 | 1,146 | 2,294 | |
| harness | 1 / 2 | 0 | 1 | 14,841 | 53,299 | |
| harness + delegate | 1 / 2 | 0 | 1 | 14,575 | 51,633 | |
| T4 4 reports by meaning | stuff | 3 / 3 | 0 | 0 | 28,312 | 28,308 |
| payload | 1 / 3 | 1 | 1 | 18,407 | 46,309 | |
| harness | 1 / 3 | 0 | 2 | 12,492 | 55,062 | |
| harness + delegate | 1 / 3 | 0 | 2 | 13,734 | 59,362 | |
| search-paths | 2 / 3 | 0 | 1 | 7,631 | 43,245 | |
| search-paths + delegate | 2 / 3 | 0 | 1 | 6,030 | 27,303 | |
| T5 20 visual neighbours | payload | 3 / 3 | 0 | 0 | 3,357 | 3,949 |
| harness | 3 / 3 | 0 | 0 | 4,355 | 9,519 | |
| harness + delegate | 2 / 3 | 0 | 1 | 6,077 | 30,710 |
Show output (the script that prints this table)
task strategy ok ovfl cap peak tok mean total tok s
T1 stuff 0/6 6 0 0 0 0.4
T1 payload 0/6 6 0 543 543 2.3
T1 harness 6/6 0 0 1270 2834 9.4
T1 harness+delegate 3/6 0 3 3343 16535 45.3
T2 stuff 3/3 0 0 28300 28300 145.7
T2 payload 0/3 2 1 14879 34771 44.4
T2 harness 0/3 0 3 29514 111476 107.5
T2 harness+delegate 0/3 0 3 8118 65201 84.4
T3 stuff 2/2 0 0 453 452 10.2
T3 payload 1/2 1 0 1146 2294 11.6
T3 harness 1/2 0 1 14841 53299 51.8
T3 harness+delegate 1/2 0 1 14575 51633 56.1
T4 stuff 3/3 0 0 28312 28308 151.8
T4 payload 1/3 1 1 18407 46309 53.0
T4 harness 1/3 0 2 12492 55062 80.1
T4 harness+delegate 1/3 0 2 13734 59362 104.6
T4 search-paths 2/3 0 1 7631 43245 92.6
T4 search-paths+delegate 2/3 0 1 6030 27303 109.5
T5 v-payload 3/3 0 0 3357 3949 56.1
T5 v-harness 3/3 0 0 4355 9519 163.4
T5 v-harness+delegate 2/3 0 1 6077 30710 222.8
T6: sub-agent brief size (12 target reports, exact verdict + confidence)
narrow 12/12 mean prompt tokens 364
pad20-start 12/12 mean prompt tokens 2946
pad20-middle 12/12 mean prompt tokens 2946
pad20-end 12/12 mean prompt tokens 2946
pad100-start 12/12 mean prompt tokens 13924
pad100-middle 12/12 mean prompt tokens 13924
pad100-end 12/12 mean prompt tokens 13924Pick a task and see the same data as bars. The red line is the 65,536-token window; a bar that would cross it is a run that overflowed.
0.5.1 T1: the needle in the big CSV, where the checklist earns its keep
The needle task in 28 seconds, with the real token counts.
“What are n_voids and void_area for the row with id test_00xyz?” The CSV is about 113,500 tokens.
- Stuffing and whole-file tools both fail, every time. Stuffing is refused outright (6 of 6 overflow). The
payloadagent callsread_fileon the CSV and the whole thing is refused (6 of 6). - The harness gets 6 of 6, in three steps, with 2,834 tokens in total. It greps the id, reads the lines around the match, and answers. That is about one fortieth of the CSV, and it is the exact picture the article paints for “find Company B’s quarterly revenue in a 40,000-row export”.
- Adding the sub-agent made it worse: 3 of 6, and 16,535 tokens on average. On the three failures the model did not even call
analyze. It grepped once and then paged throughread_filethirteen times until it hit the 14-step cap. Same tools as the 6-of-6 arm plus one more, and the behavior changed. I did not find out why, and I would not read a mechanism into it.
0.5.2 T2: counting across 200 reports, and what the first bug taught me
Why the first bug made the wrong strategy look best: 25 seconds.
“How many reports have verdict escalate AND at least one kept candidate with severity critical?”
- Stuffing is right 3 of 3, at 28,300 tokens and about 146 seconds a question. All 200 reports fit, and a 27B model reading ~23,000 tokens and counting step by step got exact counts. That surprised me, and it is the result my first bug had hidden.
- Every tool arm is 0 of 3. The harness arms ran out of the 14-step cap: grep for the verdict, grep for the severity, and then they have to intersect two lists by hand.
payloadoverflowed on two and capped on one. - This is a job for plain code, as in Part 8: a ten-line script over the files gives the count instantly and exactly. I did not give any strategy a code-execution tool, so that is a prediction, not a measurement. The retrieval question here is not “how do I fetch it” but “should a language model be counting at all”.
0.5.3 T3: the stale policy
Two policy files, one marked SUPERSEDED. Every run that finished chose the current one. The three that failed did so for other reasons: one payload run overflowed after reading whole files, and one harness run (and its delegate twin) ran grep ten times, had four results large enough to be offloaded, and hit the cap. I did not inspect which patterns it used.
So this test, as I built it, was too easy: both files are small, both are visible in ls, and one says “SUPERSEDED” in its first lines. It says nothing about the harder case, an unmarked stale document inside a large index. I did not test that.
0.5.4 T4: find four reports by meaning
“Find 4 reports where a human should double-check a defect the model was unsure about.” The gold is the set of reports whose record says a kept candidate is uncertain, so the text and the answer do not share keywords the way a grep wants.
- Stuffing is 3 of 3 (28,308 tokens).
- Of the tool arms, search-paths is 2 of 3 (the semantic search returns paths, paged reads, no grep), and search-paths + delegate is also 2 of 3 at 27,303 tokens, the cheapest tool arm and about the same as stuffing.
- The arms that had grep were no better (1 of 3). The model reached for grep first, spent its steps there (grep 10 to 14 times on the failing runs), and ran out of steps. The article’s “grep beats embeddings when you know the words” is right; here the question was phrased as an idea, so the words were not the right ones.
One honest observation about cost: the tool arms do not use fewer tokens than stuffing here, because the agent loop re-sends its whole history on every step. Prompt caching in the server could change what that costs in time or money. I counted tokens, not dollars, and I did not measure caching.
0.5.5 T5: visual retrieval with DINOv3 and LanceDB
You asked whether the same pattern works with an image search tool, so I plugged one in. The embedding model is vit_small_patch16_dinov3.lvd1689m (DINOv3, small, from timm) over the 600 test images, in a LanceDB table. The tool is similar_cases(image_id, k), and like the text search it can return the full case records (payload) or just paths under cases/ (the article’s “retrieve, then offload”).
notebooks/agentic-zero-to-advanced/ctx_visual.py
def neighbours(image_id: str, k: int) -> list[str]:
t = table()
q = t.search().where(f"id = '{image_id}'").limit(1).select(["vector"]).to_list()[0]["vector"]
hits = t.search(q).metric("cosine").limit(k + 1).select(["id"]).to_list()
return [h["id"] for h in hits if h["id"] != image_id][:k] # the query image is its own nearest neighbourDoes the retrieval itself work on this data? I measured it separately, because scoring an agent on neighbours that I defined as correct would be circular. Of the 4 nearest neighbours of each of the 600 test images, how many are the same kind of part (shoe, bag, coat…)?
Show output
72.8% against 10.1% for random neighbours. DINOv3 clusters these synthetic X-rays by part shape, about seven times better than chance, even though it was never trained on them.
Does the harness help? The task: “get the 20 test images most similar to test_00010, and count how many have 3 or more voids” (and two siblings). Each record is small, so 20 of them is only about 3,000 tokens.
- The plain payload arm is the best one: 3 of 3 and 3,949 tokens. The harness is also 3 of 3 but costs 9,519. The delegate arm is 2 of 3 and 30,710.
- That is the honest reading: when the results are small, the machinery is overhead. The article’s checklist is about results that are big, and 20 small records are not.
0.5.6 T6: how much should a sub-agent be given?
The sub-agent brief and the visual-search numbers: 36 seconds.
You asked about this directly: a sub-agent given a lot of context may degrade (“context rot”), so give it only what the small job needs. I tested it, with the same analyst and the same question about one report (its verdict and the confidence of its first kept candidate), in three briefs: just that report, that report plus 19 others, and that report plus 99 others. For the padded briefs the target sits at the start, middle or end. 12 target reports, 84 runs.
| Brief | Correct | Mean prompt tokens |
|---|---|---|
| Narrow: the target report only | 12 / 12 | 364 |
| 20 reports, target at start / middle / end | 12 / 12 each | 2,946 |
| 100 reports, target at start / middle / end | 12 / 12 each | 13,924 |
No context rot showed up, at this scale and with this model. All 84 runs were exactly right, including with the target in the middle of 100 reports. What the narrow brief bought was 38 times fewer tokens (364 against 13,924), and with it the time and money, and a smaller surface for injected text.
That is not evidence that rot does not exist. It means 14,000 tokens is not enough to trigger it for a 27B model on a task where the answer is two exact lines. Published rot results are at much longer lengths and on fuzzier questions. I would still give a sub-agent the narrow brief, because it is free and it is the version that scales, but this experiment gives you a reason of cost, not of accuracy.
0.6 The same tasks through two real frameworks: Deep Agents and Google ADK
The first version of this post said “I did not use the Deep Agents library”. That was the weakest part, so I ran the same 17 questions through it, and through Google ADK as well, because Google ADK is a common choice for teams working in company settings. Same model, same questions, same scoring. Nothing else about the task changed.
The kitchen picture. My harness is cooking at home from a short recipe: I chose every pan. Deep Agents is a meal kit: it arrives with its own pans, a recipe book (a long built-in system prompt), a timer for big dishes (offloading results over 20,000 tokens) and a sous-chef you can call (the task sub-agent tool). ADK is a professional kitchen with the stove and the sous-chef system included, but with the pans mostly left for you to bring, and a set of “environment” pans that are still labelled experimental.
| Word | Everyday meaning | In code |
|---|---|---|
| Framework | The kitchen that comes with the pans and the rules. | deepagents, google-adk |
| Middleware | A checkpoint every order passes through before it reaches the cook. | Deep Agents’ ModelCallLimitMiddleware; my repeat guard below |
| Sub-agent as a tool | Calling the sous-chef like any other utensil. | Deep Agents’ task tool; ADK’s AgentTool |
| Toolset | A drawer of pans sold together. | ADK’s EnvironmentToolset |
| Sandbox | A test kitchen with no door to the street, so a mistake stays inside. | A Docker container with no network |
| Call cap | The oven timer that rings even if you forgot. | 40 model calls in both frameworks |
0.6.1 What each one ships
I read the installed packages instead of trusting my memory (ADK 2.13, Deep Agents 0.7.32):
- Deep Agents brings its own
ls,read_file,glob,grep, a planning tool, thetasksub-agent tool, and offloading of tool results over 20,000 tokens by default. You add your own tools next to those. - ADK brings no file tools in the basic agent (you pass plain Python functions as tools; the type hints and docstring become the schema), a sub-agent wrapper (
AgentTool) and a call limit (RunConfig.max_llm_calls, default 500). In ADK 2.13 there is also an experimentalEnvironmentToolset:Execute(a shell),ReadFile,EditFile,WriteFile. I did not find a built-in “save this big result to a file” step; ADK has event compaction, which I did not switch on.
So I ran ADK two ways: with my own harness tools wrapped as functions (the fair comparison for the framework), and with its own toolset (what a new ADK user gets). Here is the whole agent in ADK, the part you would copy:
from google.adk.agents import LlmAgent
from google.adk.models.lite_llm import LiteLlm # any OpenAI-compatible server, here the local vLLM
from google.adk.tools.agent_tool import AgentTool
model = LiteLlm(model="openai/qwen3.8-27b", api_base=BASE_URL, api_key=KEY, temperature=0,
max_tokens=8000, extra_body={"chat_template_kwargs": {"enable_thinking": False}})
analyst = LlmAgent(name="chunk_analyst", model=model, instruction=ANALYST_SYSTEM, tools=[read_file],
description="Reads ONE file and returns only the facts that bear on the question.")
agent = LlmAgent(name="archive_agent", model=model, instruction=SYSTEM,
tools=[ls, glob, grep, read_file, search_reports, similar_cases, AgentTool(agent=analyst)])
# run with RunConfig(max_llm_calls=40); every tool is a plain function with type hints and a docstring0.6.2 Results
All figures are correct answers out of the questions in the column header. The “cap” and “overflow” columns count runs that ended without an answer. My harness row uses the best set-up per task (named in the script), which flatters it, and its cap was 14 model calls; both frameworks had 40.
| Set-up | T1 needle (6) | T2 count (3) | T3 stale (2) | T4 meaning (3) | T5 visual (3) | Total /17 | Hit the call cap | Overflowed the window |
|---|---|---|---|---|---|---|---|---|
| My harness (best set-up per task) | 6 | 0 | 1 | 2 | 3 | 12 | 5 | 0 |
| Deep Agents | 4 | 0 | 1 | 0 | 1 | 6 | 11 | 0 |
| Deep Agents + chunk sub-agent | 5 | 1 | 1 | 0 | 3 | 10 | 7 | 0 |
| ADK, my tools | 6 | 0 | 0 | 1 | 2 | 9 | 7 | 1 |
| ADK, my tools + chunk sub-agent | 5 | 0 | 1 | 1 | 1 | 8 | 5 | 3 |
| ADK, its own toolset (sandboxed) | 6 | 1 | 2 | 1 | 2 | 12 | 5 | 0 |
And what a correct answer on the needle task cost, in prompt tokens summed over all steps:
| Set-up | Prompt tokens per correct T1 run |
|---|---|
| My harness | 2,834 |
| ADK, my tools | 5,056 |
| ADK, its own toolset | 8,137 |
| Deep Agents + chunk sub-agent | 11,132 |
| Deep Agents | 13,099 |
| ADK, my tools + chunk sub-agent | 40,197 |
For comparison, pasting the 23,000-token reports in scored 3 of 3, 2 of 2 and 3 of 3 on T2, T3 and T4; no framework matched that on counting or on finding reports by meaning.
Show output of ctx_frameworks.py
harness (best set-up per task) 12/17 cap= 5 overflow=0 T1:6/6 T2:0/3 T3:1/2 T4:2/3 T5:3/3
tokens per correct run: {'T1': 2834, 'T2': None, 'T3': 2876, 'T4': 43225, 'T5': 9519}
Deep Agents 6/17 cap=11 overflow=0 T1:4/6 T2:0/3 T3:1/2 T4:0/3 T5:1/3
tokens per correct run: {'T1': 13099, 'T2': None, 'T3': 9601, 'T4': None, 'T5': 21599}
Deep Agents + chunk sub-agent 10/17 cap= 7 overflow=0 T1:5/6 T2:1/3 T3:1/2 T4:0/3 T5:3/3
tokens per correct run: {'T1': 11132, 'T2': 375853, 'T3': 9730, 'T4': None, 'T5': 47891}
ADK, my tools 9/17 cap= 7 overflow=1 T1:6/6 T2:0/3 T3:0/2 T4:1/3 T5:2/3
tokens per correct run: {'T1': 5056, 'T2': None, 'T3': None, 'T4': 26243, 'T5': 26650}
ADK, my tools + chunk sub-agent 8/17 cap= 5 overflow=3 T1:5/6 T2:0/3 T3:1/2 T4:1/3 T5:1/3
tokens per correct run: {'T1': 40197, 'T2': None, 'T3': 3831, 'T4': 22735, 'T5': 102573}
ADK, its own toolset (sandboxed) 12/17 cap= 5 overflow=0 T1:6/6 T2:1/3 T3:2/2 T4:1/3 T5:2/3
tokens per correct run: {'T1': 8137, 'T2': 29023, 'T3': 39259, 'T4': 19069, 'T5': 10240}
stuff {'T1': {'n': 6, 'correct': 0}, 'T2': {'n': 3, 'correct': 3}, 'T3': {'n': 2, 'correct': 2}, 'T4': {'n': 3, 'correct': 3}}
guard [{'strategy': 'da', 'correct': False, 'calls': 7, 'tokens': 25270, 'status': 'no_answer'}, {'strategy': 'da+chunk', 'correct': True, 'calls': 4, 'tokens': 13270, 'status': 'ok'}, {'strategy': 'da', 'correct': False, 'calls': 33, 'tokens': 711047, 'status': 'error: GraphRecursionError: Re'}]The framework comparison in 68 seconds: my loop, Deep Agents and Google ADK.
0.6.3 What I found
- The framework mattered less than the tools it was given. ADK with my own tools and with its own toolset scored 9 and 12; the same ADK with my tools plus a sub-agent scored 8. Deep Agents scored 6 and 10. Differences of one or two runs are noise at this sample size, so read the table as “no framework is clearly ahead”, not as a ranking.
- The runs that failed mostly did not give a wrong answer; they never gave one. Deep Agents hit the call cap on 18 of its 34 runs, ADK with my tools on 12 and overflowed the window on 4 more. The cap is the oven timer: even with it, one capped T1 run in Deep Agents used about 940,000 prompt tokens in total, with a peak of 57,000 against the 65,536 window.
- I read the loops. In a T1 run, Deep Agents had the answer after its first search (the row with
n_voids1 andvoid_area93) and the next call showed the header, so the columns were known. It then re-read the same line for about 11 more calls, alternatinglimit: 1andlimit: 2, and never answered. The four correct runs of the same question took 3 to 4 calls. I do not know why the model would not commit. In a T3 run the agent found the current policy and then searched for smaller areas one number at a time (area_px 19,18,17, …), ten searches per step, until its window was full. - A repeat guard saves tokens; it does not fix the loop. I added a middleware that ends the run when the same tool call has been made three times, and re-ran the three looping T1 questions. One stopped after 7 calls and 25,270 tokens instead of burning a window, but with no answer. One answered correctly in 4 calls. One still ran into the recursion limit (33 calls, 711,047 tokens). My first version of the guard required three identical calls in a row and never fired, because the loop alternated between two calls; those runs are kept as
old_ctx_da_guard_consecutive_rule.json. Temperature 0 on this server is not bit-exact, so the one that flipped to correct is not proof that the guard helped. - ADK’s own toolset is a real shell, and I nearly ran it on my machine.
Executeruns a command with my user’s rights and a copy of all my environment variables, API keys included. A security check flagged it, and it was right. I stopped that run, set its partial results aside (old_ctx_adk_env_unsandboxed_runs.json) and re-ran the whole arm with every command inside a throwaway Alpine container: no network, read-only root, all capabilities dropped, memory and process limits, only a copy of the archive mounted, no environment variables. I checked from inside it that the container could not see a test secret, could not reach the model server, could not write to/usr, and could not read/root. A side effect: the shell is busyboxsh, not bash. Whatever framework you use, a model with a shell wants this boundary. - A tool that raises can end the whole run. My first ADK wrapper let a
FileNotFoundErrorfromread_fileescape, which killed one run. My own harness hands the error back to the model as text, so I wrapped the ADK tools the same way and redid that run. This is a harness decision, not a model one. - Offloading is a setting you have to choose. Deep Agents offloads tool results over 20,000 tokens by default; I set my own harness to 2,000 because the window here is 65,536. I did not measure how often Deep Agents’ offloading fired. ADK, in my runs (no offloading step), overflowed the window four times.
0.6.4 What this does not tell you
It was one local model (Qwen3.8-27B through an OpenAI-compatible server), not Gemini. ADK is built around Gemini; I did not test it. A hosted model with a longer window and stronger tool use could behave very differently, and the two frameworks’ defaults were designed for that. The takeaways below are about how to measure, not about which framework to buy.
0.7 Best practices, and how they connect to the rest of the series
The practices and where each meets an earlier part: 50 seconds.
The article ends on a checklist. Here it is again, next to what my runs say about each item and where it meets something we already built. “Not tested” means exactly that: I am repeating the article’s advice, not backing it with a measurement.
| Practice (the article’s checklist, plus two of mine) | What my runs say | Where it connects to this series |
|---|---|---|
| 1. Use the cheapest tool that works. Grep beats embeddings when you know the words. | Confirmed on the CSV needle (grep + paged read, 6 of 6). Weaker when the question was phrased as an idea (T4): the arms with grep were 1 of 3, the semantic-only arm 2 of 3. | Same rule as Part 8, tool design: plain code, then a small typed model, then a language model, by what each step needs. Search tools are one more place that rule applies. |
| 2. Check whether it fits before you retrieve (mine). | Pasting 23,000 tokens beat every tool setup on counting and by-meaning (3 of 3 each). Only the 113,500-token table forced retrieval. | In Part 5 the agent’s only memory is the message list. Everything in it is re-read at every step, which is one reason a tool loop cost more tokens than one big prompt here. |
| 3. Page instead of loading. | The single biggest effect: 6 of 6 at about 1/40 of the table’s tokens, against 0 of 6 for whole-file reads. | The Part 7 pipeline works one frame at a time with a small record per frame, so it never faces a whole archive. |
| 4. Return pointers, not payloads, once results are big. | Only true when results are big. At 20 small records the plain payload was cheapest (3,949 tokens against 9,519). | A threshold, not a rule. Mine was 2,000 tokens, the article’s about 20,000. Set yours from your window and your data. |
| 5. Delegate the reading with a narrow brief. | 38x fewer tokens for the narrow brief and no accuracy difference up to 14,000 tokens (84 of 84). But adding the sub-agent tool to the needle task made a 6-of-6 agent 3 of 6. In the frameworks it was mixed: Deep Agents 6 to 10 of 17, ADK with my tools 9 to 8. | The Part 7 split, “code owns the order, the model owns two judgments”, applied to context: the harness decides what crosses over, the model decides what to ask for. |
| 6. Look at individual answers, not just the score (mine). | It caught my 1,200-token cap bug, which had made the best strategy look like the worst. | The same lesson as the token-probability bug in Part 8: summary numbers will not tell you the harness is broken. |
| 7. Use plain code for counting and joining. | Every tool setup scored 0 of 3 on the count. Not measured: a script. | Part 8 again: when a step is arithmetic, it is not a model’s job. |
| 8. Package search know-how as a skill (progressive disclosure). | Not tested. | Planned for the Deep Agents posts in Arc 3. |
| 9. Treat retrieved text as untrusted, and verify answers against the source. | Not tested. | Planned for the security post in Arc 6. |
| 10. Prefer fresh sources, filter by date. | Not really tested: my stale file was labelled “superseded”, which made it easy. | Part 9 (human review) is the planned place for cases the system cannot settle by itself. |
| 11. Cap your loops, and put a repeat guard next to the cap (mine). | Deep Agents hit the 40-call cap on 18 of 34 runs; ADK with my tools on 12 of 34. A repeat guard saved tokens in one of three looping runs and did not rescue the answer. | The Part 5 loop already had a step limit; the frameworks make it a setting (RunConfig.max_llm_calls, a middleware). Set it, and log the runs that hit it. |
| 12. A model with a shell needs a boundary (mine). | ADK’s Execute ran with my user’s rights and a copy of my environment variables. Re-run inside a no-network Docker container with nothing mounted but a copy of the data. |
The security post on the roadmap. Treat it the way the article treats retrieved text: untrusted. |
One more connection, to the thing that is still missing in this series: the article’s own next question is how an agent manages its context after retrieval, as a long task piles up. In this series that is the memory post on the roadmap, together with a longer look at Deep Agents, and this post’s numbers (what a tool result costs on every later step) are the reason those matter.
0.8 What I take from this
- Check whether it fits before you build anything. The 200 reports were 23,000 tokens; pasting them in beat every retrieval setup I built on counting and by-meaning questions. The 4,000-row CSV was about 113,500 tokens; only the harness worked. The checklist is the plan for when the corpus stops fitting.
- For a needle in a big file, grep + paged read is not a nicety, it is the whole difference (6 of 6 against 0 of 6, at about one fortieth of the tokens).
- A sub-agent is not free. Where the answer was in one file, the narrow brief was perfect and 38x cheaper than a padded one. But adding an
analyzetool to the needle task made a 6-of-6 agent a 3-of-6 one, and I do not know why. - Counting is for code. The model got T2 right only by reading everything, and the tool arms failed. A script would not have needed any of this.
- Small results do not need offloading. On the visual task, the plain payload beat the harness.
- Your first bug will be in the harness. Mine was a token cap that made one strategy look bad.
- The tools and the caps mattered more than the framework. On the same model and questions, Deep Agents scored 6 and 10 of 17, ADK with my tools 9 and 8, ADK with its own toolset 12, my harness 12 (with its best set-up per task). The failures were mostly runs that never answered, not wrong answers: set a cap and read the loops.
- Put the shell in a box. ADK’s
Executeinherits your environment variables. Run it where a mistake stays inside.
0.9 Jargon, once more, in one sentence each
- Stuff: put it all in the prompt. Fine until it does not fit.
- Offload: if a result is huge, save it to a file and pass the path.
- Delegate: let a fresh, small-brief call read one file and report back.
0.10 What I did not do
- The framework comparison is small and local. 17 questions per set-up, one run each, one model (Qwen3.8-27B through an OpenAI-compatible server), not Gemini, so nothing here says how ADK behaves with the model it was designed for. My harness row takes the best set-up per task and had a 14-call cap; Deep Agents and ADK had 40. Differences of one or two runs are noise.
- ADK’s
EnvironmentToolsetis experimental in 2.13, and my sandboxed shell is busybox, not bash. I did not enable ADK’s event compaction or look at Deep Agents’ summarization, and I did not measure how often Deep Agents’ offloading fired. - My repeat guard was wrong the first time (three identical calls in a row, which never happened) and the re-test has three runs. The guard changed one outcome at most.
- One model, one setting: Qwen3.8-27B (FP8), temperature 0, thinking off, on a GPU shared with other jobs. A different model, or thinking on, could change every ranking.
- Small samples. 6, 3, 2, 3 and 3 questions per task. One run is 33 percentage points on T2 and T4. With the same configuration, T4’s harness + delegate arm went from 2 of 3 to 1 of 3 between my two passes, so differences of one run are noise.
- A 14-step cap. Many tool failures are “ran out of steps”, not “gave a wrong answer”. A larger cap would change T2 and T4 and I did not try one.
- Offload threshold scaled to 2,000 tokens (the article uses about 20,000), because my window is 65,536.
- Tokens, not dollars. The loop re-reads its history every step. If the server caches shared prefixes, the cost of the tool arms is lower than the token count says. I did not measure it. Latency was on a shared GPU.
- The stale-document test was easy (two small files, one labelled “superseded”). An unmarked stale file inside a large index is untested.
- Prompt injection through retrieved text (the article’s last point) is untested here.
- T2 and T4 were run twice, because of the token-cap bug described at the top. T1, T3 and T5 were run once; their failures were overflow or step-cap, not truncated answers.
- T6 stops at about 14,000 tokens. I did not go longer, and I used a task with two exact facts.
- For T5, the agent is scored on reading neighbour records, not on whether the neighbours are the “right” ones. Retrieval quality is the separate 72.8% against 10.1% check, and it only compares part class, not defect similarity.
0.11 Reproduce this yourself
Show reproduce commands
cd quarto_blog_hasan/notebooks/agentic-zero-to-advanced
uv sync # lancedb and timm are in pyproject.toml now
# local vLLM on :8000, as in Parts 3-8 (I used Qwen3.8-27B; set AGENTIC_ZERO_MODEL=<name>)
uv run python ctx_corpus.py # build the archive: CSV + 200 reports + 2 policies
uv run python ctx_visual.py build # DINOv3-small embeddings of 600 test images -> LanceDB, cases/*.md
uv run python ctx_visual.py quality # same-part-class share of the 4 nearest neighbours
uv run python ctx_eval.py # T1-T5, all strategies; resumes from out/ctx_eval_runs.json
uv run python ctx_subagent.py # T6: narrow / 20 / 100-report briefs
uv run python ctx_summary.py # the tables in this post
uv run python ctx_deepagents.py # the same T1-T5 through Deep Agents (about 5 hours, most of it loops)
uv run python ctx_da_guard.py # the repeat-guard re-test on the three looping T1 runs
uv venv .venv-adk && uv pip install --python .venv-adk/bin/python google-adk litellm mcp lancedb timm transformers \
numpy scipy opencv-python-headless pillow matplotlib aisuite pydantic openai langgraph laya # ADK in its own venv
.venv-adk/bin/python ctx_adk.py # Google ADK with my tools (+ chunk sub-agent)
.venv-adk/bin/python ctx_adk_env.py # ADK's own toolset; needs Docker, runs every command in a no-network container
uv run python ctx_frameworks.py # the framework tables in this post
uv run python ctx_diagrams.py # the desk-and-library figure
# the six literate notebooks (outputs are saved in the files; this re-executes them in place, about 2 minutes)
cd literate && ../.venv/bin/python run_all.py 14a_desk_and_archive 14b_harness_switches 14c_results_t1_to_t5 \
14d_subagent_brief 14e_two_frameworks 14f_loops_guard_sandboxDo not rerun ctx_corpus.py after ctx_visual.py build without building again: it recreates the archive folder.
0.12 What’s next
The article ends by saying retrieval is half the story: the other half is managing the context after it is in the window, as it grows over a long task. That is the natural next post. The roadmap’s RAG-over-the-defect-catalog item is this post’s neighbour, and the earlier Retrieval and RAG post covers what to embed and how to chunk it.
Reuse
Citation
@online{goni2026,
author = {Goni, Hasan},
title = {Agentic {Developer:} {Zero} to {Advanced,} {Part} 14:
{Context} {Engineering} {Starts} {With} {Retrieval}},
date = {2026-10-24},
langid = {en}
}