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

> Source: <https://dev.to/jackm-singularity/ai-coding-agent-task-graphs-split-features-without-creating-merge-debt-lip>
> Published: 2026-11-13 06:44:56+00:00

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.

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

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
```

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.

“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.

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
```

`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.

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"] },
];
```

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.

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:

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.

For each candidate task, answer five questions:

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.

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:

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.
```

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.

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": []
}
```

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.

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

`provides` and `consumes`.
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.

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

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.

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.

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.

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.

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.

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.

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.

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.

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.
