Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Reference-grade guide to the engineering AROUND the model — the control loop, tools, memory, retries, and budgets (the harness) plus context as a dynamically-assembled token budget — covering lost-in-the-middle, context rot, just-in-time retrieval, compaction, sub-agent isolation, prompt-cache-stable ordering, and the agent-loop and eval patterns that actually determine reliability.
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-02 | ✓→✓ | = Same ✓ | 197% | 0% |
| case-03 | ✓→✓ | = Same ✓ | 261% | 0% |
| case-04 | ✓→✓ | = Same ✓ | 222% | 0% |
| case-05 | ✓→✓ | = Same ✓ | 190% | 0% |
| case-06 | ✓→✓ | = Same ✓ | 263% | 0% |
Prompt engineering optimizes the string you send the model. Harness and context engineering optimize everything else: the loop that decides when to call the model, the tools it can reach, what gets retrieved and assembled into the window, what gets remembered across turns, how failures retry, and what budgets cap it. In production agent systems the harness — not the prompt — is the dominant lever on reliability. A perfect prompt inside a loop with no retry, no tool error-handling, no token budget, and a stale context will fail; a mediocre prompt inside a disciplined harness ships.
The mental model that organizes this whole skill: the context window is the model's working memory, and it is a managed budget, not a bucket you fill. Every token you spend on a tool definition is a token you can't spend on retrieved evidence or conversation history. Context engineering is the discipline of allocating that scarce budget — assembling, ordering, compacting, and evicting — so the right tokens are present at the right position for each model call. This skill is the playbook for both the harness around the model and the context inside it.
Modern sources to anchor on: Anthropic's Building effective agents (2024) and Effective context engineering for AI agents (2025); the "lost in the middle" finding (Liu et al., TACL 2024); Chroma's Context Rot report (2025); the ReAct loop (Yao et al., 2022); and the prompt-caching mechanics from the major providers (cross-reference the prompt-caching skill for the cache-specific depth).
| | Prompt engineering | Harness engineering | |---|---|---| | Unit of work | The text in one request | The code around every request | | Optimizes | Phrasing, examples, format, role | Loop, tools, retries, budgets, memory, routing | | Failure it fixes | "model misunderstood the instruction" | "model called a broken tool and the loop hung" | | Tested by | eval on prompt variants | integration tests on the control loop + failure injection | | Owns reliability | a little | most of it |
The harness is the deterministic scaffolding: a state machine that calls the model, parses its output, dispatches tools, handles tool errors, enforces a step/token/wall-clock budget, decides when to stop, and assembles the next context. The prompt is one input to one node of that machine.
Why the harness dominates reliability. LLM calls are non-deterministic, occasionally malformed, and bounded by a finite window. The harness is where you make a non-deterministic component behave like a dependable one: validate every output, retry transient failures, degrade gracefully when a tool 500s, cap runaway loops, and keep the window coherent over long horizons. None of that lives in the prompt. The single highest-leverage realization for a team that has been "prompt-tuning" for weeks: most of your remaining failures are harness bugs wearing a prompt costume.
loop(goal, ctx):
for step in range(MAX_STEPS):
if budget.exceeded(): return halt("budget")
msg = model(assemble_context(ctx)) # one call
plan = parse_or_repair(msg) # validate; reprompt on schema miss
if plan.is_final: return plan.answer
obs = dispatch_tool(plan.tool, plan.args) # adapter: timeout, retry, error-as-value
ctx = update_context(ctx, plan, obs) # append + compact if near budget
return halt("max_steps")Everything interesting in production is in parse_or_repair, dispatch_tool, assemble_context, update_context, and budget — not in model.
A "long prompt" is a static blob you author once. Context is the full set of tokens present for a given model call, dynamically assembled from distinct, separately-governed sources:
| Component | What it is | Volatility | Budget instinct | |---|---|---|---| | System / instructions | role, policy, output contract | stable (cache-friendly) | small, fixed | | Tool definitions | names, descriptions, schemas | stable per-session | medium; prune unused tools | | Retrieved knowledge | RAG hits, file slices, docs | per-step, just-in-time | largest swing; relevance-gated | | Conversation history | prior turns, tool results | grows unbounded | compact aggressively | | Scratchpad / plan | the agent's working notes | per-step | keep current, evict stale | | User message | the actual ask | per-turn | verbatim |
Context engineering is the policy that decides how many tokens each component gets, in what order, on each call. The opposite — concatenating everything you have "just in case" — is the central anti-pattern. More tokens is not more capability; past a point it is less, for three measured reasons below.
Don't treat the window as free because the model "supports 200K." Supporting a window size is not the same as reasoning well across it. Treat every token as paid-for working memory.
These are empirical, named, and reproducible. Design against them.
Relevance over volume is the through-line of all three. The job is not "fit more in" — it's "put exactly what's needed where it's read best, and nothing else."
Two strategies for getting knowledge into context:
greps and reads on demand.JIT wins for agents because the agent's own actions reveal what's relevant, and you pay tokens only for what's actually used. Anthropic's context-engineering guidance frames this as "let the agent retrieve as it goes" rather than pre-deciding. The cost is more round-trips (latency) and a smarter loop.
Hybrid (recommended default): front-load a small, high-certainty core (the task spec, a directory map, the 2-3 docs you know are needed) and JIT everything speculative. Keep a "context manifest" — a compact index of what's available to fetch — so the model knows what it can reach without the content being resident.
# manifest stays in context; bodies are fetched on demand
manifest = [
{id: "spec", tokens: 800, resident: true},
{id: "api.md", tokens: 4000, resident: false, fetch: read("docs/api.md")},
{id: "schema", tokens: 1200, resident: false, fetch: read("db/schema.sql")},
]Conversation and tool output grow without bound; the window does not. You need a pruning/eviction policy.
Techniques, cheapest to richest:
Eviction policy checklist: pin the system prompt and active goal; summarize resolved sub-tasks; drop verbose tool outputs once their conclusion is captured (keep "the query returned 3 rows: X, Y, Z," drop the 2K-token raw dump); never evict the immediate question.
Do compact at a threshold and log what was dropped. Don't let history grow until the provider hard-truncates it silently — you lose control of what is lost (usually the head, your most important tokens).
A concrete compaction trigger:
if tokens(history) > 0.7 * BUDGET:
old, recent = split(history, keep_recent=8)
summary = model(SUMMARIZE_PROMPT, old) # decisions, facts, open Qs, dead-ends
history = [pinned_goal, summary, *recent]A single agent accumulating one giant context across a 50-step task hits rot and lost-in-the-middle. Sub-agents (orchestrator + workers) are a context-engineering pattern as much as a parallelism one:
When to reach for it: the task decomposes into independent chunks (research N sources, refactor M files), each of which would otherwise flood the main context with intermediate junk. When not to: tightly-coupled sequential work where the handoff cost (re-establishing context per worker) exceeds the savings, or where shared state makes isolation a lie.
Handoff discipline: define the worker's return contract narrowly. "Return: the chosen approach (1 paragraph), the 3 files changed, and any blocker." A worker that returns its full scratchpad has defeated the isolation.
plan = orchestrator(goal) # lean context
results = parallel(
worker(sub, scoped_ctx(sub)) for sub in plan # each: isolated window
) # returns: condensed, not transcript
answer = orchestrator(synthesize(plan, results))Make the budget explicit and enforced — not an afterthought the provider enforces for you.
A workable default allocation for a long-context agent (tune per task):
| Component | Target share | Hard cap behavior | |---|---|---| | System + tools | 5-15% | fixed; if tools blow this, prune the tool list | | Conversation / state | 20-40% | compaction trigger at threshold | | Retrieved evidence | 30-50% | relevance-ranked; truncate tail hits first | | Scratchpad / plan | 5-15% | evict stale steps | | Response headroom | reserve max_tokens | never let input crowd out the answer |
Rules:
input_budget = context_limit − max_output_tokens − safety_margin. Running the input to the brim leaves no room to answer and risks truncated tool calls.Every tool you expose costs tokens (its name, description, JSON schema) and cognitive load (more tools = harder routing, more wrong-tool errors — Hick's Law for models). Tool design is context design.
{ok:false, error:"file not found: X. Did you mean Y?"} lets the model recover within the loop. Make tool errors actionable strings, not stack traces.Do: name tools by action, document the boundary between similar tools, validate args, return concise results. Don't: expose every internal function, write one-word descriptions, or let a tool throw across the loop boundary.
Context is ephemeral (one window); memory is durable (across turns and sessions). A tiered memory system is what lets a finite window behave like unbounded recall. Each tier has a distinct read path into context.
| Tier | Holds | Lifetime | How it enters context | |---|---|---|---| | Working | current goal, plan, active step | this task | resident, pinned | | Scratchpad | intermediate reasoning, draft results | this task | resident; evicted when stale | | Episodic | what happened in past sessions/turns | across sessions | retrieved by recency/relevance | | Semantic | distilled facts, preferences, learned rules | long-term | retrieved by similarity; small, high-value |
Anti-pattern: treating the conversation transcript as your only memory. When the window compacts, undocumented decisions vanish. Externalize durable facts to semantic memory as they're decided, so compaction is lossless for what matters.
/clear-style resets.How you format and order context changes both quality and cost.
<system_policy>, <task>, <retrieved_docs>, <conversation>, <output_contract>. Models attend better to delimited, labeled blocks than to a wall of prose, and you can address sections ("using only <retrieved_docs>, …"). Anthropic models in particular respond well to XML tags.[ STABLE PREFIX — cached ] [ VARIABLE SUFFIX — not cached ]
system policy retrieved docs (this step)
tool definitions ──cache──► conversation tail
static few-shot examples breakpoint user message
"Given the above, do X."Anti-pattern that silently doubles cost: putting a per-request timestamp, request ID, or "today's date" at the very top of the system prompt. It changes every call, so the cache never hits. Move volatile values below the breakpoint.
The control loop is the heart of the harness. The canonical pattern is ReAct (Yao et al., 2022): interleave reasoning ("Thought"), tool calls ("Act"), and tool results ("Observation") in a loop until a final answer.
Thought: I need the user's last order date.
Act: query_db(sql="SELECT max(date) ...")
Obs: 2026-05-30
Thought: That's within 7 days, so they're eligible.
Act: final_answer("Eligible — last order 2026-05-30.")Design decisions that determine whether the loop is reliable:
final_answer tool call (preferred — unambiguous), a max-steps cap, a token/wall-clock budget, or a repeated-state detector (same tool + same args twice = stuck loop, break). Never rely solely on the model "deciding it's done" in prose — parse a structured terminal signal.Termination table:
| Stop reason | Detect by | Action | |---|---|---| | Task complete | final_answer tool call | return answer | | Step budget | step counter ≥ MAX | halt + summarize progress | | Token/time budget | running meter | halt gracefully, return partial | | Stuck loop | repeated (tool,args) | nudge once, then halt | | Unrecoverable tool error | error after retries | surface to user, don't fake success |
You can't improve what you don't measure. Evaluate the context assembly, not just the final answer.
| Failure mode | Symptom | Fix | |---|---|---| | Stuff-everything context | Slow, expensive, worse answers as you add docs | Relevance-rank + cap; ablate components; JIT retrieval | | No compaction | Long sessions silently truncate the system prompt | Pin head, summarize at threshold, log evictions | | Unstable cache prefix | Cost/latency never drops despite caching enabled | Move all volatile tokens below the cache breakpoint | | Retrieve by volume | Top-k=40, accuracy drops | Tune k down, add a relevance threshold, rerank | | No token budget | Provider hard-truncates the wrong end (the head) | Explicit budget, reserve output space, control eviction | | Tool throws across loop | One bad tool call kills the whole agent | Errors-as-values; validate args; retry transient only | | No termination signal | Agent loops, burns tokens, never stops | final_answer tool + max-steps + stuck-loop detector | | Lost in the middle | Needed fact present but ignored | Place critical content at top/bottom, restate at tail | | Transcript-as-memory | Compaction loses a locked decision | Externalize durable facts to semantic memory as decided | | Mega-agent context | One agent's 50-step run rots | Sub-agent isolation; condensed handoffs |
Before shipping an agent, confirm the harness — not the prompt — answers each:
The prompt is the last 5% of the work. The harness and the context budget are the other 95%, and they are where reliability is won or lost.
Other measured skills in the registry, with their headline benchmark lift.