AI Engineering
tutorial
Featured

Your Agent Fails the Same Way Every Week and Learns Nothing

Your agent trips over the same edge case every Monday, you patch the prompt by hand, and next Monday it trips again. Fine-tuning is slow and expensive, and a naive memory that summarizes everything quietly erases the details that mattered. Agentic context engineering is the middle path: let the agent evolve a living playbook from its own execution feedback, with a Generator, Reflector, and Curator that add small deltas instead of rewriting the whole thing.

Viral Ruparel
11 min read
Share:

Your agent has a recurring failure. Every Monday the finance team runs the same reconciliation task, and every Monday it mishandles a multi-currency invoice the exact same way it did last week. You know the fix. You have explained the fix to the agent, in the prompt, more than once. It still gets it wrong, because the fix you typed on Tuesday is not in the context on the following Monday, or it is buried under six other instructions and the model skims past it.

So you do the thing everyone does. You open the system prompt and you add another line. "When an invoice has line items in more than one currency, convert each to the account base currency before summing." The prompt grows. Three months later it is 4,000 tokens of accumulated patches, half of which contradict each other, and nobody remembers which line fixed which bug. The alternative you keep hearing about is fine-tuning, which means a labeled dataset, a training run, an eval cycle, and a redeploy for what is essentially a bug fix. Both paths are slow, and neither one lets the agent actually learn from what it just saw happen.

Agentic context engineering is the path between those two. Instead of hand-editing a prompt or retraining a model, you let the agent maintain its own playbook and update it from execution feedback. It is the idea behind ACE, a framework from Stanford, SambaNova, and Microsoft that showed up at ICLR 2026 and reports gains of around 10% on agent benchmarks over a strong tuned-prompt baseline, matching a top production agent on AppWorld while running on a smaller open model. This post is about how it works and how to build a minimal version you can actually run.

The business problem: agents that cannot compound

The thing that makes a senior engineer valuable is not raw intelligence, it is accumulated context. They remember that this client's API returns dates in the wrong timezone, that this table has a soft-delete column you have to filter on, that the reconciliation job breaks on multi-currency invoices. A new hire with the same IQ makes those mistakes for months before the lessons stick.

Most agents are permanent new hires. They start every task with the same static context and no memory of the specific ways they failed yesterday. The business cost is not abstract. It is the same support ticket filed twice, the same wrong number in a report, the same tool call that fails because the agent never learned the argument format the API actually wants. Each failure costs a human intervention, and the interventions never stop because the agent never internalizes the correction.

The two standard fixes both have a tax. Fine-tuning turns a one-line lesson into a data-and-training project, and it is opaque afterward, you cannot read a weight to find out what the model learned. Hand-editing the prompt does not scale past a few dozen rules before the context turns into a swamp and the model starts ignoring parts of it. What you actually want is the thing the senior engineer does automatically: take a concrete failure, distill the lesson, write it down somewhere durable, and apply it next time. That loop is what context engineering as a discipline is about, and the agentic version automates it.

The naive version, and why it collapses

The obvious first attempt is to give the agent a memory and let it summarize each task into a note. Here is roughly what people write, and it is worth seeing exactly why it degrades.

# DON'T: summarize-and-overwrite memory. This looks reasonable and
# quietly destroys detail every time it runs.
def update_memory(old_memory: str, task, outcome) -> str:
    prompt = f"""Here is the agent's current memory:
{old_memory}

It just finished this task with this outcome:
{task} -> {outcome}

Rewrite the memory to incorporate anything useful. Keep it concise."""
    return llm(prompt)  # returns a fresh blob that REPLACES the old one

Two failure modes are baked in. The first is brevity bias: you asked for "concise," so the model favors short generic wording and drops the specific caveat that made the lesson worth keeping. "Handle currencies carefully" survives; "convert each line to the account base currency using the invoice-date rate, not today's rate" does not. The second is context collapse: because every update rewrites the entire blob, small distortions compound. Run this loop fifty times and the memory reads fine but has quietly overwritten most of what it once knew. The rewriting itself is the erosion.

The fix is structural. Stop summarizing, and stop rewriting the whole thing. Keep the context as a list of small, itemized entries, add to it in deltas, and only prune on a schedule you control.

The three roles: Generator, Reflector, Curator

The core move is to split the work that the naive loop crammed into one call. The Generator does the task. The Reflector looks at how it went and extracts lessons. The Curator decides how those lessons change the playbook. Keeping them separate is what prevents collapse, because the role that writes new detail is never the same role that is tempted to compress the old detail away.

