AI EngineeringZero to ProductionHome·About·Contact
Multi-Agent Orchestration · Chapter M4

Deep Agents — Reflection, Planning & Long-Term Memory

A shallow agent does one ReAct loop and stops. A deep agent tackles long, open-ended work by combining four capabilities: an explicit plan, reflection to self-correct, long-term memory that survives the context window, and sub-agents for focused subtasks. This is the pattern behind "deep research" and coding agents — and the synthesis of everything in this module.

⏱️ ~55 min🧪 3 labs🎯 Advanced

Learning objectives

  • Explain what makes an agent "deep" — the four pillars and why long tasks need them.
  • Give an agent an explicit, updatable plan (a to-do list as state).
  • Add a reflection/critique step that improves output before finalizing.
  • Add long-term memory that persists beyond one run and beyond the context window.
  • Use sub-agents to keep the main context clean, and assemble all four into one agent.
The capstone of the agentic arcThis chapter reuses everything: L1's reflection & plan-execute, L5's persistence, M1–M2's sub-agents, M3's retrieval. A "deep agent" isn't a new framework — it's a composition pattern. LangGraph's deepagents-style scaffolding packages it, but you already have every piece.

Why shallow agents fail on long tasks advanced

A plain ReAct agent (L1) works great for a few steps. Point it at "research this market and write a 20-page report" and it falls apart in predictable ways — the exact failures the four pillars fix.

Shallow-agent failureDeep-agent fix
Loses the thread over many stepsPlanning — an explicit to-do it keeps returning to
Ships the first draft, mistakes and allReflection — critique & revise before finalizing
Forgets earlier findings as context fillsLong-term memory — offload to durable storage
One context tries to hold everythingSub-agents — isolate subtasks, return only results
agent loop 1 · Plan 3 · Memory 2 · Reflect 4 · Sub-agents a deep agent is a core loop wrapped in plan + reflect + memory + delegation Four pillars around one loop. The ReAct loop you already know sits in the center; planning gives it direction, reflection gives it quality, memory gives it endurance, and sub-agents give it focus. Remove any one and long-horizon work degrades.
🗺️ How to read this diagram

This picture is the whole lesson in one glance. A basic "agent" is just a loop: think, use a tool, look at the result, repeat. That loop is the box in the middle. A deep agent wraps four extra abilities around that same loop so it can handle long, messy tasks without falling apart.

  • The centre box, agent loop, is the ordinary think-act-observe cycle you already know. Everything else on the diagram is bolted onto it.
  • Reading clockwise from the top: 1 · Plan (a written to-do list so the agent knows the next step), 4 · Sub-agents on the right (hand a focused chunk of work to a helper), 3 · Memory at the bottom (save findings somewhere durable), and 2 · Reflect on the left (check its own work before finishing).
  • The dashed arrows point inward from each pillar to the loop — meaning each ability feeds into and supports that central cycle; the loop is not replaced, it's strengthened.
  • The line under the picture is the one-sentence summary: a core loop wrapped in plan + reflect + memory + delegation. If any single pillar is missing, long tasks start to degrade in the way the table above describes.

In short: "Deep" is not a new tool or library — it's these four helpers added around the plain loop. The rest of the lesson builds them one at a time, then snaps them together.

Lab M4.1 · Planning as state advanced

The simplest, highest-leverage pillar: give the agent an explicit to-do list in its state that it writes first and updates as it goes. It's L1's plan-and-execute, made durable via L4 state.

Example code for learning — review, test, and adapt it before running against real or production systems. Commands can create, change, or delete resources. See the Terms & Disclaimer.
Lab M4.1

Requires: pip install langgraph

plan.pyfrom typing import TypedDict, Annotated
from langgraph.graph import add_messages

class State(TypedDict):
    messages: Annotated[list, add_messages]
    plan: list[str]        # the to-do list, e.g. ["research X", "draft", "review"]
    done: list[str]        # completed steps, for progress & self-check

def make_plan(s):
    steps = model.invoke(f"Break this task into 3-6 concrete steps:\n{s['messages'][-1]}")
    return {"plan": _parse_steps(steps), "done": []}

def next_step(s) -> str:
    return END if not s["plan"] else "execute"   # plan empty → finished
