DEV Community

Jack M
Jack M

Posted on

AI Coding Agent Task Graphs: Split Features Without Creating Merge Debt

Parallel coding agents look fast right up to the moment three clean branches all change the same contract. Each agent can finish its own slice, run green tests, and still leave the team with a slow, risky integration problem. The mistake happened before the first edit: the feature was divided into a to-do list instead of an AI coding agent task graph.

A task graph makes dependencies, shared interfaces, and proof of completion explicit. It tells you which work can safely fan out, which work must stay in order, and what evidence each agent must return. That turns “let several agents build this” into a plan that a human can inspect and a repository can enforce.

This guide is tool-neutral. Use it with terminal agents, IDE agents, or an internal runner. The goal is not to maximize agent count. It is to create independent, mergeable units of work.

Why a task list fails for parallel coding agents

Consider a request: “Add invoice export.” A flat task list might say:

  1. Add an export API.
  2. Build an export button.
  3. Add audit logging.
  4. Write tests.

That sounds parallel. It is not. The API response shape affects the button, authorization affects the audit event, and tests need a stable contract. If four agents begin from that list, they may invent four versions of the same assumptions.

A graph captures the relationships instead:

define export contract ──┬── API implementation ──┬── API tests
                         │                         └── audit event tests
                         └── UI implementation ─────── UI tests
Enter fullscreen mode Exit fullscreen mode

The contract is a dependency, not “planning overhead.” It is the smallest shared decision that lets the other nodes proceed without guessing. The first useful question is therefore not “what tasks exist?” Ask: which decisions would make two agents change or interpret the same thing differently?

Recent developer discussion makes the distinction practical. Worktrees prevent agents from overwriting one another’s files, but they do not make two changes semantically independent. Search results on parallel coding commonly recommend splitting along module or service boundaries, then treating shared APIs, schemas, and utilities as planned dependencies. That is exactly what the graph is for.

Start with seams, not roles

“One agent for frontend, one for backend, one for tests” is a role chart, not a decomposition. It often sends all three agents toward the same shared types and acceptance criteria.

Instead, find seams in the change:

Seam A good independent node A coupled node that should wait
Module boundary A new export formatter inside its own package A formatter that changes a shared money type
Stable interface A client built against an agreed response schema A client guessing an endpoint still being designed
Data ownership A read-only query with defined fields A migration and every caller it changes
Test boundary Fixture tests for a stable contract End-to-end tests for behavior still in motion
Operational boundary Documentation for a finished flag Deployment work that depends on new secrets or jobs

Prefer a vertical slice when it can own a stable behavior end to end. Prefer a sequential “foundation” task when it changes a shared contract. This is a useful rule of thumb:

If merging one task while the other is still running would force a compatibility shim, a rework, or a second interpretation of a rule, draw a dependency edge.

Do not split merely because there are several files. Two separate files can share one customer-visible invariant. Conversely, several edits in one module can belong to one safe task when one agent can validate them together.

Write a work contract before dispatch

Every node needs more than a natural-language title. Give the agent a bounded contract it can use to decide whether it is done and whether it has discovered a dependency.

id: invoice-export-api
depends_on:
  - export-contract
