TechByteByByte

Reasoning and Planning

Level 4 begins here: why agents sometimes need to plan ahead across multiple steps before acting — task decomposition, dependencies, and static vs. dynamic planning, using a complex trip-planning example.

#AI Agents#AI#Planning#Level 4

Begin with the problem

Some goals contain dependent steps that should be considered before acting. Planning makes those steps visible, but plans must still adapt to new evidence.

large goal → smaller tasks → dependencies → execute → inspect evidence → replan when needed

What you will learn

  • Separate reasoning about the next decision from planning several future steps.
  • Break a large goal into smaller tasks with dependencies.
  • Replan when an observation makes the original plan outdated.
  • Recognize when a fixed workflow is more reliable than model-generated planning.

Current real-system grounding: Google’s tool documentation shows the critical difference between provider-executed built-in tools and custom functions executed by your application.

These official links document available product features. They do not reveal every provider’s private implementation, hidden reasoning, default setting, or internal limit.

1. The problem this module solves

Module 4’s loop reasons one step at a time — observe, decide, act, repeat. This module addresses a different, complementary question: sometimes an agent benefits from thinking through several steps before acting at all, rather than purely reacting one observation at a time. This is planning.


2. The Problem — Reactive Loops Can Be Inefficient

Pure reactive loop (Module 4): observe -> decide ONE next step -> act
                              -> observe -> decide ONE next step ->
                              act ->...

For a complex, multi-part goal, this can mean the agent
discovers dependencies and structure ONE STEP AT A TIME, potentially
taking inefficient or even wrong early actions it later has
to undo.

Planning addresses this by having the agent reason about the whole task’s structure — real subtasks and their dependencies — before committing to a sequence of actions.


3. Task Decomposition — Breaking a Goal Into Subtasks

Goal: "Plan my 5-day trip to Japan."

DECOMPOSED into subtasks:

1. Understand requirements (dates, budget, preferences)
2. Search flights
3. Search hotels
4. Build itinerary
5. Check constraints (budget, dates)
6. Adjust plan if needed
7. Present final result

Notice the real DEPENDENCIES here: “Build itinerary” can’t happen before BOTH “Search flights” AND “Search hotels” are done. Recognizing this structure UP FRONT — rather than discovering it reactively, one step at a time — is precisely what planning adds.


4. Sequential vs. Parallel Dependencies

SEQUENTIAL: subtask B CANNOT start until subtask A finishes
           (e.g., "Build itinerary" needs flight AND hotel results
           first)