Start with the playbook as structured data, not prose. Each entry is an atomic bullet with enough metadata to prune later.

from dataclasses import dataclass, field
from typing import Literal
import uuid, time

@dataclass
class PlaybookItem:
    id: str
    section: str                 # e.g. "invoices", "sql", "api_quirks"
    text: str                    # one concrete, self-contained lesson
    helpful: int = 0             # times this item preceded a success
    harmful: int = 0             # times it preceded a failure
    created_at: float = field(default_factory=time.time)

@dataclass
class Playbook:
    items: list[PlaybookItem] = field(default_factory=list)

    def render(self, sections: list[str] | None = None) -> str:
        # Inject into the agent prompt grouped by section, newest last.
        out = []
        for sec in sorted({i.section for i in self.items}):
            if sections and sec not in sections:
                continue
            out.append(f"## {sec}")
            for it in self.items:
                if it.section == sec:
                    out.append(f"- [{it.id[:6]}] {it.text}")
        return "\n".join(out)

Now the Reflector. It takes one trajectory (the task, what the agent did, and the outcome signal) and returns candidate delta items. Crucially it does not see or rewrite the existing playbook, so it cannot compress it. It only proposes new detail.

import json

def reflect(task: str, trajectory: str, outcome: str) -> list[dict]:
    """Turn one execution into concrete, reusable lessons (deltas)."""
    prompt = f"""A task agent just ran this task:
TASK: {task}
WHAT IT DID: {trajectory}
OUTCOME: {outcome}

Extract 0-3 specific, reusable lessons for next time. Each lesson must be
concrete enough to act on with no other context. Prefer the exact rule,
argument format, or edge case over general advice. Return JSON:
[{{"section": "<short-topic>", "text": "<one concrete lesson>"}}]
If nothing generalizes, return []."""
    return json.loads(llm(prompt))  # execution feedback -> candidate deltas

The Reflector runs on both wins and losses. A failed multi-currency invoice yields a lesson about conversion. A success on a gnarly SQL join yields a reusable pattern. Learning from wins is what lets the playbook capture strategies, not just error patches.

The Curator: grow and refine, never rewrite

The Curator is where collapse is actually prevented. It takes the Reflector's candidate deltas and merges them into the playbook without ever regenerating existing items. Its only powers are append, increment a counter, and, on a schedule, deduplicate. That last part is the "grow-and-refine" idea: the playbook is allowed to grow freely between refinements, and refinement is a periodic, bounded cleanup rather than a rewrite on every step.

def curate(pb: Playbook, deltas: list[dict], dedup_threshold: float = 0.86):
    for d in deltas:
        near = _most_similar(pb, d["section"], d["text"])
        if near and _cosine(near.text, d["text"]) >= dedup_threshold:
            near.helpful += 1          # reinforce, do not duplicate
            continue
        pb.items.append(PlaybookItem(
            id=str(uuid.uuid4()), section=d["section"], text=d["text"],
        ))

def refine(pb: Playbook, max_items_per_section: int = 40):
    """Run this every N tasks, not every task. Bounded, scheduled cleanup."""
    by_section: dict[str, list[PlaybookItem]] = {}
    for it in pb.items:
        by_section.setdefault(it.section, []).append(it)

    kept: list[PlaybookItem] = []
    for sec, items in by_section.items():
        # Rank by net usefulness, then recency; drop the long tail and
        # anything that has hurt more than it has helped.
        items.sort(key=lambda i: (i.helpful - 2 * i.harmful, i.created_at),
                   reverse=True)
        kept.extend([i for i in items if i.harmful <= i.helpful]
                    [:max_items_per_section])
    pb.items = kept

The helpful and harmful counters are the feedback signal doing real work. When a task succeeds, increment the items that were in context for it. When it fails, increment their harmful count. Over time the refine step lets genuinely useful lessons rise and quietly retires the ones that were noise or that stopped being true. You are grading the playbook with the agent's own outcomes, no labels required.

Wiring it into the loop is straightforward, and the online version is the one that pays off.

def run_task(pb: Playbook, task: str) -> str:
    context = pb.render()                      # inject current playbook
    trajectory, outcome = agent_execute(task, context)

    # credit assignment: which items were live for this task
    live_ids = [ln[2:8] for ln in context.splitlines() if ln.startswith("- [")]
    for it in pb.items:
        if it.id[:6] in live_ids:
            if outcome == "success": it.helpful += 1
            else: it.harmful += 1

    curate(pb, reflect(task, trajectory, outcome))
    return outcome