owns:
  paths:
    - apps/api/src/invoices/export/**
  behavior:
    - GET /v1/invoices/:id/export returns a signed download URL
inputs:
  contract_ref: docs/contracts/invoice-export.yaml
must_not_change:
  - payment-token storage
  - existing invoice response fields
acceptance:
  - unauthorized callers receive 404
  - export never includes payment tokens
  - audit event contains invoice ID and actor ID
evidence:
  - command: pnpm test invoices:export
  - artifact: test-results/invoice-export.xml
handoff:
  - changed paths
  - commit SHA
  - assumptions or newly found dependencies
Enter fullscreen mode Exit fullscreen mode

owns makes the likely edit surface visible; it is not a file lock. must_not_change protects surrounding behavior that an eager agent might “clean up.” acceptance is the behavior to prove, and evidence says how to prove it. Those fields turn an agent’s prose summary into something a reviewer can check.

Keep contracts short enough to read before work starts. A massive specification becomes stale; a sentence becomes ambiguous. The right size is usually one independently reviewable outcome.

Draw dependencies as a directed graph

Use a directed edge when task B needs an output or decision from task A. The output can be a schema, an interface, a migration, a feature flag, a test fixture, or a policy decision.

type Task = {
  id: string;
  dependsOn: string[];
  paths: string[];
  provides: string[];
  consumes: string[];
  acceptance: string[];
};

const tasks: Task[] = [
  { id: "export-contract", dependsOn: [],
    paths: ["docs/contracts/invoice-export.yaml"],
    provides: ["ExportResponse v1"], consumes: [],
    acceptance: ["schema validates example responses"] },
  { id: "export-api", dependsOn: ["export-contract"],
    paths: ["apps/api/src/invoices/export/**"],
    provides: ["GET /v1/invoices/:id/export"],
    consumes: ["ExportResponse v1"],
    acceptance: ["authorization and redaction tests pass"] },
  { id: "export-ui", dependsOn: ["export-contract"],
    paths: ["apps/web/src/invoices/**"],
    provides: ["download action"], consumes: ["ExportResponse v1"],
    acceptance: ["keyboard and error-state tests pass"] },
];
Enter fullscreen mode Exit fullscreen mode

Topologically sort this graph before dispatch. Nodes with no unfinished dependencies are eligible to run. The sorter is simple; the hard and valuable work is naming real dependencies before agents hide them in commits.

Treat shared contracts as first-class nodes

Teams often put a schema change inside whichever implementation task starts first. That makes the first agent an accidental architect and forces the others to chase a moving target.

Instead, create a small contract node when a change touches any of these:

  • public API request or response fields;
  • database migrations or shared domain types;
  • permissions, quota rules, or tenant boundaries;
  • event names and payloads;
  • feature-flag behavior; or
  • a user-visible error or retry promise.

The contract node need not be an elaborate document. A versioned JSON Schema, a typed interface plus examples, or a concise decision record can be enough. Its acceptance test should reject incompatible examples. Once it is accepted, downstream agents have a stable target.

Run the fan-out test before adding agents

For each candidate task, answer five questions:

  1. Can it name one owner and a bounded behavior?
  2. Can it complete without changing an unfinished shared contract?
  3. Can it run a meaningful check without another task’s unmerged code?
  4. Can a reviewer understand its diff without reading all sibling tasks?
  5. Can it be dropped or delayed without leaving the feature in an unsafe state?

Four or five “yes” answers make a strong parallel candidate. One or two means the task is probably a disguised dependency. Merge it with its prerequisite or sequence it after a foundation node.

This test also avoids a hidden cost of broad fan-out: every agent needs repository context, tool access, and review time. A small number of well-bounded tasks usually beats a swarm whose agents repeatedly rediscover the same architecture.

Make ownership semantic, not just path-based

Path claims are cheap and helpful. If two agents intend to edit apps/api/src/billing/**, stop and inspect the plan. But they miss dependencies such as a UI route consuming a renamed API field, or a worker emitting an event another service parses.

Add two declarations to each contract:

  • Provides: a route, type, event, migration, or invariant the task creates or changes.
  • Consumes: a route, type, event, migration, or invariant it relies on.

Then flag these collisions before dispatch:

Task A provides: Invoice.status = "exporting"
Task B consumes: Invoice.status values are ["draft", "paid"]

Result: add an edge A → B, or update the shared contract first.
Enter fullscreen mode Exit fullscreen mode

This does not need perfect static analysis. A short structured declaration catches the expensive kind of overlap: two changes that merge textually but disagree about behavior.

Use evidence as the handoff format

Agents should not hand off “done.” They should hand off a small receipt:

{
  "task": "export-api",
  "commit": "8d21c4a",
  "base": "f3c980b",
  "changed_paths": ["apps/api/src/invoices/export/route.ts"],
  "checks": [{"command": "pnpm test invoices:export", "status": "passed"}],
  "contract_version": "invoice-export/v1",
  "assumptions": [],
  "new_dependencies": []
}
Enter fullscreen mode Exit fullscreen mode

Require an explicit new_dependencies field. When it is nonempty, pause that node and revise the graph rather than allowing the agent to silently widen scope. This is the smallest useful guardrail against a task that looked independent but was not.

The receipt naturally feeds later workflow stages: a definition-of-done gate can verify the task’s evidence, and a merge queue can revalidate the immutable commit against the fresh shared base. Those are different controls. The graph decides whether work may fan out; the merge queue decides whether completed work may land.

A safe dispatch sequence

Here is a compact procedure for a feature with parallel potential:

  1. Write the user-visible outcome and non-negotiable invariants.
  2. List shared contracts, data changes, and permission rules.
  3. Make each shared decision a small foundation node.
  4. Draft work contracts for implementation and test nodes.
  5. Add dependency edges using provides and consumes.
  6. Run the fan-out test; keep coupled work sequential.
  7. Dispatch only ready nodes into isolated worktrees.
  8. Collect receipts, then send immutable commits to the integration queue.

The sequence is intentionally conservative around contracts and aggressive only after the plan becomes stable. That is where parallelism earns its speed: implementation agents spend less time debating interfaces, and reviewers spend less time reconstructing what was supposed to happen.

Common failure modes

Splitting by layer too early

Frontend, API, database, and test agents often all need one unresolved rule. Make the rule a contract node first. Only then split the implementations.

Hiding a migration inside an implementation task

Migrations change the ground beneath every reader and writer. Give the migration its own node, define forward and rollback behavior, and make dependent work wait for it.

Treating test work as automatically independent

Tests are independent only when they target a stable interface. Otherwise a test agent either blocks or invents a contract. Attach fixtures and expected behavior to the contract node.

Letting an agent expand scope silently

An agent that finds a shared utility may be right to touch it. The safe response is to update the contract and graph, not to bury the expanded change inside the same receipt.

FAQ

What is an AI coding agent task graph?

It is a directed plan of independently reviewable coding tasks, their dependencies, their owned behavior, and the evidence each task must provide. It makes parallelism conditional on real independence.

How is a task graph different from a project checklist?

A checklist records work to do. A graph records what must be decided or completed before another task can safely start. Its edges prevent agents from guessing about shared contracts.

When should coding agents work sequentially?

Keep work sequential when tasks share an unfinished API, schema, permission model, migration, or invariant. First create and verify the common foundation; fan out only once consumers have a stable contract.

Are Git worktrees enough for parallel coding agents?

No. Worktrees isolate files and branches, which is valuable, but they do not detect conflicting assumptions about an API or business rule. Use contracts and dependency edges alongside worktree isolation.

What should an agent hand off after completing a task?

Use an evidence receipt with an immutable commit, base commit, changed paths, checks run, contract version, assumptions, and newly discovered dependencies. That lets the next reviewer or integration system verify the work without trusting a claim of completion.

How many coding agents should a task graph fan out to?

Only as many as there are ready, independently verifiable nodes. Start with the graph’s true parallel frontier rather than a fixed agent count; fewer agents with clean seams are usually faster to integrate.

Sources and further reading

Top comments (0)