Why Decompose Tasks
When you give an AI agent a large task ("add user authentication to the app"), two problems emerge:
- Context bloat - the agent reads every file it thinks might be relevant, accumulating 50k+ tokens of context
- Accuracy drop - with a huge context, the model may lose track of constraints stated earlier, produce inconsistent code, or miss edge cases
Decomposing into focused subtasks keeps each call's context minimal and gives the model a clear, single goal.
Monolith vs Unit Tasks
# ONE big task:
"Add a complete user authentication system including:
- Login and registration forms
- Password hashing and verification
- JWT or session tokens
- Remember me functionality
- Password reset via email
- Role-based access control
- Admin panel for user management"
# What happens:
- Agent reads 20+ files to understand the codebase
- Context grows to 80,000+ tokens
- Agent tries to hold all requirements in one pass
- Inconsistencies appear between frontend forms and backend logic
- Token cost: very high. Quality: often poor.
# SEVEN focused tasks:
Task 1: Create users table migration and UserModel with password hashing
Context needed: /config/database.php, existing model template
Tokens: ~3,000
Task 2: Build login form (POST /auth/login) using AuthController
Context needed: Task 1 output (UserModel), routes.php
Tokens: ~2,500
Task 3: Build registration form with validation
Context needed: UserModel, existing form examples
Tokens: ~2,000
Task 4: Implement session-based auth with remember_me cookie
Context needed: AuthController from Task 2, config/app.php
Tokens: ~2,500
Task 5: Password reset flow (request email + reset form)
Context needed: UserModel, Mailer class, email templates
Tokens: ~3,000
Task 6: RBAC - add role column, middleware, @role annotations
Context needed: UserModel, existing middleware pattern
Tokens: ~2,500
Task 7: Admin user management panel
Context needed: UserModel, admin layout template
Tokens: ~3,000
Total tokens: ~18,500 (vs 80,000+ monolith)
Each task is accurate because it has focused, relevant context only
Decomposition Patterns
Three common ways to split coding tasks:
| Pattern | Split by | Best for |
|---|---|---|
| Layer split | DB model, controller, view, tests | CRUD features, form flows |
| File split | One subtask per file or module | Refactors, style fixes |
| Step split | Research, plan, implement, verify | Unknown codebases, complex bugs |
| Concern split | Frontend, backend, API, tests | Full-stack features |
Parallel Subagents
Tasks that do not depend on each other can run in parallel. Claude Code's Agent tool supports multiple concurrent subagents, and the Anthropic API can be called in parallel from your own code.
import anthropic
import concurrent.futures
client = anthropic.Anthropic()
def run_subtask(task: dict) -> dict:
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=2048,
system=task["system"],
messages=[{"role": "user", "content": task["prompt"]}],
)
return {"task": task["name"], "result": response.content[0].text}
# Independent tasks that can run simultaneously
subtasks = [
{
"name": "Write UserModel",
"system": "PHP 8.2 developer. PDO only. No raw SQL.",
"prompt": f"Write UserModel class with findByEmail(), create(), hashPassword(). Schema: {db_schema}",
},
{
"name": "Write UserModelTest",
"system": "PHPUnit developer. Mock the DB layer.",
"prompt": "Write PHPUnit tests for UserModel. Test findByEmail with valid/invalid email.",
},
{
"name": "Write migration",
"system": "MySQL 8 DBA.",
"prompt": f"Write CREATE TABLE migration for users: {user_fields}",
},
]
# Run all three in parallel
with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
results = list(executor.map(run_subtask, subtasks))
for r in results:
print(f"[{r['task']}]\n{r['result']}\n")
Sequential Chaining
When one task's output is required as input for the next, chain them sequentially and pass only the relevant output forward (not the full prior context):
def chain_tasks(steps: list) -> list:
results = []
previous_output = ""
for step in steps:
# Only pass the RELEVANT part of the prior output, not everything
prompt = step["prompt_template"].format(prior=previous_output[:2000])
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
system=step["system"],
messages=[{"role": "user", "content": prompt}],
)
previous_output = response.content[0].text
results.append({"step": step["name"], "output": previous_output})
return results
steps = [
{
"name": "Find bug location",
"system": "PHP developer. Be concise.",
"prompt_template": f"In auth.php lines 45-90, find the root cause of: session_regenerate_id fails. Report: file:line only. Code: {code_snippet}",
},
{
"name": "Write fix",
"system": "PHP developer. Show diff only.",
"prompt_template": "Fix the issue found at {prior}. Show the corrected lines as a diff.",
},
{
"name": "Write test",
"system": "PHPUnit developer.",
"prompt_template": "Write one test to verify the fix: {prior} is correct.",
},
]
When chaining tasks, resist the urge to pass the full output of step N to step N+1. Extract only the essential part: the file:line reference, the new function signature, the error type. Truncating handoffs is where sequential chains save the most tokens.
Practical Examples
| Task | Monolith tokens | Decomposed tokens | Saving |
|---|---|---|---|
| Add CRUD feature | ~45,000 | ~12,000 (4 subtasks) | 73% |
| Refactor large class | ~30,000 | ~8,000 (method by method) | 73% |
| Debug and fix a test suite | ~25,000 | ~7,000 (locate, fix, verify) | 72% |
| Write full test coverage | ~40,000 | ~10,000 (1 class per call) | 75% |