Part of AI Agent Handbook
See how AI agents use planning to handle complex, multi-step tasks. Topics include task decomposition, sequential execution.
Example: Planning with Our Assistant
You've learned how to break down tasks and implement plan-and-execute patterns. Now let's see these concepts in action with a realistic scenario that brings together everything our assistant has learned so far.
A Complex Request
Imagine you send this message to your personal assistant:
Book a meeting next week with Alice to discuss the Q4 marketing campaign.
Send her a summary of what we accomplished in Q3 before the meeting.This isn't a simple, single-step task. It requires coordination across multiple systems, retrieving historical information, and executing several actions in the right order. Let's watch our assistant plan and execute this request step by step.
The Planning Phase
When our assistant receives this request, it doesn't immediately start booking meetings or sending emails. Instead, it enters a planning phase where it breaks down the goal into manageable subtasks.
Here's how the assistant might decompose this request:
## Using Claude Sonnet 4.5 for its superior planning capabilities
import os
from anthropic import Anthropic
client = Anthropic(api_key=os.getenv("ANTHROPIC_API_KEY"))
user_request = """
Book a meeting next week with Alice to discuss the Q4 marketing campaign.
Send her a summary of what we accomplished in Q3 before the meeting.
"""
planning_prompt = f"""You are a personal assistant. Break down this request into a numbered list of specific steps:
Request: {user_request}
List each step as a clear action. Consider what information you need and what order makes sense."""
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=1024,
messages=[{"role": "user", "content": planning_prompt}],
)
print(response.content[0].text)[31m---------------------------------------------------------------------------[39m
[31mTypeError[39m Traceback (most recent call last)
[36mCell[39m[36m [39m[32mIn[3][39m[32m, line 18[39m
[32m 7[39m user_request = [33m"""[39m
[32m 8[39m [33mBook a meeting next week with Alice to discuss the Q4 marketing campaign.[39m
[32m 9[39m [33mSend her a summary of what we accomplished in Q3 before the meeting.[39m
[32m 10[39m [33m"""[39m
[32m 12[39m planning_prompt = [33mf[39m[33m"""[39m[33mYou are a personal assistant. Break down this request into a numbered list of specific steps:[39m
[32m 13[39m
[32m 14[39m [33mRequest: [39m[38;5;132;01m{[39;00muser_request[38;5;132;01m}[39;00m
[32m 15[39m
[32m 16[39m [33mList each step as a clear action. Consider what information you need and what order makes sense.[39m[33m"""[39m
[32m---> [39m[32m18[39m response = [43mclient[49m[43m.[49m[43mmessages[49m[43m.[49m[43mcreate[49m[43m([49m
[32m 19[39m [43m [49m[43mmodel[49m[43m=[49m[33;43m"[39;49m[33;43mclaude-sonnet-4-5[39;49m[33;43m"[39;49m[43m,[49m
[32m 20[39m [43m [49m[43mmax_tokens[49m[43m=[49m[32;43m1024[39;49m[43m,[49m
[32m 21[39m [43m [49m[43mmessages[49m[43m=[49m[43m[[49m[43m{[49m[33;43m"[39;49m[33;43mrole[39;49m[33;43m"[39;49m[43m:[49m[43m [49m[33;43m"[39;49m[33;43muser[39;49m[33;43m"[39;49m[43m,[49m[43m [49m[33;43m"[39;49m[33;43mcontent[39;49m[33;43m"[39;49m[43m:[49m[43m [49m[43mplanning_prompt[49m[43m}[49m[43m][49m[43m,[49m
[32m 22[39m [43m)[49m
[32m 24[39m [38;5;28mprint[39m(response.content[[32m0[39m].text)
[36mFile [39m[32m/private/tmp/mb-ai-style-scanner/.venv/lib/python3.11/site-packages/anthropic/_utils/_utils.py:282[39m, in [36mrequired_args.<locals>.inner.<locals>.wrapper[39m[34m(*args, **kwargs)[39m
[32m 280[39m msg = [33mf[39m[33m"[39m[33mMissing required argument: [39m[38;5;132;01m{[39;00mquote(missing[[32m0[39m])[38;5;132;01m}[39;00m[33m"[39m
[32m 281[39m [38;5;28;01mraise[39;00m [38;5;167;01mTypeError[39;00m(msg)
[32m--> [39m[32m282[39m [38;5;28;01mreturn[39;00m [43mfunc[49m[43m([49m[43m*[49m[43margs[49m[43m,[49m[43m [49m[43m*[49m[43m*[49m[43mkwargs[49m[43m)[49m
[36mFile [39m[32m/private/tmp/mb-ai-style-scanner/.venv/lib/python3.11/site-packages/anthropic/resources/messages/messages.py:996[39m, in [36mMessages.create[39m[34m(self, max_tokens, messages, model, cache_control, container, inference_geo, metadata, output_config, service_tier, stop_sequences, stream, system, temperature, thinking, tool_choice, tools, top_k, top_p, extra_headers, extra_query, extra_body, timeout)[39m
[32m 989[39m [38;5;28;01mif[39;00m model [38;5;129;01min[39;00m MODELS_TO_WARN_WITH_THINKING_ENABLED [38;5;129;01mand[39;00m thinking [38;5;129;01mand[39;00m thinking[[33m"[39m[33mtype[39m[33m"[39m] == [33m"[39m[33menabled[39m[33m"[39m:
[32m 990[39m warnings.warn(
[32m 991[39m [33mf[39m[33m"[39m[33mUsing Claude with [39m[38;5;132;01m{[39;00mmodel[38;5;132;01m}[39;00m[33m and [39m[33m'[39m[33mthinking.type=enabled[39m[33m'[39m[33m is deprecated. Use [39m[33m'[39m[33mthinking.type=adaptive[39m[33m'[39m[33m instead which results in better model performance in our testing: https://platform.claude.com/docs/en/build-with-claude/adaptive-thinking[39m[33m"[39m,
[32m 992[39m [38;5;167;01mUserWarning[39;00m,
[32m 993[39m stacklevel=[32m3[39m,
[32m 994[39m )
[32m--> [39m[32m996[39m [38;5;28;01mreturn[39;00m [38;5;28;43mself[39;49m[43m.[49m[43m_post[49m[43m([49m
[32m 997[39m [43m [49m[33;43m"[39;49m[33;43m/v1/messages[39;49m[33;43m"[39;49m[43m,[49m
[32m 998[39m [43m [49m[43mbody[49m[43m=[49m[43mmaybe_transform[49m[43m([49m
[32m 999[39m [43m [49m[43m{[49m
[32m 1000[39m [43m [49m[33;43m"[39;49m[33;43mmax_tokens[39;49m[33;43m"[39;49m[43m:[49m[43m [49m[43mmax_tokens[49m[43m,[49m
[32m 1001[39m [43m [49m[33;43m"[39;49m[33;43mmessages[39;49m[33;43m"[39;49m[43m:[49m[43m [49m[43mmessages[49m[43m,[49m
[32m 1002[39m [43m [49m[33;43m"[39;49m[33;43mmodel[39;49m[33;43m"[39;49m[43m:[49m[43m [49m[43mmodel[49m[43m,[49m
[32m 1003[39m [43m [49m[33;43m"[39;49m[33;43mcache_control[39;49m[33;43m"[39;49m[43m:[49m[43m [49m[43mcache_control[49m[43m,[49m
[32m 1004[39m [43m [49m[33;43m"[39;49m[33;43mcontainer[39;49m[33;43m"[39;49m[43m:[49m[43m [49m[43mcontainer[49m[43m,[49m
[32m 1005[39m [43m [49m[33;43m"[39;49m[33;43minference_geo[39;49m[33;43m"[39;49m[43m:[49m[43m [49m[43minference_geo[49m[43m,[49m
[32m 1006[39m [43m [49m[33;43m"[39;49m[33;43mmetadata[39;49m[33;43m"[39;49m[43m:[49m[43m [49m[43mmetadata[49m[43m,[49m
[32m 1007[39m [43m [49m[33;43m"[39;49m[33;43moutput_config[39;49m[33;43m"[39;49m[43m:[49m[43m [49m[43moutput_config[49m[43m,[49m
[32m 1008[39m [43m [49m[33;43m"[39;49m[33;43mservice_tier[39;49m[33;43m"[39;49m[43m:[49m[43m [49m[43mservice_tier[49m[43m,[49m
[32m 1009[39m [43m [49m[33;43m"[39;49m[33;43mstop_sequences[39;49m[33;43m"[39;49m[43m:[49m[43m [49m[43mstop_sequences[49m[43m,[49m
[32m 1010[39m [43m [49m[33;43m"[39;49m[33;43mstream[39;49m[33;43m"[39;49m[43m:[49m[43m [49m[43mstream[49m[43m,[49m
[32m 1011[39m [43m [49m[33;43m"[39;49m[33;43msystem[39;49m[33;43m"[39;49m[43m:[49m[43m [49m[43msystem[49m[43m,[49m
[32m 1012[39m [43m [49m[33;43m"[39;49m[33;43mtemperature[39;49m[33;43m"[39;49m[43m:[49m[43m [49m[43mtemperature[49m[43m,[49m
[32m 1013[39m [43m [49m[33;43m"[39;49m[33;43mthinking[39;49m[33;43m"[39;49m[43m:[49m[43m [49m[43mthinking[49m[43m,[49m
[32m 1014[39m [43m [49m[33;43m"[39;49m[33;43mtool_choice[39;49m[33;43m"[39;49m[43m:[49m[43m [49m[43mtool_choice[49m[43m,[49m
[32m 1015[39m [43m [49m[33;43m"[39;49m[33;43mtools[39;49m[33;43m"[39;49m[43m:[49m[43m [49m[43mtools[49m[43m,[49m
[32m 1016[39m [43m [49m[33;43m"[39;49m[33;43mtop_k[39;49m[33;43m"[39;49m[43m:[49m[43m [49m[43mtop_k[49m[43m,[49m
[32m 1017[39m [43m [49m[33;43m"[39;49m[33;43mtop_p[39;49m[33;43m"[39;49m[43m:[49m[43m [49m[43mtop_p[49m[43m,[49m
[32m 1018[39m [43m [49m[43m}[49m[43m,[49m
[32m 1019[39m [43m [49m[43mmessage_create_params[49m[43m.[49m[43mMessageCreateParamsStreaming[49m
[32m 1020[39m [43m [49m[38;5;28;43;01mif[39;49;00m[43m [49m[43mstream[49m
[32m 1021[39m [43m [49m[38;5;28;43;01melse[39;49;00m[43m [49m[43mmessage_create_params[49m[43m.[49m[43mMessageCreateParamsNonStreaming[49m[43m,[49m
[32m 1022[39m [43m [49m[43m)[49m[43m,[49m
[32m 1023[39m [43m [49m[43moptions[49m[43m=[49m[43mmake_request_options[49m[43m([49m
[32m 1024[39m [43m [49m[43mextra_headers[49m[43m=[49m[43mextra_headers[49m[43m,[49m[43m [49m[43mextra_query[49m[43m=[49m[43mextra_query[49m[43m,[49m[43m [49m[43mextra_body[49m[43m=[49m[43mextra_body[49m[43m,[49m[43m [49m[43mtimeout[49m[43m=[49m[43mtimeout[49m
[32m 1025[39m [43m [49m[43m)[49m[43m,[49m
[32m 1026[39m [43m [49m[43mcast_to[49m[43m=[49m[43mMessage[49m[43m,[49m
[32m 1027[39m [43m [49m[43mstream[49m[43m=[49m[43mstream[49m[43m [49m[38;5;129;43;01mor[39;49;00m[43m [49m[38;5;28;43;01mFalse[39;49;00m[43m,[49m
[32m 1028[39m [43m [49m[43mstream_cls[49m[43m=[49m[43mStream[49m[43m[[49m[43mRawMessageStreamEvent[49m[43m][49m[43m,[49m
[32m 1029[39m [43m[49m[43m)[49m
[36mFile [39m[32m/private/tmp/mb-ai-style-scanner/.venv/lib/python3.11/site-packages/anthropic/_base_client.py:1364[39m, in [36mSyncAPIClient.post[39m[34m(self, path, cast_to, body, content, options, files, stream, stream_cls)[39m
[32m 1355[39m warnings.warn(
[32m 1356[39m [33m"[39m[33mPassing raw bytes as `body` is deprecated and will be removed in a future version. [39m[33m"[39m
[32m 1357[39m [33m"[39m[33mPlease pass raw bytes via the `content` parameter instead.[39m[33m"[39m,
[32m 1358[39m [38;5;167;01mDeprecationWarning[39;00m,
[32m 1359[39m stacklevel=[32m2[39m,
[32m 1360[39m )
[32m 1361[39m opts = FinalRequestOptions.construct(
[32m 1362[39m method=[33m"[39m[33mpost[39m[33m"[39m, url=path, json_data=body, content=content, files=to_httpx_files(files), **options
[32m 1363[39m )
[32m-> [39m[32m1364[39m [38;5;28;01mreturn[39;00m cast(ResponseT, [38;5;28;43mself[39;49m[43m.[49m[43mrequest[49m[43m([49m[43mcast_to[49m[43m,[49m[43m [49m[43mopts[49m[43m,[49m[43m [49m[43mstream[49m[43m=[49m[43mstream[49m[43m,[49m[43m [49m[43mstream_cls[49m[43m=[49m[43mstream_cls[49m[43m)[49m)
[36mFile [39m[32m/private/tmp/mb-ai-style-scanner/.venv/lib/python3.11/site-packages/anthropic/_base_client.py:1058[39m, in [36mSyncAPIClient.request[39m[34m(self, cast_to, options, stream, stream_cls)[39m
[32m 1055[39m options = [38;5;28mself[39m._prepare_options(options)
[32m 1057[39m remaining_retries = max_retries - retries_taken
[32m-> [39m[32m1058[39m request = [38;5;28;43mself[39;49m[43m.[49m[43m_build_request[49m[43m([49m[43moptions[49m[43m,[49m[43m [49m[43mretries_taken[49m[43m=[49m[43mretries_taken[49m[43m)[49m
[32m 1059[39m [38;5;28mself[39m._prepare_request(request)
[32m 1061[39m kwargs: HttpxSendArgs = {}
[36mFile [39m[32m/private/tmp/mb-ai-style-scanner/.venv/lib/python3.11/site-packages/anthropic/_base_client.py:521[39m, in [36mBaseClient._build_request[39m[34m(self, options, retries_taken)[39m
[32m 518[39m [38;5;28;01melse[39;00m:
[32m 519[39m [38;5;28;01mraise[39;00m [38;5;167;01mRuntimeError[39;00m([33mf[39m[33m"[39m[33mUnexpected JSON data type, [39m[38;5;132;01m{[39;00m[38;5;28mtype[39m(json_data)[38;5;132;01m}[39;00m[33m, cannot merge with `extra_body`[39m[33m"[39m)
[32m--> [39m[32m521[39m headers = [38;5;28;43mself[39;49m[43m.[49m[43m_build_headers[49m[43m([49m[43moptions[49m[43m,[49m[43m [49m[43mretries_taken[49m[43m=[49m[43mretries_taken[49m[43m)[49m
[32m 522[39m params = _merge_mappings([38;5;28mself[39m.default_query, options.params)
[32m 523[39m content_type = headers.get([33m"[39m[33mContent-Type[39m[33m"[39m)
[36mFile [39m[32m/private/tmp/mb-ai-style-scanner/.venv/lib/python3.11/site-packages/anthropic/_base_client.py:451[39m, in [36mBaseClient._build_headers[39m[34m(self, options, retries_taken)[39m
[32m 441[39m custom_headers = options.headers [38;5;129;01mor[39;00m {}
[32m 442[39m headers_dict = _merge_mappings(
[32m 443[39m {
[32m 444[39m [33m"[39m[33mx-stainless-timeout[39m[33m"[39m: [38;5;28mstr[39m(options.timeout.read)
[32m (...)[39m[32m 449[39m custom_headers,
[32m 450[39m )
[32m--> [39m[32m451[39m [38;5;28;43mself[39;49m[43m.[49m[43m_validate_headers[49m[43m([49m[43mheaders_dict[49m[43m,[49m[43m [49m[43mcustom_headers[49m[43m)[49m
[32m 453[39m [38;5;66;03m# headers are case-insensitive while dictionaries are not.[39;00m
[32m 454[39m headers = httpx.Headers(headers_dict)
[36mFile [39m[32m/private/tmp/mb-ai-style-scanner/.venv/lib/python3.11/site-packages/anthropic/_client.py:196[39m, in [36mAnthropic._validate_headers[39m[34m(self, headers, custom_headers)[39m
[32m 193[39m [38;5;28;01mif[39;00m headers.get([33m"[39m[33mAuthorization[39m[33m"[39m) [38;5;129;01mor[39;00m [38;5;28misinstance[39m(custom_headers.get([33m"[39m[33mAuthorization[39m[33m"[39m), Omit):
[32m 194[39m [38;5;28;01mreturn[39;00m
[32m--> [39m[32m196[39m [38;5;28;01mraise[39;00m [38;5;167;01mTypeError[39;00m(
[32m 197[39m [33m'[39m[33m"[39m[33mCould not resolve authentication method. Expected either api_key or auth_token to be set. Or for one of the `X-Api-Key` or `Authorization` headers to be explicitly omitted[39m[33m"[39m[33m'[39m
[32m 198[39m )
[31mTypeError[39m: "Could not resolve authentication method. Expected either api_key or auth_token to be set. Or for one of the `X-Api-Key` or `Authorization` headers to be explicitly omitted"The assistant generates this plan:
To complete this request, I'll need to:
1. Check Alice's calendar availability for next week
2. Check your calendar for the same period
3. Identify overlapping time slots
4. Retrieve your Q3 marketing notes and accomplishments
5. Create a concise summary of Q3 work
6. Send the summary email to Alice
7. Schedule the meeting once a time is confirmed
8. Provide you with confirmation of both actions
I notice there are dependencies here. I should send the summary before
scheduling the meeting so Alice has context when she receives the invite.
I also need to find availability before I can schedule anything.Notice how the assistant has identified dependencies on its own. You can't schedule a meeting before checking availability, and you should send the summary before the meeting gets scheduled so Alice has context when she accepts. This kind of dependency awareness is what makes planning valuable.
Executing the Plan
Now our assistant moves through each step, using the tools and capabilities we've built in previous chapters.
Step 1-3: Finding a Meeting Time
## Example (Claude Sonnet 4.5)
## Simulating calendar tool calls
def check_calendar(person, start_date, end_date):
"""Tool to check calendar availability"""
# In reality, this would call Google Calendar API or similar
if person == "Alice":
return {
"available_slots": [
"2025-11-17 14:00-15:00",
"2025-11-18 10:00-11:00",
"2025-11-19 15:00-16:00",
]
}
else: # User's calendar
return {
"available_slots": [
"2025-11-17 14:00-15:00",
"2025-11-18 10:00-11:00",
"2025-11-20 09:00-10:00",
]
}
## Agent executes steps 1-3
alice_availability = check_calendar("Alice", "2025-11-17", "2025-11-23")
user_availability = check_calendar("User", "2025-11-17", "2025-11-23")
## Find overlapping slots
alice_slots = set(alice_availability["available_slots"])
user_slots = set(user_availability["available_slots"])
common_slots = alice_slots.intersection(user_slots)
print(f"Available times for both: {list(common_slots)}")
## Output: ['2025-11-17 14:00-15:00', '2025-11-18 10:00-11:00']The assistant has now completed the first three steps of its plan. It found two possible meeting times.
Step 4-5: Retrieving and Summarizing Q3 Work
Next, the assistant needs to gather information about Q3 marketing activities. This is where memory and retrieval come in.
## Example (Claude Sonnet 4.5)
## Using the memory system from Chapter 6
def retrieve_project_notes(project_name, time_period):
"""Retrieve notes from long-term memory"""
# This would query a vector database or document store
return {
"notes": [
"Launched new social media campaign in July - 50K impressions",
"Redesigned email templates - open rate increased 15%",
"Partnered with 3 influencers for product reviews",
"A/B tested landing pages - version B performed 22% better",
"Ran webinar series - 500 attendees total",
],
"documents": ["Q3_campaign_metrics.pdf", "social_media_report.xlsx"],
}
## Agent executes step 4
q3_data = retrieve_project_notes("marketing", "Q3 2025")
## Agent executes step 5: summarize the findings
summary_prompt = f"""Summarize these Q3 marketing accomplishments in a brief, professional email format:
{q3_data["notes"]}
Keep it concise but highlight key metrics."""
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=512,
messages=[{"role": "user", "content": summary_prompt}],
)
summary = response.content[0].text
print(summary)The assistant generates a summary:
Hi Alice,
Here's a quick overview of our Q3 marketing accomplishments:
- Social Media: Launched new campaign reaching 50K impressions
- Email Marketing: Redesigned templates with 15% improvement in open rates
- Influencer Partnerships: Collaborated with 3 influencers for product reviews
- Website Optimization: A/B testing showed 22% better performance on new landing page
- Webinar Series: Hosted events with 500 total attendees
Looking forward to discussing Q4 plans!Step 6: Sending the Summary
## Example (Claude Sonnet 4.5)
def send_email(to, subject, body):
"""Tool to send email"""
# Would use SendGrid, Gmail API, or similar
print(f"Sending email to {to}")
print(f"Subject: {subject}")
print(f"Body: {body}")
return {"status": "sent", "message_id": "msg_12345"}
## Agent executes step 6
email_result = send_email(
to="alice@company.com",
subject="Q3 Marketing Summary - Ahead of Our Q4 Planning Meeting",
body=summary,
)
print(f"Email status: {email_result['status']}")Step 7-8: Scheduling and Confirming
## Example (Claude Sonnet 4.5)
def schedule_meeting(attendees, time_slot, subject, description):
"""Tool to create calendar event"""
# Would use Google Calendar API or similar
return {
"event_id": "evt_67890",
"status": "scheduled",
"time": time_slot,
"attendees": attendees,
}
## Agent executes step 7
## Choose the first available slot
chosen_time = list(common_slots)[0]
meeting_result = schedule_meeting(
attendees=["alice@company.com", "user@company.com"],
time_slot=chosen_time,
subject="Q4 Marketing Campaign Planning",
description="Discuss Q4 marketing strategy and campaign plans",
)
## Agent executes step 8: Confirm with user
confirmation_message = f"""
Task completed! Here's what I did:
✓ Found available time: {chosen_time}
✓ Retrieved and summarized Q3 marketing accomplishments
✓ Sent summary to Alice at alice@company.com
✓ Scheduled meeting: "Q4 Marketing Campaign Planning"
The meeting is confirmed for {chosen_time}. Alice has received the Q3 summary in advance.
"""
print(confirmation_message)What Made This Work
Let's unpack what just happened. Our assistant successfully handled a complex, multi-step request by combining several capabilities:
Planning: The assistant didn't just react to the request. It thought ahead, identified dependencies, and created a logical sequence of actions.
Tool Use: Multiple tools came into play - calendar APIs for checking availability, a memory system for retrieving past work, and email services for communication. The assistant knew which tool to use at each step.
Memory and Retrieval: The assistant pulled relevant information from its long-term memory about Q3 marketing activities. Without this capability, it couldn't have generated an accurate summary.
Reasoning: When finding a meeting time, the assistant had to reason about overlapping availability. It understood that both parties need to be free at the same time.
State Management: Throughout execution, the assistant maintained state about what it had accomplished, what remained to be done, and what information it had gathered. This prevented it from repeating steps or losing track of progress.
Handling Complications
Real-world scenarios rarely go perfectly. Let's see how our assistant handles a complication.
Suppose when the assistant tries to retrieve Q3 notes, the memory system returns incomplete information:
## Example (Claude Sonnet 4.5)
## Simulating incomplete data retrieval
q3_data_incomplete = retrieve_project_notes("marketing", "Q3 2025")
if len(q3_data_incomplete["notes"]) < 3:
# Assistant recognizes insufficient information
replanning_prompt = """
I was asked to summarize Q3 marketing work, but I only found limited notes.
What should I do?
Options:
A) Send what I have
B) Ask the user for more information
C) Search additional sources
D) Indicate in the summary that information is incomplete
Consider: The summary will be sent to Alice before a meeting.
"""
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=256,
messages=[{"role": "user", "content": replanning_prompt}],
)
print(response.content[0].text)A well-designed assistant might respond:
I should ask the user for more information (Option B). Sending incomplete
information to Alice could make us look unprepared for the meeting. I'll
pause execution and request additional details from the user.This demonstrates adaptive planning. When the original plan hits an obstacle, the assistant can recognize the problem and adjust its approach.
Bringing It All Together
This example shows how planning lets our assistant handle real work instead of only answering questions. The key elements that made this possible:
Task Decomposition: Breaking "book a meeting and send a summary" into eight concrete steps made the complex request manageable. Without decomposition, the assistant might have tried to do everything at once or missed important steps.
Sequential Execution: Following the plan in order, with each step building on the previous ones, ensured nothing was forgotten or done out of sequence. The assistant knew to check calendars before scheduling and to send the summary before creating the meeting invite.
Tool Orchestration: The assistant coordinated multiple tools (calendar, memory, email) to accomplish different parts of the task. Each tool served a specific purpose, and the assistant knew when to invoke each one.
Contextual Awareness: Throughout execution, the assistant maintained awareness of the overall goal and how each step contributed to it. This prevented it from getting lost in the details or forgetting why it was performing certain actions.
Graceful Handling: When problems arise, the assistant can recognize them and adapt rather than blindly continuing. This makes the difference between a brittle system that breaks easily and a robust one that handles real-world messiness.
Design Considerations
As you build planning capabilities into your own agents, consider these trade-offs:
Plan Detail vs. Flexibility: Highly detailed plans are easier to execute but harder to adapt when things change. More abstract plans are flexible but require more reasoning at each step. For our assistant, we chose medium-detail plans that specify what to do but allow some flexibility in how.
Upfront Planning vs. Incremental Planning: Should the agent plan everything before starting, or plan a few steps and then replan? Upfront planning works well for predictable tasks. Incremental planning handles uncertainty better but adds overhead. Our example used upfront planning because meeting scheduling is fairly predictable.
Error Recovery Strategies: When a step fails, should the agent retry, skip it, ask for help, or abort entirely? The right choice depends on the consequences of failure. For our assistant, we chose to ask for help when critical information is missing, since sending incomplete information could damage professional relationships.
You now have an assistant that doesn't just respond to requests but actively works to accomplish goals. In the next chapter, we'll explore how multiple agents can work together, enabling even more sophisticated capabilities through collaboration and specialization.
Glossary
Task Decomposition: The process of breaking down a complex goal into smaller, manageable subtasks that can be executed sequentially or in parallel.
Tool Orchestration: Coordinating multiple tools or APIs so each one handles the right part of a larger task.
Sequential Execution: Performing planned steps in a specific order, where each step may depend on the results of previous steps.
Adaptive Planning: The ability to recognize when a plan isn't working and adjust the approach, either by replanning or by asking for additional information.
State Management: Maintaining awareness of what has been accomplished, what remains to be done, and what information has been gathered during task execution.
Quiz
Ready to test your understanding? Take this quick quiz to reinforce what you've learned about planning in AI agents.
Planning with AI Agents
Reference
Citation details
Cite or share this article.
Continue with the full handbook
This chapter is part of AI Agent Handbook. Use the handbook page to browse the complete table of contents and continue reading in sequence.
Explore AI Agent HandbookStay up to date
Get articles, book updates, and news delivered to your inbox.
No spam, unsubscribe anytime.
Join the community
Sign in to remove popups, track your reading progress, and join the discussion.

Comments
No comments yet. Be the first to share your thoughts!