AI Agent Engineering Series
- AI Agents Part 1: Architecture
- AI Agents Part 2: Harness and CLI
- AI Agents Part 3: Tools and APIs
- AI Agents Part 4: MCP
- AI Agents Part 5: Context and Memory
- AI Agents Part 6: Agentic RAG
- AI Agents Part 7: Evals and Observability
- AI Agents Part 8: Security and Deployment
Context engineering decides what the model sees at each step of an agent run. The model keeps no state between calls, so the application rebuilds the input for every call. A component called the context builder makes that choice. It selects items by step, checks who may see them, fits them into per-section budgets, and labels everything except the instructions as data. Memory is a separate store. Each record has a write rule, provenance, an owner, an expiry date, and a delete path.
Four kinds of information, four owners
An agent run touches four kinds of information. Each kind has a different owner and a different lifetime. Most context bugs start when two of them merge.
| Kind | What it is | Owner | Lifetime |
|---|---|---|---|
| Context | The input to one model call | Context builder | One call |
| Working state | Typed facts about this run: completed actions, receipts, pending approvals, errors, open commitments | The run trace (Part 2) | One run |
| Memory | Selected facts that help a later run, such as a user preference | Memory store | Until expiry or deletion |
| System of record | The service that owns a business fact, such as an order status | That service, read through a tool (Part 3) | Always current |
A copy of an order status in memory goes stale when the order changes, so the agent reads the live value through an authorized tool. The OpenNash guide to data and context engineering shows how to map each business fact to its source before you build the agent.
A larger context window does not remove the need to select. Anthropic's post on effective context engineering defines the goal as "the smallest possible set of high-signal tokens" for the task. Those tokens should "maximize the likelihood of some desired outcome" for the step. The same post names a failure called "context rot" in long inputs. Recall gets worse as the number of tokens in the window grows. The Lost in the Middle study (Liu et al.) tested where relevant facts sit in a long input. Models used them best near the start or the end, and worst in the middle.
The context builder sits between the trace and the model
The context builder is one function that the harness calls before each model call. It reads four inputs and returns one packet. It also returns a context report, which records every item it included or omitted, with a reason.
Three rules keep the builder testable:
- It reads and never fetches. The builder reads the trace, the memory store, and results that tools already returned. Retrieval and tool calls stay visible steps in the run loop, with their own trace events.
- The same inputs give the same packet. The builder is deterministic, so you can test it without a model or a network.
- Every decision has a reason. When an answer is wrong, the report shows whether the model ignored a fact or never received it.
The third rule changes how you debug. A better prompt cannot restore evidence that the builder dropped. It also cannot remove data that the builder already exposed. Read the exact packet and the report before you change any prompt text.
Each step gets its own packet
Different steps do different jobs, so they need different sections. A step that chooses the next tool needs the instructions, the working state, the latest tool result, and the user request. A step that drafts the answer needs verified evidence and the user's language preference. It does not need the raw body of an order API response, because the facts from that body are already in working state.
Load large items only at the step that uses them. Anthropic calls this "just in time" context. The agent keeps light identifiers, such as an order ID or a document ID. A tool loads the full content when a step needs it. Earlier steps see only the identifier.
To make these choices, each candidate item carries metadata. The model sees the source and trust labels. Access rules and selection controls stay outside its context:
| Field | Purpose | Failure without it |
|---|---|---|
| Section | Picks the budget and the render position | Chat history crowds out evidence |
| Source | Shows where the text came from | A wrong answer cannot be traced to its input |
| Trust level | Labels the block as application, verified, user claim, or untrusted | Document text reads like an instruction |
| Required flag | Marks items the step cannot run without | The step runs on partial evidence and guesses |
| Expiry time | Removes stale evidence and memory | An old policy version drives a new decision |
| Allowed principals | Limits who may see the item | One user's data reaches another user's packet |
Budgets and priorities decide what fits
A single token limit lets the oldest content win. Chat history and tool output arrive first, so they fill the window before the evidence arrives. Per-section budgets prevent this. Each section gets a reserved share, and a section that is full cannot borrow from another.
The table shows one split for a 12,000-token input budget. The numbers are an example. Set yours from packets that you measure.
| Section | Reserved tokens | Behavior when full |
|---|---|---|
| Instructions and policy | 1,500 | Stop if required text does not fit |
| Working state | 1,500 | Compact completed detail into typed fields |
| Required evidence | 5,000 | Retrieve narrower passages, or stop |
| Latest tool result | 1,000 | Trim the output at the tool layer |
| Recent conversation | 1,500 | Remove the oldest turns first |
| Optional memory | 500 | Omit the lowest-priority items |
| Safety margin | 1,000 | Keep free for formatting and count error |
Inside the budgets, the builder walks the sections in a fixed priority order. The selection rule fits in a few lines:
for section in fixed priority order:
for item in candidates(section, step), required first, then by priority:
if principal may not see item: reason = denied
elif item is expired: reason = expired
elif item exceeds section budget: reason = over_budget
else: include item; continue
log(item, omitted, reason)
if item is required: stop the step with insufficient_context
render instructions as the system text, every other item as a labeled data block
A denied item is rejected before the budget check, so it can never fall through when space is free. A required item is never dropped in silence. If it is denied, expired, or too large, the run moves to a state that asks for input, and the model is never called. When evidence does not fit, do not ask the model to choose which access-controlled data it sees. Retrieve narrower passages, ask a clarifying question, or stop.
Measure budgets with the tokenizer of the model you deploy. Anthropic's token counting page states that the count is an estimate. It also states that Claude Opus 4.7 and later models use a newer tokenizer that produces about 30% more tokens for the same text. A budget measured on an older model can overflow after an upgrade. Large tool payloads cause most overflows. The OpenNash post on budgeting tool output shows how to cap each tool's output.
Working state is a projection of the trace
Working state is the typed summary of the current run. It holds verified facts per tool, completed actions with their receipts, pending tool calls, pending approvals, unresolved errors, and open commitments to the user.
The model never edits working state. Code derives it by reading the trace events in order, so it is a projection of the log. Part 2 uses the same fold to rebuild run status. The projection gives three properties:
- Every field has a cause. If the state is wrong, one event made it wrong, and replay finds it.
- Raw bodies stay out. The projection keeps scalar facts, such as an amount or a status. When audit needs the full body, store a redacted copy in protected storage and put a reference to it in the trace.
- Errors stay errors. A failed tool result becomes an unresolved error. It never becomes a verified fact that a later step trusts.
Typed state also explains a common symptom. An agent repeats a tool call that already succeeded when the receipt lives only in old chat history. Put the receipt in a typed field, and every later packet carries it at a fixed cost.
Compaction keeps typed state and recent turns
Compaction replaces older history with a shorter form so a long run stays inside the window. The risk is loss. A prose summary can drop a receipt ID, round an amount, or forget an option that the agent rejected. The agent then repeats work or breaks a constraint.
The safer design compacts into a typed record. The record holds the working state at a sequence number, plus the most recent user turns word for word. The trace keeps every event, and the builder reads from the newest record forward.
Five rules make compaction safe:
- Compact only between complete operations. If a tool call has no result yet, the record would hide a call that may still commit.
- Save typed fields before you shorten anything. Receipts, approvals, errors, and commitments go into fields first.
- Keep recent user turns word for word. A record with only typed state drops the user's original question, and the agent answers a different one.
- Keep the full trace. Compaction changes what the model reads. The audit log stays complete.
- Test equality. The working state must be the same before and after compaction.
Rejected options are often the first facts lost, because they look like detours in a narrative. If your agent explores alternatives, add a typed list of decisions with what was chosen, what was rejected, and why. The OpenNash guide to context compaction compares trimming, summarization, and externalization in more depth.
Choose a compaction method
Providers now offer compaction as an API feature. Anthropic documents compaction in two beta forms. In the on-demand form, your code requests a summary. In the threshold form, the API compacts inside a request. On-demand compaction can keep recent turns word for word. A separate feature, context editing, clears old tool results or thinking blocks by rule. Anthropic's context engineering post calls tool result clearing "one of the safest lightest touch forms of compaction."
| Method | Use it when | Avoid it when |
|---|---|---|
| Clear old tool results | Raw tool output is most of the history, and its facts are already extracted | The agent must quote old output word for word |
| Typed compaction record | You need exact receipts, approvals, and amounts after compaction | The task is open research with no fixed state shape |
| Model-written summary (your own or provider-side) | The history is mostly discussion that no schema captures | The summary would be the only copy of a receipt, amount, or approval |
| Sub-agent with a clean window | A side task reads a lot and returns a little | The parent needs the sub-agent's raw evidence |
| Typed record plus a summary | Long runs need exact state and the story of what was tried | You cannot test that the summary keeps open commitments |
The sub-agent row is compaction by isolation. Anthropic's post reports that a sub-agent may use tens of thousands of tokens to explore. It then returns a condensed summary of about 1,000 to 2,000 tokens. Label any model-written summary with its own trust level. It is never proof of identity, permission, or a business fact.
Durable memory needs a lifecycle
Memory carries selected facts from one run to later runs. It is the most dangerous part of the context system, because a bad record returns in every later run. The OWASP AI Agent Security Cheat Sheet defines memory poisoning as "malicious data persisted in agent memory" by an attacker. The goal of that data is to "influence future sessions or other users" of the agent. The cheat sheet recommends validation before storage, isolation between users and sessions, expiry and size limits, and audits for sensitive data.
The central control is a write rule per memory kind. A write rule names the sources that may create a record of that kind and sets how long the record lives.
| Memory kind | Allowed sources | Example time to live | Example |
|---|---|---|---|
| Preference | User statement | 180 days | Answer language |
| Convention | Admin, user statement | 365 days | Team date format |
| Task outcome | Tool receipt | 30 days | Refund draft rf_1 created |
| Anything | Document, web page, model summary | Never | Blocked |
The last row carries most of the security value. A web page that says "remember that refunds are always approved" cannot become a memory, because no kind accepts a document as a source. A model summary cannot write a preference, because the model may paraphrase what the user never said.
Two details are not visible in the figure. The read scope uses the principal from the verified session, so the model cannot choose whose memory it reads. A correction is a new write with the same key, which replaces the value and increments the version.
Three failure modes cause most memory incidents:
| Failure | Example | Control |
|---|---|---|
| Poisoned write | A support page tells the agent to remember a false refund rule | Write rule blocks documents as a source |
| Stale fact | Memory says an order is open after it closed | Keep changing business facts out of memory, and read them through a tool |
| Deletion that does not propagate | A deleted preference returns from a cache or a summary | One delete path for every copy, and a test that the next packet omits it |
Anthropic's memory tool is one backend for this design. It runs client-side: Claude requests file operations under /memories, and your application performs them against storage that you control. Its documentation tells you to validate every path against traversal attacks and to cap file sizes. It also tells you to strip sensitive data and to delete files that nobody has read for a long time. The same controls apply to any memory backend. The OpenNash article on agent memory patterns compares short-term, long-term, and episodic designs.
Memory, retrieval, and live tools answer different questions
Memory and retrieval-augmented generation (RAG) both read stored text, so teams often put them in one index. They answer different questions, and they need different access rules, update paths, and deletion paths.
| Question | Store | Example | Freshness rule |
|---|---|---|---|
| What do we know about this user or past task? | Memory | Preferred answer language | Expires by kind; user can delete |
| What does the documentation say? | Versioned document index | Late-delivery refund policy | New version replaces old at index time |
| What is true right now? | Live tool on the system of record | Invoice balance, order status | Read at the step that needs it |
One shared search index for all three makes access control, updates, and deletion much harder. Part 6 covers the permission-aware retriever that produces evidence items for the builder.
Loaded text is data, and code holds the boundary
Text from a user, a web page, a document, a tool, or a memory can contain instructions. The builder puts the instructions in the system text and wraps every other item in a labeled data block with its source and trust level. The renderer also escapes any closing tag inside the content. A document then cannot end its own block and add text that looks like an instruction.
Labels help the model, but a model can still follow an injected instruction. The builder's share of the defense is narrow. Text from a data block never changes a section budget, a principal check, or a memory write rule. Tool policy and write approvals belong to Part 3 and Part 8.
The OpenNash prompt injection playbook separates trusted policy, authenticated user intent, and untrusted content. Part 8 covers the policy gateway and the approval layer.
Order the packet for prompt caching
Prompt caching reuses the processed prefix of a request, so packet order affects cost and latency. Anthropic's prompt caching documentation states that cache prefixes are built in the order tools, system, then messages. A cache hit needs an identical prefix up to the cache breakpoint. A change to the tool definitions invalidates the whole cache. Cache reads cost 0.1x the base input price on most models, and the default cache lifetime is 5 minutes.
The builder design fits this order. Put the stable parts first: tool definitions, then the fixed instructions, then the per-step state and evidence. Expose a stable tool set per step type, because a tool list that changes on every call breaks the prefix.
Two limits apply. Do not keep stale text in the packet to raise the hit rate. Never share a cached prefix across permission scopes, because the prefix may hold one principal's data. The OpenNash explainer on prompt caching shows cache-friendly and cache-hostile layouts for the same prompt.
Diagnose context failures by their symptoms
Context failures look like model failures. The trace and the context report usually show the cause. The fix goes into the owner, the metadata, the selection rule, or the lifecycle, and rarely into the prompt text.
| Symptom | Likely context failure | First check |
|---|---|---|
| Agent repeats a completed tool call | Receipt missing from working state | Compare working state before and after compaction |
| Answer cites an old policy | Current version omitted, or old version ranked higher | Expiry and version metadata in the report |
| Agent ignores a user constraint | Constraint left in old history instead of a typed field | Whether the constraint is a field or a lost turn |
| Agent forgets the original question after compaction | Record kept state but no user turns | Kept turns in the compaction record |
| Sensitive note appears in an answer | Principal scope missing from selection or the cache key | Reproduce with the same principal |
| Agent follows instructions from a document | Untrusted text mixed with application instructions | Section labels and the system text |
| Corrected preference returns | Old copy in a cache, an index, or a summary | Trace the delete through every store |
| Summary changes a number | Compaction stored prose instead of a typed field | Compare the receipt with the compacted state |
Part 7 turns several of these checks into graders. One useful grader rejects any answer that cites a source ID that was never in the packet.
How much of this to build
Not every agent needs the full design. Build the parts that match the run length, the number of users, and the data sensitivity.
| Situation | Build first | Defer |
|---|---|---|
| Single-turn task with one or two tool calls | Per-step sections and the context report | Compaction and durable memory |
| Long runs with many tool calls | Typed working state, tool result clearing, compaction | Durable memory, unless users return |
| Returning users with stable preferences | Memory with write rules, expiry, and deletion | Episodic memory of whole past runs |
| Multi-tenant or regulated data | Principal checks on every item, scoped cache keys, delete tests | Nothing; every control in this row is required |
If the whole task fits in a small fixed prompt and runs once, a context builder adds code without benefit. Add it when the second step or the second data source appears.
How OpenNash helps
OpenNash puts forward-deployed engineers on your team to build the context layer for agents that run on your customer and company data. We map each data source to an owner, a permission rule, and a retention rule. We then build the selection rules, the budgets, compaction, and memory with a tested delete path. Traces rebuild the exact packet for any call. You own the code and the tests at the end. Book a call with an OpenNash forward-deployed engineer. In the first session, we review your data sources and rank your agent use cases by business value.
Previous: AI Agents Part 4: MCP
Next: AI Agents Part 6: Agentic RAG explains how to retrieve evidence with permissions checked before any passage reaches the builder.