Agentic Development Part 3: Designing Tasks for Agents
Part 2 dissected planner, tool use, memory, and observation as the components that build the agent loop. But no matter how well those four components work, the result is still bounded by the quality of the input given to the planner at the start — the task itself. A good planner’s dynamic plan still goes astray if the task it’s given is ambiguous; careful observation still can’t verify anything if there’s no clear definition of “done” to reference. Part 4 of the Spec-Driven Development series already covered how specs translate into task groups in general — this article takes that discussion one level deeper, into the discipline of designing individual tasks: the right granularity, unambiguous definitions of done, task dependencies, and how to handle work that’s exploratory in nature and can’t be precisely defined from the start.
Why Task Design Isn’t Just Breaking Work into Pieces
There’s a common misconception: that designing tasks for agents is simply a matter of “making chunks of work smaller”. Breaking work into smaller pieces is indeed necessary, but it’s a necessary condition, not a sufficient one. Small but wrongly-cut tasks still produce problems — just on a smaller scale, and sometimes harder to trace because they hide among many other tasks that look fine.
Consider two ways of breaking down the work “add validation to the registration form”:
BAD BREAKDOWN (small but wrongly cut):
Task A: Add email validation
Task B: Add password validation
Task C: Add phone number validation
Task D: Fix error messages so they're consistent
The problem: Task D depends on how Tasks A, B, and C each write
their error messages. If all three are done independently by
different agents without a shared convention, Task D will find
three different error message styles and have to normalize them —
work that shouldn't have been necessary if the scope had been
cut correctly.
GOOD BREAKDOWN (cuts aligned with the real boundaries):
Task A: Define the validation error message convention (format, language,
response structure) as a shared reference
Task B: Implement email, password, and phone number validation
following the convention from Task A
These tasks are fewer in number, but their boundaries align with
the dependencies that actually exist in the work.
The principle behind this difference: good tasks are cut along boundaries that are naturally independent, not merely cut by size. Breaking work apart too early based on “so it looks small” without regard to the actual dependencies just creates unnecessary coordination work.
A more useful question than “how small is this task” is “can this task be completed and verified without waiting on a decision from another unfinished task”. If the answer is no, the task boundaries need redrawing, not just further splitting.
Granularity: Too Big vs Too Small
Once task boundaries are set correctly, the next question is size. Wrong granularity in either direction is problematic, just with different symptoms.
Tasks that are too big produce the problems already touched on in Part 1: hard to verify because too much changes at once, hard to review because the reviewer must understand the whole new context before judging which parts are right and which are wrong, and when an error appears mid-way, the cost of tracing the root cause is much higher because you have to go through the entire large change.
Tasks that are too small have the opposite symptoms but are just as troublesome: the coordination overhead between tasks becomes larger than the value of the work itself. Every task needs context to be understood, a checkpoint to be reviewed, and possibly synchronization with other related tasks. If tasks are split too finely, the time spent coordinating these small tasks can exceed the time saved by splitting them.
flowchart LR
A[Task Too Big] --> B[Hard to Verify]
A --> C[Review Needs Full Context Understanding]
A --> D[High Error-Tracing Cost]
E[Task Too Small] --> F[High Coordination Overhead]
E --> G[Excessive Cross-Task Synchronization]
E --> H[Work Value per Task Doesn't Justify Overhead]
I[Right Granularity] --> J[Independently Verifiable]
I --> K[Review Without Full Context from Scratch]
I --> L[Reasonable Coordination Overhead]A practical heuristic as a starting point: one task should ideally produce one unit of change that can be reviewed and understood in one short session without needing to open other tasks to understand its context, and have one clear definition of done without needing to be split further into many sub-criteria. If explaining “what makes this task done” already requires a long list with many independent conditions, that’s a signal the task needs splitting. If the opposite — the task is so narrow that its context can’t be understood without reading several other tasks first — that’s a signal the boundary is too fine and tasks need merging.
There’s no fixed number like “one task should be X lines of code” or “X minutes of work”. The right size depends on domain complexity, not the physical size of the change. A task changing one line of config with wide impact across the whole system can be “bigger” in substance than a task writing a hundred lines of straightforward boilerplate.
Defining an Unambiguous “Done”
Acceptance criteria at the spec level, as covered in Part 2 of the Spec-Driven Development series, usually span the whole feature. The definition of done at the task level is a narrower derivative — the subset of acceptance criteria relevant specifically to that task, plus specific technical criteria that may not be stated explicitly in the feature-level spec but still need to be clear at the execution level.
Task: Implement the POST /password-reset/request endpoint
Definition of Done:
□ The endpoint accepts requests matching the already-defined
OpenAPI schema (reference: spec.md Acceptance Criteria section #1-2)
□ Rate limiting of 3 requests/email/hour works and is tested
□ Tokens are stored as hashes, not plaintext (security
constraint from spec.md)
□ Unit tests for email validation and integration tests for the
endpoint are complete, both passing
□ Does not change the existing users table schema (non-goals
from spec.md)
A good definition of done always traces back to a higher document — spec, constraints, or non-goals — rather than being newly invented in isolation with no clear link to the agreed contract. This keeps task-level definitions of done from silently drifting from what the spec level actually intended.
What separates a good definition of done from a bad one isn’t length but testability. Every line should ideally be answerable with a definite yes or no, without needing extra interpretation — the same principle as the testable acceptance criteria from Part 2 of the previous series, just applied at a smaller, more technically specific scale.
Task Independence and Dependencies
Some tasks can be done in parallel without interfering with each other; others must wait for certain tasks to finish first. Identifying which is which is an important part of task design that’s often ignored until two agents (or two working sessions) clash while working on interdependent things at the same time.
A simple dependency graph helps visualize this before execution starts:
flowchart TD
A[Task 1: Database Schema] --> B[Task 2: Request Reset Endpoint]
A --> C[Task 3: Confirm Reset Endpoint]
B --> D[Task 4: Rate Limiting on Request Endpoint]
C --> E[Task 5: Token Validation on Confirm Endpoint]
D --> F[Task 6: Audit Log]
E --> FThis graph makes it clear: Tasks 2 and 3 can be done in parallel once Task 1 finishes, because they don’t depend on each other — they just both depend on the database schema. But Task 6 (audit log) must wait for Tasks 4 and 5 to finish, because the audit log records the results of both endpoints.
Without an explicit dependency graph, the risk is that two tasks that actually depend on each other get executed as if independent — producing conflicts only detected when merging results, rather than prevented from the start. Conversely, tasks that are actually independent but treated as sequential just waste time waiting unnecessarily.
Before execution starts, take the time to draw the dependency graph between tasks — even for small projects. This small investment prevents situations where two working sessions (whether with the same agent at different times, or different agents in parallel) assume different things about the same state.
Scope Boundaries: Keeping Tasks from Drifting
Non-goals at the spec level, as covered in Part 2 of the Spec-Driven Development series, keep the whole feature from drifting beyond what was intended. But scope creep also happens at a smaller level — individual tasks that start out narrowly defined, then expand during execution because the agent finds things that “look related” and decides to handle them while it’s at it.
ANTI-PATTERN (task without a scope boundary, expanding during execution):
Task: Fix password validation to follow the new policy
(minimum 8 characters, combination of letters and numbers)
What happens: the agent discovers that the current password
validation error messages are also unclear, so it goes ahead and
fixes the entire error message system in the registration form,
then finds the phone number field isn't well validated either,
so it adds validation for that too.
A task that started narrow ends up touching four different parts
of the registration form, is hard to review, and is hard to
separate into what was requested vs. what was extra initiative.
CORRECT (explicit scope boundary at the task level):
Task: Fix password validation to follow the new policy
(minimum 8 characters, combination of letters and numbers)
Scope Boundary:
- ONLY changes password validation logic, doesn't touch
validation of other fields
- ONLY changes error messages for invalid password cases,
doesn't normalize other fields' error messages
- If other related problems are found (e.g. other fields'
error messages are also unclear), record them as separate
findings, DO NOT fix them in this task
The “record as a separate finding, don’t fix it in this task” pattern is important to state as an explicit instruction, not assumed to be obvious. An agent that finds something seemingly related will naturally tend to want to “help” by finishing it too — that intent actually makes sense from the agent’s perspective, but without explicit boundaries, that good intent just produces tasks that are hard to review and hard to assign responsibility for.
Tasks for Ambiguous or Exploratory Work
Not all work can be precisely defined from the start. Sometimes what’s needed is exploration to understand something before concrete implementation tasks can be defined — for example, investigating the cause of a bug whose root cause isn’t known yet, or researching technical approaches before an architectural decision can be made.
Forcing the “testable definition of done” format on this kind of work doesn’t make sense, because what the final result will look like isn’t known yet. The solution isn’t avoiding defining tasks for exploratory work, but changing the output type: from “working code” to “documented findings”.
Task (type: spike/exploration): Investigate the cause of the search
endpoint's response time slowing significantly in production
Exploration Scope/Time Limit:
- Investigation covers at most: database queries, indexes in
use, and traffic patterns over the last 7 days
- Does NOT change any code or configuration at this stage
Expected Output (not code, but findings):
- Identification of candidate causes (or several candidates if
one can't be confirmed yet)
- Supporting data (query plans, metrics, patterns found)
- Recommendations for next steps, which will become separate
implementation tasks after this investigation is reviewed
The “spike task” pattern — a term borrowed from agile practice, used here in the same sense — is bounded explicitly in both directions: the exploration scope that may be touched, and the explicit prohibition on changing code in this task. Once the spike task’s findings are human-reviewed, concrete implementation tasks get defined based on those findings — so implementation tasks still have clear, testable definitions of done, only their information comes from spike results rather than being assumed from the start.
flowchart LR
A[Ambiguous/Not Yet Understood Condition] --> B[Spike Task: Bounded Exploration]
B --> C[Documented Findings]
C --> D[Human Review]
D --> E[Implementation Task with Clear Definition of Done]The biggest risk with spike tasks is boundaries that aren’t kept tight — an investigating agent finds an “obvious small fix” mid-exploration and applies it immediately, even though the prohibition on changing code at this stage exists for a clear reason: implementation decisions should wait for complete findings and review, not be made halfway through while understanding is still partial.
Task Writing Template
Combining all the principles above, here’s a practical template for writing individual tasks that can be used directly:
## Task: [Short, Specific Task Name]
### Goal
[One or two sentences: what this task aims to achieve]
### Context
[Reference to the relevant spec/document — which part of spec.md
this task is based on. Background information the agent needs to
understand why this task exists, without rereading the entire spec]
### Constraint
[Technical constraints specific to this task, derived from the
relevant spec-level constraints]
### Scope Boundary
[What is explicitly NOT part of this task — especially things that
might look "related" and tempt you to do them while you're at it]
### Dependency
[Which tasks must finish first, and which tasks wait for this one]
### Definition of Done
□ [Criterion 1, testable, referenced to spec acceptance criteria]
□ [Criterion 2]
□ [Criterion 3]
For spike/exploratory tasks, the “Definition of Done” section is replaced with “Expected Output” as shown in the previous section, plus an “Exploration Scope/Time Limit” section to keep exploration from expanding without bounds.
This template doesn’t have to be filled out at length for every section — a simple task can have one line per section. What matters isn’t length, but that each section is thought about explicitly, not assumed to be “obvious from context”.
Anti-Patterns in Task Design
Several patterns often appear and weaken task effectiveness even when they look “well broken down” on the surface:
Overly vague tasks. Instructions like “fix this bug” or “improve this endpoint’s performance” without a concrete definition of what counts as “fixed” or “improved”. This kind of task forces the agent to guess the success criteria itself, with results that can vary widely depending on interpretation.
Tasks combining several concerns at once. One task covering a database schema change, business logic, and UI changes all together — even though it’s nominally “one task”, it actually combines several work units that should be independent, producing the granularity problems discussed above even though the recorded task count stays at one.
Tasks without clear dependencies until a parallel clash. Two tasks that both assume they’re “the first” to change a file or schema, done simultaneously without an explicit dependency graph, producing conflicts only detected when merging the two results.
Scope boundaries that are only implicit. Relying on the agent to “know on its own” where the task ends without stating it explicitly — as discussed in the scope boundary section, an agent’s good intent to help beyond what’s asked just produces tasks that are hard to review and hard to assign responsibility for.
Forcing a testable format on work that’s actually exploratory. Trying to write a precise definition of done for work that fundamentally needs investigation first — the result is a forced definition of done that doesn’t really reflect what the task actually requires. Use the spike task pattern for these cases, not a forced regular implementation task template.
An ambiguous task at the start doesn’t become clearer as execution proceeds — the ambiguity just transforms into implicit decisions the agent makes unilaterally, which only surface when the final result is reviewed. Resolving ambiguity before execution starts is always cheaper than untangling it after code is already written.
Summary
- Designing tasks isn’t just breaking work into small pieces — good task boundaries align with the dependencies that actually exist, not cuts made by size alone
- Wrong granularity is problematic in both directions: tasks too big are hard to verify, tasks too small create coordination overhead disproportionate to the work’s value
- The definition of done at the task level is a narrow derivative of spec-level acceptance criteria, plus specific technical criteria — always traceable back to the higher document
- A dependency graph between tasks prevents two interdependent tasks from being executed as if independent, and conversely prevents independent tasks from being needlessly treated as sequential
- Scope boundaries at the task level matter as much as non-goals at the spec level — without explicit boundaries, a small task can balloon because the agent finds “related” things it wants to handle while it’s at it
- For work that can’t yet be precisely defined, use the spike task pattern: bounded exploration scope, no code changes, and documented findings instead of code — then it becomes an implementation task after review
- Avoid vague tasks, tasks combining several concerns at once, dependencies that are only implicit, and forcing a testable format on work that actually needs exploration first