▶ How this works

This is the first and highest-value pillar: give the agent a written plan it stores as data and keeps returning to. Instead of the plan living invisibly "in the model's head," it lives in a shared state object the code can read, update, and check — so the agent can't quietly lose the thread on a long job.

  1. State describes the shared scratchpad every step reads and writes. messages is the running conversation; plan is the list of to-do steps (e.g. ["research X", "draft", "review"]); done is the list of steps already finished, so you can see progress.
  2. make_plan runs first: it asks the model to "Break this task into 3-6 concrete steps" based on the user's latest message, then stores those steps into plan and starts done empty. This is the "write a to-do list first" habit, in code.
  3. next_step is the traffic controller: if the plan list is empty (not s["plan"] is true when there's nothing left) it returns END to stop; otherwise it returns "execute" to go do the next step. That's how the agent loops until the whole plan is finished.

What the output means: Nothing prints here — this is the wiring. In a full run, make_plan would fill plan with a handful of steps, and the agent would work through them one by one until next_step sees an empty list and returns END.

Try this: On paper, start with plan = ["a", "b"] and imagine removing one step each pass. next_step returns "execute" twice, then END — that is the entire plan-driven loop.

A visible plan is what keeps a long run coherentMaking the plan an explicit state field — not something buried in the model's head — means the agent can re-read it every step, mark items done, and re-plan when reality diverges. This is exactly why "write a todo list first" is the single most effective instruction for a long-horizon agent (it's how strong coding agents stay on track).

Lab M4.2 · Reflection advanced

Before finalizing, a deep agent critiques its own work and revises — L1's reflection architecture, as a bounded loop (L5). A fresh-context critic catches what the generator missed.

Lab M4.2
Setup to run this snippet
END = "__end__"          # sentinel used by graph examples
class _g_t:
    add_conditional_edges = 'demo'
    def add_conditional_edges(self, *a, **k): return 'demo'
    def __getattr__(self, k): return 'demo'
g = _g_t()
class _model_t:
    invoke = 'demo'
    def invoke(self, *a, **k): return 'demo'
    def __getattr__(self, k): return 'demo'
model = _model_t()
reflect.pydef reflect(s) -> str:
    """Critique the draft against the plan; decide revise or finish."""
    critique = model.invoke(
        f"Critique this draft against the requirements. "
        f"List concrete problems, or reply 'PASS'.\n\n{s['draft']}").content
    if "PASS" in critique or s["revisions"] >= 2:   # bound it — quality has diminishing returns
        return "finish"
    return "revise"   # feed the critique back to the generator

g.add_conditional_edges("reflect", reflect, {"revise": "generate", "finish": END})
▶ How this works

The second pillar is reflection: before the agent calls its work "done," it critiques its own draft and decides whether to revise. Crucially it uses a fresh critic prompt, so it reviews the draft with clear eyes instead of defending what it just wrote.

  1. reflect asks the model to "Critique this draft against the requirements" and either list concrete problems or reply PASS. The critique text comes back in .content.
  2. The if decides when to stop: if the critique contains "PASS" (the draft is good enough) or the agent has already revised twice (s["revisions"] >= 2), it returns "finish". Capping revisions matters — extra passes give shrinking returns and can even undo good work.
  3. Otherwise it returns "revise", which sends the critique back so the generator can improve the draft and try again.
  4. The last line, g.add_conditional_edges(...), wires that decision into the graph: from the "reflect" step, "revise" loops back to "generate" and "finish" goes to END.

What the output means: Not run directly. In practice the draft cycles generate → reflect → (revise → generate) at most twice, then the agent finishes with a cleaner result than its first draft.

Try this: Picture a draft that's missing one requirement. Pass 1 finds it and returns "revise"; the rewrite fixes it; pass 2 replies PASS"finish". That's the bounded loop the warning box is describing.

Reflection has a sweet spotOne or two reflection passes markedly improve quality; beyond that you pay tokens for shrinking gains and can even make things worse as the model second-guesses good work. Cap revisions (like M3's retrieval loop and L5's cycles) — and use a separate critic prompt/context so it's genuinely critiquing, not rationalizing its own draft.

Lab M4.3 · Long-term memory expert

