What a 500-script migration taught me about when agent parallelism actually makes sense
I recently started working on a migration involving roughly 500 scripts.
The goal was to migrate legacy logging calls to a newly implemented structured logging engine, with unique logging channels for tracing and observability through Grafana, Loki, Tempo, and Alloy. The new logging engine was already implemented and available through a common include path.
What remained was the tedious part: updating hundreds of existing scripts.
My first thought was simple:
"There are 500 files. Why not use 10 sub-agents and finish this faster?"
It sounded like a perfect use case for agentic coding.
It wasn't.
The problem wasn't the number of files. It was what I was asking the agents to do.
1. The Initial Approach: More Agents = More Speed?
The idea was to divide the files into batches and give each batch to a mini-model.
Main Agent
│
┌─────────────┼─────────────┐
▼ ▼ ▼
Agent 1 Agent 2 Agent 3
50 files 50 files 50 files
│ │ │
└─────────────┼─────────────┘
▼
Migration
Each agent received essentially the same instructions:
- find legacy logging
- replace it with the new structured logger
- use the correct channel
- preserve business logic
- complete its assigned files
The files were independent, so the approach looked reasonable.
But each agent was doing much more than the actual migration.
It was also rediscovering the repository, figuring out what needed changing, deciding channel names, and keeping track of its own progress.
That repeated work became the real cost.
2. What Actually Happened
The problems were not primarily with the code changes. They were with the work surrounding them.
Problem 1: Tracking completed work
With multiple agents, someone needs to know:
- which files are pending
- which are being processed
- which are completed
- which failed
- which should be skipped
That is workflow state.
A JSON file, database, or task queue is designed for this. An LLM context isn't.
Problem 2: Finding what actually needs to change
Each agent had to discover the logging statements itself.
The process looked something like:
Open file
↓
Understand file
↓
Search for logging
↓
Find legacy calls
↓
Inspect context
↓
Decide what to change
↓
Perform migration
But if a simple command can already tell us:
ExampleScript.py:42
ExampleScript.py:87
ExampleScript.py:131
ExampleScript.py:164
there is little value in asking ten different model contexts to discover those same locations.
That is a job for tooling.
Problem 3: Channel generation consumed reasoning
Each script needed a unique channel.
For example:
ExampleScript.py
↓
example_channel
This was another decision I had unnecessarily delegated to the model.
Channel generation can be deterministic:
file path
↓
channel generator
↓
validated channel map
Once generated, the channel can simply be given to the model.
No need to make the model reconsider naming conventions and uniqueness for every file.
3. The Important Distinction: Work vs. Reasoning
This was the point where my thinking changed.
I initially saw:
500 independent files
But that doesn't necessarily mean:
500 independent reasoning problems.
Those are different things.
The migration itself was mostly:
Find legacy logging
↓
Replace with known API
↓
Use known channel
↓
Preserve everything else
The work was repeated hundreds of times, but the reasoning was mostly the same.
Now compare that with a debugging problem:
Why did API latency increase?
├── Database?
├── Cache?
├── Network?
├── AWS infrastructure?
└── Application code?
Those are genuinely different reasoning paths.
That is where sub-agents become useful:
Problem
│
┌──────────┼──────────┐
▼ ▼ ▼
Agent A Agent B Agent C
Database Network Application
│ │ │
└──────────┼──────────┘
▼
Synthesis
Each agent can investigate a substantial hypothesis independently.
That is meaningful parallelism.
In my migration, I was parallelizing work, not reasoning.
4. The Better Approach: Remove Decisions Before Calling the Model
The obvious improvement was to move deterministic work outside the model.
Instead of asking agents to discover everything, preprocess the repository first.
Repository
│
▼
Preprocessing
│
┌──────────────┼──────────────┐
▼ ▼ ▼
Inventory Log locations Channel map
│ │ │
└──────────────┼──────────────┘
▼
index.json
│
▼
Single Claude Code
│
▼
Migration
│
▼
Verification
The preprocessing stage determines:
- which files contain legacy logging
- which files contain no logging
- where the legacy calls are
- what channel belongs to each file
- what has already been completed
For example:
{
"file": "ExampleScript.py",
"status": "pending",
"legacy_log_count": 4,
"channel": "example_channel"
}
Now the model doesn't need to discover the problem.
It receives the problem.
5. The Model's Task Becomes Much Smaller
Instead of:
Explore the repository and migrate this file.
the model can receive:
File:
ExampleScript.py
Channel:
example_channel
Legacy logging locations:
47
82
119
153
Expected migrations:
4
Task:
Replace the identified legacy logging statements
with the structured logging implementation.
Do not modify business logic.
Do not change the assigned channel.
Do not modify unrelated code.
The model now focuses on the part that actually benefits from reasoning:
How should this existing logging statement be expressed using the new API while preserving its meaning?
Everything else has already been established.
This is a much better task boundary.
6. Why the Single Session Is Better Here
For this migration, a single Claude Code session has some simple advantages:
- one shared migration context
- no duplicated instructions
- no inter-agent coordination
- external state for progress
- deterministic tools for deterministic work
The workflow becomes:
index.json
│
▼
Next pending file
│
▼
Prepare file context
│
├── locations
├── channel
└── rules
│
▼
Single agent
│
▼
Verification
│
┌─────┴─────┐
▼ ▼
PASS FAIL
│ │
▼ ▼
completed review
This isn't less sophisticated than a multi-agent system.
It's simply better matched to the problem.
7. So When Are Sub-Agents Actually Useful?
The experience led to a better question.
Instead of asking:
"How many tasks do I have?"
ask:
"How many independent reasoning problems do I have?"
That distinction makes the useful cases much clearer.
Independent research
For an architectural investigation, different agents can explore different areas:
Agent A → AWS architecture
Agent B → database architecture
Agent C → observability
Agent D → security
The main agent can then combine the findings.
Competing debugging hypotheses
For a performance problem:
Performance issue
│
┌────────────┼────────────┐
▼ ▼ ▼
Database Network Application
hypothesis hypothesis hypothesis
│ │ │
└────────────┼────────────┘
▼
Synthesis
Each agent investigates a different explanation.
Independent code reviews
A change can be reviewed from different perspectives:
Agent A → correctness
Agent B → security
Agent C → performance
Agent D → maintainability
These agents aren't simply doing the same job. Each has a different analytical objective.
Independent feature implementation
If a system has clearly separated components:
API
Worker
Infrastructure
Observability
and their interfaces are already defined, separate agents can work independently.
The important condition is low coupling.
8. The Cost of Sub-Agents
Sub-agents aren't free parallel threads.
Every additional agent brings its own:
- context
- instructions
- tool calls
- reasoning
- coordination
- result synthesis
- failure handling
Conceptually:
Single agent:
shared context
+
sequential reasoning
N agents:
N × context
+
N × reasoning
+
coordination
+
synthesis
So parallelism only helps when the reasoning saved is greater than the overhead introduced.
A useful way to think about it is:
Sub-agent benefit = parallel reasoning saved - duplicated context
- coordination cost - synthesis cost
If the difference is small, adding agents is mostly adding machinery.
9. A Practical Decision Framework
Before creating a sub-agent, ask:
Is the task deterministic?
If yes:
Use a script or tool.
Does it require substantial reasoning?
If no:
A single agent is usually enough.
Is the reasoning independent?
If no:
Keep it in one session.
Would a fresh context provide meaningful value?
If yes:
A sub-agent may be worthwhile.
A simple decision tree:
Is it deterministic?
│
┌─────┴─────┐
YES NO
│ │
TOOL Substantial
reasoning?
│
┌────┴────┐
NO YES
│ │
SINGLE AGENT Independent?
│
┌─────┴─────┐
NO YES
│ │
SINGLE SUB-AGENTS
SESSION
10. What I Learned From the 500-File Migration
The migration changed how I think about agent orchestration.
1. The number of files doesn't determine the number of agents.
Five hundred files can still represent one reasoning pattern.
2. Context is a resource.
Duplicating the same context across ten agents has a cost.
3. Preprocessing can be more valuable than parallelism.
Finding the relevant information before invoking the model can save more effort than adding more workers.
4. External state beats model memory.
A model shouldn't have to remember the status of hundreds of files.
5. Deterministic decisions belong outside the LLM.
Channel generation, classification, searches, counts, and validation can be automated.
6. Agent orchestration has a cost.
More agents do not automatically mean faster or cheaper execution.
7. Agents are best used for reasoning.
Not bookkeeping.
Not counting.
Not maintaining workflow state.
Not performing searches that a deterministic tool can perform more reliably.
11. The General Principle
The biggest lesson wasn't:
"Don't use sub-agents."
It was:
Don't parallelize merely because work can be divided. Parallelize when reasoning can be divided.
A hundred deterministic transformations may be better handled by a script.
A hundred small reasoning tasks may still be better handled by one well-contextualized agent.
Five difficult, independent investigations may be perfect for five sub-agents.
The number of tasks is only one part of the problem.
The structure of the reasoning should determine the architecture.
Conclusion
My initial assumption was:
500 files
↓
many agents
↓
faster migration
The better approach turned out to be:
500 files
↓
deterministic preprocessing
↓
external state
↓
bounded context
↓
single agent
↓
deterministic verification
The lesson is not that sub-agents are bad. It's that parallelism should have a reason.
Before spawning another agent, ask:
Where is the independent reasoning?
If there isn't much, more agents may simply mean more context, more tokens, more coordination, and more things to clean up afterward.
Use sub-agents when they let you explore different substantial reasoning paths at the same time.
Otherwise, fewer agents with better boundaries usually win.
Top comments (2)
The reasoning shape framing is the part I wish more agent tools exposed. For big migrations, I like a boring manifest first. File, exact call sites, assigned channel, expected edit count, verification command. Then the model is patching against a ticket instead of rediscovering the repo 500 times.
Got it. I actually used a similar manifest/state approach, but embedded it into the tooling in my case. I found that relying on the manifest alone could still cause the agent to miss active call sites that needed changes. It even happened during long sessions when the verification output explicitly showed places that still needed attention.
Running a grep before each invocation solved that for me by giving the agent a fresh view of the current call sites rather than relying solely on the accumulated state.
I'll definitely try making the manifest more explicit in the workflow you're suggesting and see how it compares. I'll elaborate on the results.