Why repo structure matters in agent-driven software development

Layer-based folders made sense when humans held the whole system in their heads. Coding agents don't work that way — they search, slice context, and patch locally. Domain-driven layout is how you keep agents fast, accurate, and safe.

A common pattern in mature codebases looks like this:

src/
  controllers/
  services/
  repositories/
  models/
  utils/
  middleware/

Humans trained on this layout know the drill: find the controller, jump to the service, drop into the repository. It mirrors how we were taught to separate concerns — presentation, business logic, persistence.

Coding agents don't navigate like senior engineers. They start from a task description, run semantic or keyword search, open a handful of files that fit in context, and propose edits. When your repo is organized by technical layer instead of business capability, every feature change becomes a scavenger hunt across distant folders — and that's where agent-led development gets slow, wrong, or both.

How agents actually "see" your codebase

Whether you're using Cursor, Claude Code, Devin, or an in-house agent loop, most workflows share the same constraints:

  • Limited context: Only a subset of files fit in the model window at once.
  • Search-first discovery: Agents rely on ripgrep, embeddings, or file-tree summaries — not years of tribal knowledge.
  • Local patches: They tend to edit what they opened; missing a sibling file (validation, policy, migration) is a common failure mode.
  • Shallow ownership signals: Without clear boundaries, agents guess which "service" or "util" owns a behavior.

Layer-based structure optimizes for role separation. Agents optimize for task completion in minimal hops. Those goals diverge.

Why technical-layer repos hurt agent-led coding

1. Features are scattered across the tree

"Add refund eligibility to subscription cancellations" might touch:

  • controllers/billing_controller.ts
  • services/subscription_service.ts
  • repositories/subscription_repo.ts
  • models/subscription.ts
  • utils/date_helpers.ts (shared with unrelated domains)

An agent that finds the service file may never open the repository rules or the API contract. Layer folders encode how code is typed, not what business rule changed.

2. Generic names collide in search

Files named service.ts, handler.go, utils.py, or types.ts appear everywhere. Search returns dozens of near-duplicates; the agent picks the wrong one or merges patterns from unrelated domains. This is a leading cause of "looks plausible, wrong module" diffs.

3. Shared folders become junk drawers

utils/, common/, and helpers/ accumulate cross-domain logic. Agents love dropping one-off functions there because it's always "close enough." Over time, utilities stop being generic — but nothing in the path tells the agent which domain owns the behavior.

4. Tests drift from the code agents edit

In layer layouts, tests often mirror structure (tests/services/) instead of features. Agents update production code in one pass and miss the test file three directories away. Domain colocation — keeping specs next to the module — dramatically improves the odds both get updated in one turn.

5. Refactors require global reasoning agents don't have

Renaming a concept that spans layers ("CustomerAccount" → "Workspace") is trivial for a human with IDE-wide refactor. Agents piecemeal-rename what they see and leave stragglers. Bounded domains shrink the blast radius of renames and make incomplete edits obvious in review.

What domain-driven repo structure gives you

Domain-driven layout (in the repo sense — not necessarily full DDD tactical patterns) groups everything for one capability together: API surface, business rules, persistence, events, prompts, and tests.

src/
  billing/
    cancel_subscription/
      handler.ts
      policy.ts
      subscription_repo.ts
      cancel_subscription.test.ts
    refunds/
      ...
  identity/
    invite_user/
      ...
  shared-kernel/
    clock.ts
    id.ts

For agents, the benefits are immediate:

  • One folder ≈ one task: "Work in billing/cancel_subscription/" bounds search and edits.
  • Search hits are semantically dense: Paths like billing/refunds/eligibility.ts disambiguate intent.
  • Policies live next to actions: Agents see "what's allowed" beside "what runs" — critical for tool-using agents and regulated workflows.
  • Tests as local guardrails: Colocated tests signal expected behavior in the same context window as the change.
  • Clear ownership for humans and bots: CODEOWNERS, agent rules, and docs can target billing/** instead of guessing layers.

Layer-based vs domain-based: agent workflow comparison

Task Layer-based repo Domain-based repo
Find relevant code Multiple searches across controllers, services, repos Open one domain folder; list directory
Stay within context budget Hard — files spread across tree Easier — vertical slice fits in one window
Avoid cross-domain bleed Shared utils encourage copy-paste patterns Explicit shared-kernel; domains stay isolated
Review agent diffs Scattershot files; easy to miss side effects Coherent per-feature diff
Document for agents "See architecture doc" (often stale) README.md per domain with invariants

Principles for agent-friendly domain layout

Name folders after capabilities, not mechanisms

Prefer billing/refunds/ over services/refund_service.ts. Prefer support/escalation/ over handlers/escalation_handler.py. The path should answer: what business outcome does this change?

Keep vertical slices thin but complete

A slice should include everything an agent needs to implement or modify a behavior: entrypoint, domain logic, persistence adapter, schema/types, and tests. Thin slices reduce back-and-forth; completeness reduces "forgot the migration" failures.

Isolate a small shared kernel

Truly generic primitives (IDs, money types, tracing wrappers) belong in shared-kernel/ or platform/ — with strict rules: no business rules, no domain imports upward. Agents should rarely need to edit shared code for feature work.

Colocate agent-specific assets

For agent-led products, keep prompts, tool manifests, eval fixtures, and policy files beside the domain they govern — not in a global prompts/ dump. Example: billing/cancel_subscription/agent_tools.json next to policy.ts.

Add a domain README agents can read first

Each top-level domain folder should have a short README: glossary, invariants ("refunds never exceed captured amount"), forbidden actions, and links to external systems. Agents treat these as ground truth when search is ambiguous.

Align CI and ownership with domains

Run tests per domain where possible. Map CODEOWNERS to domain paths. Agent instructions (Cursor rules, AGENTS.md) can say: "When changing refunds, only edit under billing/refunds/** unless explicitly migrating shared types."

Migrating without a six-month rewrite

You don't need to move every file on day one. A practical sequence:

  1. Pick one high-churn workflow agents touch often (onboarding, billing, support triage).
  2. Create the domain folder and move the vertical slice — re-export from old paths temporarily if needed.
  3. Stop adding new code to layer folders for that domain; enforce in review.
  4. Strangle shared utils: move domain-specific helpers into the domain; shrink global utils/.
  5. Repeat per domain as agent velocity justifies the move.

Feature flags and adapter layers let old imports work while agents learn the new boundaries. The goal is monotonic improvement — each sprint, agents have one more place where "everything for this task is here."

Repo structure is part of your agent harness

Teams investing in eval harnesses, golden tasks, and CI gates for agent-led development (see validation and testing harnesses) still lose time if the codebase fights discovery. Structure is the cheapest layer of the harness: it shapes what agents find before they write a single line.

Layer-based repos made sense when the primary navigator was a human who knew the system map. Agent-driven development shifts the bottleneck to context assembly. Domain-driven layout is how you make that assembly reliable — fewer wrong files, smaller diffs, faster reviews, and features that stay where the next agent (and the next human) expects them.

CTA: Reorganizing for agents while shipping product is a balancing act. I help teams define domain boundaries, migration slices, and agent rules that match how your product actually changes — not textbook DDD for its own sake.