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.tsservices/subscription_service.tsrepositories/subscription_repo.tsmodels/subscription.tsutils/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.tsdisambiguate 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:
- Pick one high-churn workflow agents touch often (onboarding, billing, support triage).
- Create the domain folder and move the vertical slice — re-export from old paths temporarily if needed.
- Stop adding new code to layer folders for that domain; enforce in review.
- Strangle shared utils: move domain-specific helpers into the domain; shrink global
utils/. - 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.