Memory and planning
Hookโ
An agent computes a company's net revenue perfectly โ then, asked the very next question, computes it again from scratch, because it kept nothing from the first answer. A second agent is handed "produce the quarterly summary" and immediately calls a "write report" tool, having never gathered a single number. One agent has no memory; the other has no plan. Both fail the moment a task takes more than one step, which is to say: on any task worth using an agent for.
Conceptโ
The loop from Lesson 1 runs many times, and two things make those repetitions add up to real work: the agent has to remember what it has learned, and it has to sequence what it does next. Those are memory and planning.
Memory is the state the agent carries from one loop iteration to the next. In its simplest and most important form it is a scratchpad: a running record of the observations the agent has gathered and the intermediate results it has produced. When the loop "observes" a tool result, it writes that result into memory; when it next "plans," it reads memory to decide what is still needed. In the toy loop you will build in the lab, memory is a plain Python dictionary. That is not a simplification for teaching โ a dictionary threaded through the loop is genuinely the core of working memory. Larger systems layer more on top (conversation history, longer-term stores, retrieval), but the mechanism is the same: state that survives between steps.
Planning is turning a goal into an ordered sequence of actions. The central skill is task decomposition: breaking a goal too big for one tool call into subgoals that each map to a call. "Compute net revenue" decomposes into "compute gross" then "subtract refunds"; the second subgoal cannot start until the first has written its result into memory. That ordering โ and the dependency it encodes โ is the plan.
There are two honest styles of planning, and you will meet both:
| Style | How it decides | When it fits |
|---|---|---|
| Plan-then-act | Decompose the whole goal into an ordered plan up front, then execute it | Tasks whose steps are knowable in advance |
| Step-by-step | Plan one action, act, observe, then plan the next from the result | Tasks where each step depends on what you just saw |
Real tasks break, which is why the loop matters more than any fixed plan. A tool returns an error, a value is missing, an assumption was wrong. Replanning on failure is the agent's response: it observes the failure and chooses a different next action โ insert a missing lookup, retry with different arguments, or give up cleanly. Because control flow lives in the loop, replanning is just the next "plan" beat reacting to the last "observe." This is also where the iteration cap from Lesson 1 earns its place: replanning that never converges must still terminate.
Keep the discipline from the first two lessons. The plan and the memory you reason about are observable artifacts โ the ordered list of subgoals the agent emitted, and the recorded contents of the scratchpad. They are not a window into a model's private reasoning; they are the concrete state and actions in the record. When a multi-step run goes wrong, you debug it by reading the plan and the scratchpad at each step, not by guessing what the model "meant."
Worked exampleโ
Let me show decomposition and memory working together, deterministically and with no LLM. The goal is "net revenue." The planner decomposes it into an ordered plan, and the executor threads results through a memory dictionary so each step can use what the last one produced.
FACTS = {"units": 1200, "price": 9.50, "refund_rate": 0.05}
def decompose(goal):
"""Turn a goal into an ordered plan of subtasks โ the observable plan."""
return {
"net_revenue": ["compute_gross", "apply_refunds"],
}[goal]
def execute(subtask, memory):
"""Run one subtask, reading earlier results from memory (the scratchpad)."""
if subtask == "compute_gross":
return FACTS["units"] * FACTS["price"]
if subtask == "apply_refunds":
return memory["compute_gross"] * (1 - FACTS["refund_rate"])
raise KeyError(subtask)
def run_plan(goal, max_steps=8):
plan = decompose(goal)
memory = {}
for step, subtask in enumerate(plan, 1):
if step > max_steps: # iteration cap โ bounded even if a plan misbehaves
break
memory[subtask] = execute(subtask, memory)
print(f"step {step}: {subtask} -> {memory[subtask]}")
return memory
memory = run_plan("net_revenue")
print("final:", memory["apply_refunds"])
step 1: compute_gross -> 11400.0
step 2: apply_refunds -> 10830.0
final: 10830.0
Watch the two ideas cooperate. decompose returns the plan โ an ordered list,
["compute_gross", "apply_refunds"], that you can read and log. The executor
runs each subtask and writes its result into memory, so when
apply_refunds runs, it reads memory["compute_gross"] rather than recomputing
it. The dependency is real: swap the order and the second step fails, because the
value it needs is not in memory yet. That is decomposition and working memory in
their smallest honest form โ and the max_steps guard is the same bounded-loop
instinct, carried over so no plan can run away.
Hands-onโ
Now give an agent a two-step goal and watch it decompose and remember. The exercise below opens in the Agent Builder with a goal that no single tool can satisfy.
Do this: run the agent and open the trace. Identify the plan โ the ordered subgoals it chose โ and then find, for a later step, the memory entry it read that an earlier step wrote. Finally, break a tool so one step fails, re-run, and watch the agent replan. Success criteria: you can point to the decomposition, name one cross-step memory dependency, and describe how the replan differed from the original plan โ all from the trace, not from any guess about the model's reasoning.
Recapโ
- You can define memory as the state threaded between loop iterations, and describe a scratchpad as observations and intermediate results the loop writes and reads.
- You can explain planning as decomposing a goal into an ordered sequence of tool calls, and identify the dependency that fixes their order.
- You can contrast plan-then-act with step-by-step planning and say which suits a task whose steps depend on what was just observed.
- You can describe replanning on failure as the next plan beat reacting to a failed observation, and say why the iteration cap still bounds it.
That completes the core loop: observe and plan and act, tools that are bounded and validated, and memory and planning that turn single steps into finished work. Next you put all of it together in the lab โ a bounded, deterministic agent you build and run yourself, with two tools, a scratchpad, an iteration cap, and an explicit stop.
- Memory is the state an agent threads between loop steps โ a scratchpad the "observe" beat writes and the "plan" beat reads โ so later steps build on earlier results. - Planning is decomposing a goal into an ordered sequence of tool calls; the ordering encodes real dependencies between subgoals. - Replanning on failure is the next plan beat reacting to a failed observation, and the iteration cap still bounds it so a non-converging run always terminates.