Run refine on a counter, say every 50 tasks, and you have a context that adapts at test time from execution feedback alone. That is the whole loop. No training run, no labeled set, and every lesson in it is a line of English you can read, audit, and delete.

Tradeoffs and where it bites

A playbook is an attack surface and a poison surface. Anything that shapes future behavior can be corrupted. If a task's "outcome" can be influenced by untrusted input, a prompt-injection attempt can write itself into your playbook and persist. Treat curated items as untrusted until proven otherwise, keep the Curator's powers narrow, and never let a delta grant capabilities or change tool permissions. This is the same boundary problem that shows up whenever an agent acts on content it did not author.

Bad lessons compound too. The mechanism that lets good detail accumulate will just as happily entrench a wrong conclusion the Reflector drew from a fluke. The harmful counter and the harmful <= helpful filter in refine are your defense, but they only work if your outcome signal is honest. Garbage credit assignment produces a confidently wrong playbook, which is worse than no memory at all.

Token cost moves, it does not vanish. A rich playbook is more input tokens on every call. That is usually a good trade, you are paying for context instead of paying a human to re-explain the same rule, but it interacts with everything else fighting for the window. Section your playbook and inject only the parts relevant to the task, and mind the interaction with prefix caching, since a playbook that changes every task busts the cache for the tokens after it. Keeping updates append-only at the end helps here.

Detail has a shelf life. A lesson that was true in March can be false in September after an API change. Recency in the ranking and the scheduled prune handle slow drift, but for facts that flip hard you want the harmful signal to demote them fast once reality changes. Do not treat the playbook as permanent truth. Treat it as the best current guess, graded continuously.

The takeaway

The gap between a demo agent and a production one is rarely model quality. It is that the production agent is expected to get better at your specific domain over time, and most of them physically cannot, because their context is frozen and their only path to improvement is a training run or a human editing a prompt. Agentic context engineering closes that gap with a loop you can build in an afternoon: a Generator that acts, a Reflector that distills lessons from what actually happened, and a Curator that folds them in as small deltas instead of rewriting everything and eroding it. Keep the updates incremental, prune on a schedule, grade every item with real outcomes, and guard the whole thing as the attack surface it is. The result is an agent that fails a new way each week instead of the same way, which is the only kind of improvement that compounds.

If you want to go deeper on the failure mode this fixes, I wrote about how naive summarization quietly destroys long-running context in context compaction for long-running agents, and about the difference between context that persists and context that helps in giving your agent memory that survives the session.

If you are staring at an agent that keeps making the same mistake and you are not sure whether the answer is a playbook, fine-tuning, or something simpler, book a consultation call and we can figure out which one your problem actually needs before you build any of it.

Viral Ruparel

Generative AI consultant helping teams ship reliable LLM and agent systems in production.

Contact Viral about your AI project →

Frequently Asked Questions

What is agentic context engineering?+

Agentic context engineering treats an agent's context as an evolving playbook rather than a fixed prompt. Instead of retraining the model or hand-editing the system prompt after every failure, the system runs a loop of three roles: a Generator that attempts tasks, a Reflector that studies what worked and what failed, and a Curator that folds those lessons into the playbook as small structured updates. The agent improves from its own execution feedback, without labeled data and without touching model weights.

How is this different from just giving my agent memory?+

Most memory implementations summarize a conversation or a task into a short note and store it. That summarization is exactly the problem. It drops the specific, hard-won details that make a lesson useful, and when you rewrite the whole memory blob on each update it slowly erases what was there. A playbook built with agentic context engineering grows by appending and merging small delta items and only prunes on an explicit schedule, so detail accumulates instead of eroding.

What are context collapse and brevity bias?+

Brevity bias is the tendency of summarization to favor short, generic wording that drops domain-specific insight, so your memory reads clean but forgets the one caveat that mattered. Context collapse is what happens over many update cycles when you rewrite the entire context each time: small distortions compound, older detail gets overwritten, and the playbook degrades even as it appears to be maintained. Delta updates plus scheduled pruning are how you avoid both.

Does a self-improving playbook replace fine-tuning?+

For a lot of production cases it removes the reason you reached for fine-tuning in the first place, which was usually to teach the model your domain's rules and recurring edge cases. A playbook does that at inference time, updates in minutes instead of a training run, and is fully auditable because every entry is human-readable text. Fine-tuning still wins when you need to change the model's core behavior, style, or latency profile, but for "stop making this specific mistake" a playbook is faster and cheaper.