PARALLEL: subtasks with NO real dependency on each other CAN, in
         principle, run independently (e.g., "Search flights" and
         "Search hotels" don't depend on each other)

Recognizing which subtasks are parallel-capable versus sequential is directly useful for efficiency — an agent (or multi-agent system, Module 15) that recognizes “Search flights” and “Search hotels” are independent can potentially pursue both without artificially waiting.


5. Static Planning vs. Dynamic Planning

STATIC PLANNING:      the ENTIRE plan is decided UP FRONT, before any
                     execution begins -- appropriate when
                     the task's structure is well-understood in
                     advance

DYNAMIC PLANNING:         the plan is built, and REVISED,
                         INCREMENTALLY as new information becomes
                         available -- necessary when EARLY
                         steps might reveal information that changes
                         what LATER steps should even be
Example of a static plan needing to become dynamic:

Original plan: "Search flights -> Search hotels -> Build itinerary"

But flight search REVEALS: "No direct flights available -- requires
a layover, adding a day of travel"

A dynamic planner ADAPTS: "I need to REVISE the remaining
plan -- the trip is now effectively 4 days at the destination, not
5"

This is why planning and the reactive loop (Module 4) aren’t competing approaches — a well-designed agent USES planning to establish real structure, while STILL looping reactively within and across that structure to handle exactly this kind of mid-execution discovery.


6. Plan-and-Execute — Combining Both

flowchart TD
    Goal[Goal] --> Plan[Create Initial Plan<br/>decompose into subtasks]
    Plan --> Exec[Execute Next Subtask<br/>via the agent loop, Module 4]
    Exec --> Check{New information<br/>changes the plan?}
    Check -->|No| Next{More subtasks<br/>remaining?}
    Check -->|Yes| Revise[Revise Remaining Plan]
    Revise --> Next
    Next -->|Yes| Exec
    Next -->|No| Done[Present Final Result]

This pattern — plan, execute a piece, check whether the plan still holds, revise if needed, continue — is one of the most common real architectures for handling complex, multi-part goals.


7. A Real Developer Example

TechCorp builds a travel-booking assistant using exactly this pattern:

StepWhat Happens
1. DecomposeBreak “5-day Japan trip” into flights, hotels, itinerary, constraints
2. Execute: search flightsDiscovers no direct flights — adds a layover day
3. Check plan validityThe original “5 days at destination” assumption is now wrong
4. Revise planAdjust itinerary subtask to plan for 4 destination days, not 5
5. Execute: search hotelsProceeds normally, unaffected by the flight discovery
6. Execute: build itineraryUses the REVISED constraint (4 days), not the original plan
7. Present final resultA coherent plan reflecting the real, discovered constraint

8. A Simple Agentic AI Connection

Planning connects directly to Module 15’s multi-agent systems — a common pattern has one agent responsible for decomposition and planning (a “planner” or “supervisor” role), while separate agents or tool calls execute individual subtasks, exactly mirroring this module’s dependency structure (Section 3-4) at the multi-agent level.


9. How Is This Used in AI?

🤖 How Is This Used in AI?

Production agent systems handling complex, multi-part goals — research assistants, complex booking systems, multi-step data analysis pipelines — typically implement some version of plan-and- execute, since purely reactive, one-step-at-a-time reasoning can be both less efficient and more prone to inconsistency across a long, complex task.


10. Real-World Applications

  • Travel and itinerary planning assistants
  • Research agents decomposing a broad question into focused sub-investigations
  • Project or task-management agents breaking a large request into trackable subtasks

11. Common Mistakes

Incorrect idea: Committing rigidly to a static plan even after discovering information that invalidates it.

Why it is incorrect: As shown directly in Section 5, dynamic revision is necessary when early steps reveal new, plan-changing information.

Incorrect idea: Treating every task as needing explicit planning.

Why it is incorrect: Many simple tasks are well-served by Module 4’s pure reactive loop alone — planning adds real overhead that’s only worth it for complex, multi-part goals.

Incorrect idea: Missing real dependencies between subtasks.

Why it is incorrect: As shown directly in Section 3-4, executing “Build itinerary” before flight and hotel results are available would fail or produce incoherent results.


12. Limitations

  • Planning adds real reasoning overhead (and cost, Module 19-20) before any action is taken — a real trade-off against pure reactive looping’s simplicity
  • An LLM’s initial decomposition can be wrong or incomplete — dynamic revision (Section 5) helps, but doesn’t eliminate the risk of a fundamentally flawed initial plan

13. Quick Reference

flowchart LR
    G[Complex Goal] --> D[Decompose into<br/>Subtasks]
    D --> Dep[Identify Dependencies:<br/>Sequential vs Parallel]
    Dep --> S{Static or<br/>Dynamic?}
    S -->|Structure well-known| ST[Static Plan]
    S -->|May need revision| DY[Dynamic Plan<br/>revise as needed]

14. Code — Implementing Task Decomposition With Dependencies

🎯 Target of this example: implement Section 3’s Japan trip decomposition directly — real subtasks with explicit dependencies, and a function correctly determining which subtasks are ready to execute based on what’s already been completed.

Example 1 — Simple

from dataclasses import dataclass, field

@dataclass
class Subtask:
    description: str
    depends_on: list = field(default_factory=list)

def decompose_goal(goal: str) -> list:
    """Directly implements Section 3's decomposition -- real
    subtasks with EXPLICIT dependencies, decided up front (static
    planning, Section 5)."""
    if "trip" in goal.lower():
        return [
            Subtask("Understand requirements (dates, budget, preferences)"),
            Subtask("Search flights", depends_on=["Understand requirements (dates, budget, preferences)"]),
            Subtask("Search hotels", depends_on=["Understand requirements (dates, budget, preferences)"]),
            Subtask("Build itinerary", depends_on=["Search flights", "Search hotels"]),
            Subtask("Check constraints (budget, dates)", depends_on=["Build itinerary"]),
            Subtask("Present final result", depends_on=["Check constraints (budget, dates)"]),
        ]
    return [Subtask("No decomposition available")]

def can_execute(subtask: Subtask, completed_descriptions: set) -> bool:
    """A subtask can run only once ALL its dependencies are done --
    exactly Section 4's dependency principle."""
    return all(dep in completed_descriptions for dep in subtask.depends_on)

plan = decompose_goal("Plan my 5-day trip to Japan")
completed = set()

print(f"Plan has {len(plan)} subtasks:\n")
for i, task in enumerate(plan, 1):
    ready = can_execute(task, completed)
    print(f"{i}. {task.description} (ready to run: {ready}, depends on: {task.depends_on})")
    if ready:
        completed.add(task.description)

Expected Output:

Plan has 6 subtasks:

1. Understand requirements (dates, budget, preferences) (ready to
run: True, depends on: [])
2. Search flights (ready to run: True, depends on: ['Understand
requirements (dates, budget, preferences)'])
3. Search hotels (ready to run: True, depends on: ['Understand
requirements (dates, budget, preferences)'])
4. Build itinerary (ready to run: True, depends on: ['Search
flights', 'Search hotels'])
5. Check constraints (budget, dates) (ready to run: True, depends
on: ['Build itinerary'])
6. Present final result (ready to run: True, depends on: ['Check
constraints (budget, dates)'])

What we conclude from this example: each subtask’s readiness correctly depends on whether its real prerequisites have already been completed — processed in dependency order here, every subtask is ready exactly when it should be, directly demonstrating Section 3-4’s dependency structure as real, checkable logic.

Example 2 — Intermediate

from dataclasses import dataclass, field

@dataclass
class Subtask:
    description: str
    depends_on: list = field(default_factory=list)
    result: str = None

def execute_subtask(subtask: Subtask, environment: dict) -> str:
    """Simulates executing a subtask and discovering new
    information -- Section 5's dynamic planning scenario."""
    if "search flights" in subtask.description.lower():
        return environment.get("flight_search_result", "no result")
    return "completed"

def revise_plan_if_needed(plan: list, discovery: str) -> list:
    """Directly implements Section 5's dynamic revision -- if a
    discovery changes the plan, update remaining subtasks
    accordingly."""
    if "layover" in discovery.lower():
        for subtask in plan:
            if "itinerary" in subtask.description.lower():
                subtask.description += " [REVISED: plan for 4 destination days, not 5, due to layover]"
    return plan

plan = [
    Subtask("Search flights"),
    Subtask("Search hotels"),
    Subtask("Build itinerary"),
]
environment = {"flight_search_result": "No direct flights -- requires a layover, adding a day of travel"}

discovery = execute_subtask(plan[0], environment)
print(f"Discovery from executing '{plan[0].description}': {discovery}")

plan = revise_plan_if_needed(plan, discovery)
print(f"\nRevised plan:")
for task in plan:
    print(f"  - {task.description}")

Expected Output:

Discovery from executing 'Search flights': No direct flights --
requires a layover, adding a day of travel

Revised plan:
  - Search flights
  - Search hotels
  - Build itinerary [REVISED: plan for 4 destination days, not 5, due
to layover]

What we conclude from this example: the discovery from executing the FIRST subtask (no direct flights) correctly triggers a revision to a LATER, still-pending subtask (the itinerary) — exactly Section 5 and 7’s dynamic planning scenario, where new information changes the remaining plan rather than the agent rigidly following its original, now-outdated assumptions.

Example 3 — Production Grade

from dataclasses import dataclass, field
from enum import Enum

class SubtaskStatus(Enum):
    PENDING = "pending"
    READY = "ready"
    COMPLETED = "completed"
    REVISED = "revised"

@dataclass
class Subtask:
    description: str
    depends_on: list = field(default_factory=list)
    status: SubtaskStatus = SubtaskStatus.PENDING

class PlanAndExecuteAgent:
    """A production-style implementation of Section 6's COMPLETE
    plan-and-execute pattern -- decompose, execute each ready
    subtask, check for plan-invalidating discoveries, revise, and
    continue, exactly Section 7's TechCorp trace made into real,
    working logic."""

    def __init__(self, plan: list):
        self.plan = plan
        self.completed_descriptions = set()

    def _get_ready_subtasks(self) -> list:
        return [t for t in self.plan
                if t.status == SubtaskStatus.PENDING
                and all(dep in self.completed_descriptions for dep in t.depends_on)]

    def _revise_if_needed(self, discovery: str):
        if "layover" in discovery.lower():
            for task in self.plan:
                if "itinerary" in task.description.lower() and task.status == SubtaskStatus.PENDING:
                    task.description += " [REVISED for layover]"
                    # Deliberately KEEP status as PENDING -- a revision
                    # changes WHAT the subtask will do, not whether it
                    # still needs to run. If we marked it REVISED here,
                    # it would never be picked up by the readiness
                    # check again, and would silently never execute.

    def run(self, environment: dict) -> list:
        trace = []
        while True:
            ready = self._get_ready_subtasks()
            if not ready:
                break
            for task in ready:
                task.status = SubtaskStatus.COMPLETED
                self.completed_descriptions.add(task.description.split(" [REVISED")[0])
                discovery = environment.get(task.description.lower().replace(" ", "_") + "_result", "")
                if discovery:
                    self._revise_if_needed(discovery)
                trace.append({"executed": task.description, "discovery": discovery})
        return trace

plan = [
    Subtask("Search flights"),
    Subtask("Search hotels"),
    Subtask("Build itinerary", depends_on=["Search flights", "Search hotels"]),
]
environment = {"search_flights_result": "No direct flights -- requires a layover"}

agent = PlanAndExecuteAgent(plan)
trace = agent.run(environment)

for step in trace:
    print(f"Executed: {step['executed']}")
    if step["discovery"]:
        print(f"  Discovery: {step['discovery']}")

Expected Output:

Executed: Search flights
  Discovery: No direct flights -- requires a layover
Executed: Search hotels
Executed: Build itinerary [REVISED for layover]

What we conclude from this example: the “Build itinerary” subtask was automatically, structurally revised to include the layover adjustment BEFORE it was ever executed — because the discovery from “Search flights” (executed earlier) triggered the revision, and the dependency-based readiness check (Example 1’s logic, reused here) ensured “Build itinerary” only ran after both its prerequisites, by which point the revision had already been applied. This is exactly the complete plan-and-execute pattern from Section 6, working end-to-end.


15. Interview Questions

Q: Why might a purely reactive agent loop (deciding only one step at a time) be less efficient than a planning approach for a complex task?

Ans: A purely reactive loop discovers a task’s structure and dependencies one step at a time, potentially taking actions before recognizing real dependencies between subtasks, which can lead to inefficient ordering or even actions that later need to be undone. Planning has the agent reason about the whole task’s structure — identifying subtasks and their dependencies — up front, allowing it to execute in a more coherent, efficient order from the start.

Q: Explain the difference between sequential and parallel dependencies using the trip-planning example.

Ans: A sequential dependency means one subtask cannot begin until another finishes — building an itinerary can’t happen before both flight and hotel search results are available. A parallel dependency means two subtasks don’t depend on each other and could, in principle, proceed independently — searching for flights and searching for hotels don’t need to wait on each other, since neither’s outcome is required as input for the other.

Q: When would a static plan need to become dynamic, and why can’t planning and the reactive loop be treated as competing approaches?

Ans: A static plan needs to become dynamic when execution reveals information that invalidates an assumption the original plan was built on — for example, discovering that no direct flights exist changes how many days should actually be planned at the destination. Planning and the reactive loop aren’t competitors because a well-designed agent uses planning to establish real structure up front, while still looping reactively within and across that structure to handle exactly this kind of mid-execution discovery and revision.

Q: Design a plan-and-execute system for a task in your own domain, and identify one point where a real mid-execution discovery might require revising the remaining plan.

Ans: For a research assistant summarizing a topic, an initial plan might decompose into: identify key subtopics, search for sources on each, synthesize findings, and present a summary. If the source-search step for one subtopic reveals that it’s actually two distinct, unrelated concepts being conflated, the remaining plan would need revision — splitting that subtopic into two separate research subtasks before proceeding to synthesis, rather than rigidly continuing with the original, now-inaccurate decomposition.


16. What You Should Remember

  • Task decomposition breaks a complex goal into subtasks with explicit dependencies — verified directly through a working readiness-check function correctly sequencing the Japan trip example.
  • Static planning decides the full plan up front; dynamic planning revises the plan as new, plan-changing information is discovered — verified directly through a scenario where a flight search discovery correctly triggers revision of a later subtask.
  • Plan-and-execute combines both — establishing real structure while still looping reactively to handle discoveries — verified directly through a complete, working implementation.

17. Quick Practice

Decompose a complex goal from your own life or work into subtasks with explicit dependencies, following Section 3’s format. Identify which subtasks are sequential versus parallel, and describe one plausible discovery that might require revising your plan mid-execution.

18. Next Step

Next: Module 9 — ReAct — a specific, influential reasoning pattern that interleaves thought, action, and observation at every single step, directly building on this module’s planning foundation.

Author
TechByteByByte Editorial Team
Reviewed by
TechByteByByte Admin
Published
Last reviewed