In the previous post, Memory gave our agents the ability to remember — short-term context, persistent state across sessions, the foundation for everything that comes after. Today we use that foundation: an agent with memory is an agent that can, in principle, learn.
That word — learn — is loaded. It tends to drag along an assumption that you need fine-tuning, gradient updates, RLHF pipelines, GPU clusters, the whole apparatus of model training. None of that. The Learning and Adaptation pattern is about something simpler and far more practical: agents that change their behavior based on what worked and what didn’t, without ever touching the weights of the model.
Pattern #9: Learning and Adaptation#
The Problem#
We already have Reflection (Chapter 4). The Producer-Critic loop produces a draft, critiques it, refines it, and stops when the result is good enough. That works beautifully — for a single output.
The moment the agent moves to a different task, all that effort is gone. The next time someone asks for a similar piece of work, the agent starts from scratch, makes the same mistakes, and the same critic has to flag them again. There’s no carry-over. No accumulation. The agent isn’t smarter on its tenth attempt than it was on its first.
That’s the gap Learning fills. Reflection improves this output. Learning improves future outputs by changing what the agent does next time.
The Solution#
Learning and Adaptation at the agent level (not the model level) has three ingredients:
- A quantitative evaluation signal — not just “is this good?” but “how good, on a measurable axis?”
- A strategy that can be adjusted — what the agent does has to be modifiable based on the feedback.
- A memory of what worked — so the next task benefits from the last one.
The starting point is an adaptive loop where the agent doesn’t just refine its output — it adjusts its approach. Here’s the core graph from my repo:
class AdaptiveState(TypedDict):
task: str
output: str
score: float
feedback: str
iteration: int
strategy: str
def performer(state: AdaptiveState) -> dict:
"""Generates output using the current strategy and any prior feedback."""
strategy = state.get("strategy", "default")
feedback = state.get("feedback", "")
system_msg = f"You are an adaptive agent. Current strategy: {strategy}."
if feedback:
system_msg += f"\n\nPrevious feedback to incorporate:\n{feedback}"
# ... run LLM with this evolving system message
return {"output": output, "iteration": iteration + 1}
def evaluator(state: AdaptiveState) -> dict:
"""Evaluates output and recommends a new strategy."""
# Prompt the model to output:
# SCORE: <1-10>
# FEEDBACK: <specific improvement suggestions>
# STRATEGY: <recommended approach for next attempt>
# ... parse and return
return {"score": score, "feedback": feedback, "strategy": strategy}
def should_adapt(state) -> Literal["performer", "__end__"]:
if state["score"] >= 8.0: return "__end__"
if state["iteration"] >= MAX_ADAPTATIONS: return "__end__"
return "performer"
builder.add_edge("performer", "evaluator")
builder.add_conditional_edges("evaluator", should_adapt)This looks like Reflection, and structurally it is — same generator-critic loop. But two design choices change the semantics:
The evaluation is quantitative. Instead of a free-form critique (“the tone is off, the example is unclear”), the evaluator emits a numeric score and stops the loop when the score crosses a threshold. This isn’t cosmetic. It makes “good enough” an explicit, debuggable decision: 8.0/10 passes, 7.9 doesn’t. You can tune the threshold per task. You can log scores over time and see whether your agent is actually improving. None of that is possible with prose-only critique.
The strategy is part of the state. The evaluator doesn’t just say what to fix — it recommends how to approach the next attempt. That recommendation gets stored in state["strategy"], and the performer reads it as part of its system message in the next iteration. The agent isn’t just refining text; it’s switching strategies. This is the difference between “rewrite this paragraph more clearly” and “this whole approach is wrong, try a different one.”
From Adaptation to Learning#
What you have above is within-task adaptation: the agent gets better at the current task across iterations. The leap to across-task learning is one step further — and it’s where the Memory pattern from Chapter 8 becomes load-bearing.
The pattern (which the code in the repo gestures at but doesn’t fully implement, deliberately, because there’s no single right way to do it):
- After each task completes, persist the winning strategy along with the task description, the score, and the iteration count.
- On a new task, retrieve similar past tasks (vector similarity over task descriptions) and pull their winning strategies.
- Inject those strategies as context — “Here’s what worked on similar tasks before” — into the initial system message.
The performer now starts from a better baseline. Iteration 1 of task N benefits from iteration 3 of task N–1. The agent isn’t getting smarter in the parameter sense — the model weights are unchanged — but the system around it is accumulating useful structure. This is what’s sometimes called in-context learning or few-shot retrieval, and it’s the most practical form of agent improvement you can ship without a training pipeline.
The same architecture supports the inverse signal too. Persist the failure modes, not just the wins. “On tasks like this, the model tends to skip edge cases — make sure to enumerate them explicitly.” Failure memory is often more useful than success memory, because the model already knows how to produce plausible output; what it doesn’t know is the specific way your system has burned it in the past.
Why This Matters#
Learning at the agent level is what makes long-running deployments improve over time without retraining. The model is fixed; the system that wraps it is what evolves.
Where this pattern actually pays off:
- Code generation agents that remember which style guides, conventions, and libraries your team uses — and apply them by default on the next ticket.
- Customer support assistants that learn from resolved tickets which phrasings escalate well and which don’t.
- Content workflows that retain the tone, structure, and editorial choices that survived past review cycles.
- Internal automation that learns which approaches your specific environment tolerates — what’s “the way we do things here.”
What it doesn’t replace: actual fine-tuning, RLHF, or model training. When the gap is in the model’s underlying capability — bad at math, bad at a specific language, missing domain knowledge — adaptation can’t fix it. You’re working around the model, not inside it.
Trade-offs to be honest about:
- Score reliability. An LLM grading itself isn’t an oracle. Scores drift, scales aren’t calibrated, and the same output can get an 8 on Monday and a 6 on Friday. Use it for relative ranking and stopping conditions, not as a metric for management dashboards.
- Reward hacking. Optimize against a score and you’ll get outputs that game the score. If the evaluator rewards verbosity, the performer will pad. Cross-check periodically with a different evaluation rubric or human review.
- Stale strategies. A strategy that worked six months ago on an older model may underperform on the current one. Strategies need TTL or revalidation, just like any other piece of stored intelligence.
- Cost stacks fast. Every iteration is an LLM round-trip. Persisting strategies adds embedding and retrieval costs. Cap iterations aggressively (
MAX_ADAPTATIONS = 3is a sane default).
Rule of thumb: start with within-task adaptation (the loop in this post). Only move to across-task learning when you can point to a class of tasks where the same lessons keep getting relearned. The infrastructure overhead — embedding, retrieval, strategy storage — is real, and not worth building until you have evidence it’ll pay off.
The Bigger Picture#
This is post #9 in my series documenting Antonio Gulli’s Agentic Design Patterns. As always, full credit for the conceptual framework goes to him.
Learning composes with everything we’ve built so far. Memory (Chapter 8) is the substrate. Reflection (Chapter 4) is the within-iteration analogue. Multi-Agent (Chapter 7) gives you the option of dedicating an entire agent to evaluation and strategy selection. Planning (Chapter 6) is what the strategy actually shapes. None of these patterns exist in isolation — Learning is what ties them into a system that gets better over time.
All the code from this post is in my repository: carlosprados/Agentic_Design_Patterns, under 09_Learning_and_Adaptation/. The adaptive agent is a runnable example you can clone and extend with persistence if you want to take it the rest of the way. Works with Gemini and Ollama through the shared get_llm() abstraction.
What’s Next#
In the next post we’ll tackle the Model Context Protocol (MCP) — the emerging standard for how agents connect to external resources. Tools, data, services, all behind a single protocol. It’s where the fragmented landscape of integrations starts to look more like the early days of HTTP — and it’s about to matter a lot.
Stay tuned.