The context window is finite; a long task overflows it. Long-term memory offloads durable facts to external storage the agent reads and writes as a tool — surviving both the context window and the run itself.

agent (context) write findings recall when needed memory storedurable · outlives context short-term = the messages in context · long-term = external, persistent Two kinds of memory. Short-term is the messages in the current context (L2/L4). Long-term is an external store — a file, a DB, or a vector store — the agent writes key facts to and recalls later, so knowledge outlives both the window and the session.
🗺️ How to read this diagram

This diagram explains the third pillar, long-term memory. The problem it solves: the model's working memory (its "context window") is a fixed size, so on a long task the earliest findings get pushed out and forgotten. The fix is to keep important facts in a separate store outside the context.

  • The left box, agent (context), is the agent's short-term working memory — only the messages currently in the conversation. It's limited and temporary.
  • The top arrow labelled write findings goes from the agent out to the store: when the agent learns something worth keeping, it saves it.
  • The lower dashed arrow labelled recall when needed goes back the other way: later, the agent asks the store to hand a saved fact back into context.
  • The right box, memory store, is marked durable · outlives context — a file, database, or vector store that survives even after the run ends. The caption sums up the split: short-term = messages in context; long-term = this external, persistent store.

In short: Think of the context window as your desk (small, cleared each day) and the memory store as a filing cabinet (keeps things for good). The agent writes notes to the cabinet and pulls them back out when needed.

Lab M4.3

Requires: pip install langchain-core

memory.pyfrom langchain_core.tools import tool

@tool
def save_memory(key: str, value: str) -> str:
    """Save a durable fact for later. Use for findings you'll need after the context fills."""
    store.put(key, value)                 # file / DB / vector store — outlives this run
    return "saved"

@tool
def recall_memory(query: str) -> str:
    """Retrieve durable facts saved earlier."""
    return store.search(query)             # semantic recall (M3 retrieval)

agent = create_react_agent(model, tools=[save_memory, recall_memory, ...])
▶ How this works

Here long-term memory becomes real code. The trick: give the agent two tools — one to save a fact and one to look facts up. A "tool" is just a function the model is allowed to call on its own, so the agent decides when to remember and when to recall.

  1. The @tool decorator marks each function as something the agent can call. The text inside the triple-quoted """docstring""" is not a comment for you — the model reads it to decide when to use the tool, so it's written as an instruction.
  2. save_memory(key, value) stores a fact under a name via store.put(...). Its docstring tells the agent to use it for findings you'll need after the context fills — exactly the "write findings" arrow from the diagram.
  3. recall_memory(query) is the other direction: store.search(query) finds relevant saved facts by meaning (the same semantic search from the RAG lesson), matching the "recall when needed" arrow.
  4. The last line, create_react_agent(model, tools=[save_memory, recall_memory, ...]), hands both tools to the agent so it can save and recall on its own during a run.

What the output means: No output on its own — this defines the memory tools. Once handed to the agent, calling save_memory returns "saved" and recall_memory returns the stored text later, even after earlier messages have scrolled out of context.

Try this: The real skill isn't the code — it's judgement about what to save. Store distilled decisions and findings, not whole raw transcripts, so recall stays useful and cheap.

Memory tiers you've already metLong-term memory reuses tools you know: a durable store (L5's checkpointer, or a real DB) plus semantic recall (M3's retrieval). LangGraph's memory store and the memory tool are ready-made backends. The skill is deciding what is worth remembering — save decisions and findings, not raw transcripts.

Sub-agents: focus through isolation expert

The fourth pillar reuses M1/M2 directly. A deep agent spawns a sub-agent for a focused subtask (research one source, verify one claim); the sub-agent burns its own context and returns only the result, keeping the main agent's context clean and on-plan.

Sub-agents are context hygiene, not just parallelismThe win isn't only doing things at once — it's that a sub-agent's messy intermediate work (dozens of tool calls, dead ends) stays in its context, and the parent sees just the clean answer. That's why deep-research agents delegate: it keeps the orchestrator's context focused on the plan, not the noise. (This is the supervisor topology from L1/M1.)

Putting the four pillars together expert

A deep agent is a LangGraph graph that wires all four into one loop:

Deep agent skeleton plan → # write the to-do list into state (M4.1) loop over plan items: execute step → maybe spawn sub-agent for focused work (M1/M2) write findings to long-term memory (M4.3) mark item done; re-plan if needed reflect on the result → revise or finish (M4.2, bounded) compile(checkpointer=# durable & resumable — L5) # every irreversible action still passes the HITL gate (L5)
PillarReusesBuys
PlanningL1 plan-execute + L4 stateCoherence over long runs
ReflectionL1 reflection + L5 bounded cycleQuality / self-correction
Long-term memoryL5 store + M3 retrievalEndurance past the context window
Sub-agentsM1/M2 + L1 supervisorFocus & clean context
Depth multiplies cost, latency, and riskPlan + reflect + memory + sub-agents means many more LLM calls and a wider failure/injection surface (T1, M3). Add pillars only when a shallow agent demonstrably fails the task — most problems don't need all four. And keep the L5 safety gate in front of every irreversible action, no matter how sophisticated the agent above it.

Common pitfalls expert

PitfallFix
Plan lives only in the model's headMake it explicit state the agent re-reads and updates
Unbounded reflection loopCap revisions; use a separate critic context
Saving raw transcripts as "memory"Save distilled findings/decisions, not noise
Sub-agents for trivial stepsDelegate only focused, context-heavy subtasks
Adding all four pillars by defaultStart shallow; add a pillar only when it fails without it
Sophisticated agent, no safety gateDepth doesn't replace the L5 HITL gate on risky actions

Exercises expert

Exercise M4.1 — Plan-driven agent

Context: An agent that keeps its plan in state — writing it, executing it, and updating it — stays on a multi-step task instead of wandering. Watching it drain its own plan is the proof.

Your task: Build an agent that writes a plan into state, executes each step, and marks it done, looping until the plan is empty, then give it a multi-step task.

Requirements:

  • The plan lives in state, written before execution begins
  • Execute steps one at a time, marking each done as it completes
  • Loop until the plan has no open steps left
  • Confirm the agent follows and updates its own plan rather than wandering

💡 Hint: The signal of success is that the agent consults "what's next" from state each step, not that it happens to finish.

Exercise M4.2 — Add reflection

Context: A bounded reflection loop critiques a draft before finishing — but more reflection is not monotonically better. Pushing the cap up reveals the plateau where extra passes stop helping.

Your task: Add a bounded reflection loop (max 2 revisions) that critiques the draft before finishing, compare the reflected output to the first draft on a task with an easy-to-miss requirement, then push revisions to 5 and note where quality plateaus.

Requirements:

  • Add a critique-and-revise loop capped at 2 revisions
  • Choose a task with an easy-to-miss requirement so reflection has something to catch
  • Compare the reflected output against the un-reflected first draft
  • Raise the cap to 5 and observe where quality stops improving

💡 Hint: Revision 1→2 usually fixes real issues; past that the critic starts nitpicking or undoing good choices — that plateau is why you cap it.

Show what to look for

Revision 1→2 usually fixes real issues; past that the critic starts nitpicking or undoing good choices. That plateau is why you cap it — more reflection is not monotonically better.

Exercise M4.3 — Deep research agent (capstone of the module)

Context: The capstone combines all four pillars into a mini deep-research agent — the same shape as Project 6 in the gallery. By now every piece has been built in isolation; this wires them together.

Your task: Combine the four pillars into a mini "deep research" agent: plan the research, spawn sub-agents per source, save findings to memory, retrieve them to write the report, and reflect before finishing.

Requirements:

  • Plan the research as explicit, updatable state
  • Spawn a sub-agent per source, each with isolated context (from M1/M2)
  • Save findings to long-term memory and retrieve them when writing the report
  • Run a reflection pass before finishing
  • Integrate all four pillars end to end, not as separate demos

💡 Hint: You already have every piece from the ladder and earlier modules; the deliverable is the integration, so lean on the seams you built rather than rewriting them.

🪜 Practice ladder beginner → industry

Six graded exercises, easy to real-world. Try each before opening its solution.

Exercise 1 · Why shallow agents fail on long tasksBeginner

Context: A shallow agent is a thin think-act loop that drifts on long tasks. Four pillars fix this — planning, reflection, long-term memory, and sub-agents — and each maps to a specific long-horizon failure it prevents.

Your task: Name the four pillars of a deep agent and map each one to the failure mode it prevents, as a runnable lookup.

Requirements:

  • Enumerate all four pillars: planning, reflection, long-term memory, sub-agents
  • Pair each pillar with the concrete failure it addresses
  • Planning fixes drift; reflection fixes shipping a flawed first draft
  • Memory fixes re-learning / lost context; sub-agents fix one overloaded context window
  • Print the pillar-to-failure mapping

💡 Hint: The through-line: a long task needs an explicit plan to stay on target, reflection to self-correct, memory to persist, and sub-agents to keep each context focused.

Show solution

Each pillar targets a specific long-horizon failure. Runnable lookup:

PILLARS = {
    "planning (to-do as state)": "drift / forgetting the goal mid-task",
    "reflection (critique loop)": "shipping a first draft with obvious errors",
    "long-term memory":          "re-learning facts every run / losing context",
    "sub-agents":                "one context window overloaded with everything",
}
for pillar, fixes in PILLARS.items():
    print(f"{pillar:<28} fixes -> {fixes}")

The through-line: a long task needs an explicit plan to stay on target, reflection to self-correct, memory to persist, and sub-agents to keep each context focused.

Exercise 2 · Planning as state: a to-do list the agent updatesIntermediate

Context: "Make a to-do list first" is the single most effective long-horizon instruction, but only if the plan is durable state the agent re-reads each step rather than a line buried in the prompt.

Your task: Model a plan as updatable state: a Plan that creates tasks, marks them done, and always surfaces the next open task plus overall progress.

Requirements:

  • Represent each task with a done/not-done flag
  • Expose the next open task (skipping completed ones)
  • Allow marking a specific task complete
  • Report progress as a done/total count alongside the next task
  • Demonstrate creating a plan and completing a few tasks

💡 Hint: Keeping the plan as inspectable state — not just in the prompt — is what stops a long-running agent from drifting; it re-reads "what's next" every step.

Show solution

The plan is durable state the agent reads and writes each step. Runnable:

class Plan:
    def __init__(self, tasks):
        self.tasks = [{"task": t, "done": False} for t in tasks]
    def next_open(self):
        for t in self.tasks:
            if not t["done"]:
                return t["task"]
        return None
    def complete(self, task):
        for t in self.tasks:
            if t["task"] == task:
                t["done"] = True
    def progress(self):
        done = sum(t["done"] for t in self.tasks)
        return f"{done}/{len(self.tasks)} done; next: {self.next_open()}"

p = Plan(["gather sources", "draft", "review", "publish"])
print(p.progress())
p.complete("gather sources"); p.complete("draft")
print(p.progress())

Keeping the plan as inspectable state (not just in the prompt) is what stops a long-running agent from drifting — it re-reads "what's next" every step.

Exercise 3 · Reflection: a bounded critique loopAdvanced

Context: Reflection generates, critiques, and revises until the draft passes — but bounded, so it terminates. An unbounded critique loop is a classic way to burn tokens forever.

Your task: Model a draft → critique → revise loop that stops on a clean critique or after a fixed number of passes.

Requirements:

  • A critique step returns a list of concrete problems with the draft
  • A revise step fixes the reported problems and returns a new draft
  • Loop until the critique is empty (PASS) or the pass cap is reached
  • Bound the loop with a max_passes limit and report best-effort on exhaustion
  • Demonstrate a draft that needs several revisions to pass

💡 Hint: Reflection catches obvious errors before finalizing; the pass cap is essential, not optional — treat it as the termination guarantee.

Show solution

The bounded reflection loop from the lesson. Runnable:

def critique(draft):
    problems = []
    if len(draft.split()) < 5:      problems.append("too short")
    if "TODO" in draft:            problems.append("has placeholder")
    if not draft.endswith("."):    problems.append("no closing punctuation")
    return problems

def revise(draft, problems):
    if "too short" in problems:        draft += " with more detail added here"
    if "has placeholder" in problems:  draft = draft.replace("TODO", "the result")
    if "no closing punctuation" in problems and not draft.endswith("."):
        draft += "."
    return draft

def reflect(draft, max_passes=3):
    for p in range(1, max_passes + 1):
        problems = critique(draft)
        if not problems:
            return f"PASS on pass {p}: {draft!r}"
        draft = revise(draft, problems)
    return f"stop after {max_passes} passes (best effort): {draft!r}"

print(reflect("TODO"))

Reflection catches obvious errors before finalizing; the max_passes bound is essential — an unbounded critique loop is a classic way to burn tokens forever.

Exercise 4 · Long-term memory across runsExpert

Context: Long-term memory persists beyond a single run: durable facts are saved and later recalled by relevance. In production the recall is embedding-based, but the save/recall seam is identical to a simpler term-overlap stand-in.

Your task: Model a keyed Memory store with save(fact) and a recall(query, k) that returns the most relevant saved facts.

Requirements:

  • Persist facts across calls in the store
  • Recall ranks saved facts by relevance to the query (term overlap stands in for embeddings)
  • Return only the top k facts, dropping zero-overlap ones
  • Note that production recall would be embedding-based (the M3 retriever)
  • Demonstrate saving several facts and recalling by two different queries

💡 Hint: Memory turns a stateless agent into one that improves across sessions; keep the save/recall seam clean so the ranking can later be swapped for real embeddings.

Show solution

Persist facts and recall by term overlap (a stand-in for embedding search). Runnable:

class Memory:
    def __init__(self):
        self.facts = []               # would be a vector store in production
    def save(self, fact):
        self.facts.append(fact)
    def recall(self, query, k=2):
        qt = set(query.lower().split())
        scored = [(len(qt & set(f.lower().split())), f) for f in self.facts]
        scored = [s for s in scored if s[0] > 0]
        return [f for _, f in sorted(scored, reverse=True)[:k]]

mem = Memory()
mem.save("The user prefers concise, bulleted answers.")
mem.save("The user's timezone is IST.")
mem.save("Project deadline is the 30th.")
print(mem.recall("what answer format does the user like"))
print(mem.recall("when is the deadline"))

Memory turns a stateless agent into one that improves across sessions; in production the recall is embedding-based (M3 retrieval), but the save/recall seam is identical.

Exercise 5 · Sub-agents: focus through isolationProfessional

Context: Sub-agents keep each context window focused: a manager delegates a subtask to a fresh agent that sees only the slice of context it needs, then merges the compact results. Isolation — not delegation alone — is the point.

Your task: Model a manager that delegates subtasks to isolated sub-agents, passing each only the context keys it needs, then merges their results.

Requirements:

  • Each sub-agent receives only the context slice its subtask requires
  • A sub-agent never sees the manager's full history or other agents' raw data
  • The manager iterates the subtasks, delegating and collecting results
  • Merge the sub-agents' outputs into a single result keyed by the goal
  • Demonstrate a manager splitting a goal across at least two isolated sub-agents

💡 Hint: Isolation keeps every context window small, focused, and cheap — the writer should never see the researcher's raw sources, only what it needs.

Show solution

Each sub-agent gets a minimal, isolated context — not the manager's whole history. Runnable:

def sub_agent(name, task, context):
    # isolated: only sees the slice it needs, returns a compact result
    return {"agent": name, "task": task,
            "result": f"[{name}] done '{task}' using {len(context)} ctx items"}

def manager(goal, subtasks, shared_ctx):
    results = []
    for name, task, needed_keys in subtasks:
        isolated = {k: shared_ctx[k] for k in needed_keys if k in shared_ctx}
        results.append(sub_agent(name, task, isolated))
    return {"goal": goal, "merged": [r["result"] for r in results]}

ctx = {"sources": [1, 2, 3], "style": "concise", "budget": 100}
plan = [("researcher", "gather facts", ["sources"]),
        ("writer",     "draft summary", ["style"])]
import json
print(json.dumps(manager("write a brief", plan, ctx), indent=2))

Isolation is the point: the writer never sees the researcher's raw sources, only what it needs — which keeps each context window small, focused, and cheap.

Exercise 6 · The four pillars together (offline) + the SDK shapeIndustry scenario

Context: A deep agent assembles all four pillars into one bounded orchestrator: plan the work, delegate each task to a sub-agent, reflect on its output, and persist learnings to memory. Offline you can model the full control flow; the SDK later adds durability and streaming.

Your task: Build an offline deep_agent(goal, tasks) that plans, delegates each task to a sub-agent, reflects on the output, and saves a learning to memory — then sketch the equivalent create_react_agent shape marked as needing the SDK.

Requirements:

  • Turn the tasks into a plan with per-task done flags
  • For each task: delegate to a sub-agent, run one bounded reflection pass, then persist a learning to memory
  • Mark each task done and record a transcript of results
  • Return the goal, a done flag, the step transcript, and the accumulated memory
  • Include a commented create_react_agent + checkpointer sketch, labelled as needing the SDK
  • Note the SDK adds durable checkpointing, streaming, and real tool execution over this same orchestration

💡 Hint: The offline model is the orchestration logic of all four pillars; the SDK's checkpointer is what makes memory durable across runs.

Show solution

All four pillars in one bounded orchestrator. Runnable:

def deep_agent(goal, tasks):
    plan = [{"task": t, "done": False} for t in tasks]
    memory, transcript = [], []
    for step in plan:
        # 1) plan: take next task  2) sub-agent: do it (isolated)
        raw = f"output for '{step['task']}'"
        # 3) reflect: one bounded critique/revise pass
        good = raw if raw.endswith("'") else raw + "."
        # 4) memory: persist a learning
        memory.append(f"learned: {step['task']} completed")
        step["done"] = True
        transcript.append({"task": step["task"], "result": good})
    return {"goal": goal, "done": all(s["done"] for s in plan),
            "steps": transcript, "memory": memory}

import json
print(json.dumps(deep_agent("write a report", ["research", "draft", "review"]),
                 indent=2))
# needs the SDK -- the same idea with LangGraph's prebuilt agent (documented API):
# from langgraph.prebuilt import create_react_agent
# from langgraph.checkpoint.memory import MemorySaver
# agent = create_react_agent(model, tools=[plan_tool, memory_tool, subagent_tool],
#                            checkpointer=MemorySaver())   # persistence = long-term memory
# agent.invoke({"messages": [("user", "write a report")]},
#              config={"configurable": {"thread_id": "run-1"}})

The offline model shows the control flow of all four pillars; the SDK adds durable checkpointing (memory across runs), streaming, and real tool execution — but the orchestration logic is what you just modeled.

✓ Checkpoint — you can move on when you can…

  • Name the four pillars and the shallow-agent failure each fixes.
  • Give an agent an explicit, updatable plan in state.
  • Add a bounded reflection loop with a separate critic.
  • Add long-term memory that outlives the context window.
  • Explain why sub-agents are about context hygiene, and assemble all four.
🏗️ Toward the capstone — and module completeThe AI DevOps Engineer is a deep agent: it plans a remediation, spawns sub-agents to investigate services in parallel, keeps incident history in long-term memory, and reflects on its plan before proposing it — all behind the L5 safety gate. You've now completed the agentic arc: architectures (L1), single graphs (L2–L5), and multi-agent depth (M1–M4). The Deep Research Agent project is the natural place to put it all together. See the capstone design →

Knowledge check check yourself

✓ Knowledge check

What are the four pillars of a deep agent, and which shallow-agent failure does each fix?

Show answer
Planning (explicit updatable to-do) fixes losing the thread over many steps; Reflection (critique/revise before finalizing) fixes shipping the first flawed draft; Long-term memory (durable external store) fixes forgetting earlier findings as context fills; Sub-agents (isolate subtasks, return only results) fix one context trying to hold everything. A deep agent is a composition pattern around the ReAct loop, not a new framework.
✓ Knowledge check

Why are sub-agents described as "context hygiene, not just parallelism"?

Show answer
The main win isn't doing things at once — it's that a sub-agent's messy intermediate work (dozens of tool calls, dead ends) stays inside its own context, and the parent orchestrator receives only the clean result. That keeps the orchestrator's context focused on the plan rather than the noise (the supervisor topology from L1/M1).
© 2026 studybydoing.in · AI Engineering: Zero to Production · All rights reserved. · About · Privacy Policy · Terms · Contact
Educational content, provided as-is and without warranty. Code samples are examples — review, test, and adapt them before using in production. See the Terms of Use & Disclaimer. Use at your own risk.
© studybydoing.in