AGENTS.md and CLAUDE.md are table stakes — enforcement is the moat

Every serious SaaS boilerplate now ships an AGENTS.md or a CLAUDE.md — a soft file the agent can quietly ignore by prompt 50. The differentiator is enforcement: ESLint-checked DDD boundaries and typed contracts that turn the build red when the agent crosses a line.

MC
Martin Coll

Open almost any serious SaaS boilerplate in 2026 and you'll find an AGENTS.md or a CLAUDE.md at the repository root. It's the new README: a plain-language brief that tells your coding agent how the codebase is laid out, which patterns to follow, and which mistakes have already bitten someone. UseDeploy ships one too — a long, opinionated CLAUDE.md. But if you're grading a boilerplate on whether it "has an AGENTS.md," you're grading the wrong thing. That file is table stakes. The moat is what happens when the agent ignores it.

The rules file is a suggestion, and the agent knows it

AGENTS.md is now a real ecosystem standard, not a Claude-specific quirk. As of mid-2026 it's used across tens of thousands of open-source repositories and read natively by 30-plus agent tools — OpenAI Codex, Claude Code, GitHub Copilot, and Cursor among them. CLAUDE.md is the Claude-specific sibling. Both do the same thing mechanically: the file is loaded into the model's context at the start of a session and injected as a prompt prefix. The agent tends to follow it.

"Tends to" is the whole problem. A markdown file — whatever its filename — is guidance, not a gate. It has no failure mode. When the agent follows it, great. When the agent drifts, nothing happens: the build still goes green, the PR still opens, and the violation ships until a human notices it in review.

And agents drift. The failure mode has a name now — practitioners call it the "doom loop" or "context rot": the agent sees only a slice of your project each session, so it edits confidently against a stale mental model and the bug rate climbs as sessions get longer. A rule the agent didn't load this session is a rule that, for this session, does not exist. You can write the most beautiful AGENTS.md in the world and the agent will still, by prompt 50, confidently reintroduce the exact pattern you told it to avoid — because the file is upstream of the model's attention, not downstream of the compiler.

This is why "AI-agent-ready" has quietly stopped being a differentiator. As of July 2026 every serious competitor ships the file: supastarter, for instance, markets an AGENTS.md optimized for Cursor and Claude Code, "designed to be extended by AI coding agents." Shipping the soft file is now the price of entry. It's necessary. It is nowhere near sufficient.

What UseDeploy's CLAUDE.md actually asks for

To be clear: the soft file earns its keep. UseDeploy's CLAUDE.md is where the hard-won, human-only knowledge lives — the stuff no linter can express. A good example is the rule about shared contracts, which exists because we shipped the bug it prevents:

When a form, mutation, or query touches an HTTP endpoint, the client-side Zod schema must come from @app/contracts — never re-define a server-known field. [...] If the form has its own fullName while the server expects name, typecheck passes and the user gets a 400 in production. We have shipped this exact bug; do not repeat it.

That's real institutional memory, and it's exactly the kind of thing an agent starting from a blank repo would get wrong on its first pass. But read it again: it's still an ask. "Must come from @app/contracts" is a sentence. The agent can nod at it and then inline the field anyway, and the file has no way to stop it. Which is why the same concern shows up a second time in the repo — as enforcement.

The moat: three walls the agent can't talk its way past

The market has already crystallized the winning frame for this. As of July 2026, one competitor landing page describes serious buyers as purchasing "the guardrails [the agent] can't write for itself: the rules that keep it consistent, and the walls that stop it breaking your login when it touches payments" — because otherwise "by prompt 50 it's spaghetti." That's the pitch. The question is whether a boilerplate actually builds those walls, or just writes them down.

UseDeploy builds them. There are three, and none of them are markdown.

1. DDD layering that fails lint, not review

The domain layer of every bounded context is forbidden — by ESLint, not by convention — from importing a framework or reaching up the stack. This is the literal config from packages/eslint-config/ddd.js:

{
  files: ['**/modules/*/domain/**/*.ts'],
  rules: {
    'no-restricted-imports': ['error', {
      patterns: [
        {
          group: ['*/application/*', '*/infrastructure/*', '*/interfaces/*'],
          message:
            'domain/ may not import from application, infrastructure, or interfaces. See ADR-001.',
        },
        {
          group: ['express', 'prisma', '@prisma/client', 'better-auth'],
          message: 'domain/ must remain framework-agnostic. See ADR-001.',
        },
      ],
    }],
  },
}

When the agent — trying to be helpful — imports @prisma/client into a domain entity to "just fetch the record here," bun run lint exits non-zero with a message that names the ADR. It's not a style nit that a reviewer might wave through at 6pm. It's a red build. The bounded-contexts doc explains the layering the rule protects; the rule is what makes the doc true even when nobody's looking.

2. The wall between login and payments is a compiler error

Remember "the walls that stop it breaking your login when it touches payments"? That's not a metaphor here — it's a second ESLint rule. A bounded context may reference another context's runtime code only through import type (for dependency-injection wiring) or its sanctioned HTTP middleware. A value import across contexts is banned:

{
  files: ['**/modules/*/**/*.ts'],
  rules: {
    '@typescript-eslint/no-restricted-imports': ['error', {
      patterns: [{
        group: ['ai', 'audit', 'billing', 'feature-flags', 'iam',
                'notifications', 'platform', 'storage', 'tenancy',
                'usage', 'webhooks'].flatMap((m) => [
          `**/${m}/domain/**`,
          `**/${m}/application/**`,
          `**/${m}/infrastructure/**`,
        ]),
        allowTypeImports: true,
        message:
          'Cross-context runtime import: a module may reference another bounded context only via `import type` or its HTTP middleware, never a value/runtime import. See ADR-001.',
      }],
    }],
  },
}

So when the agent is deep in modules/billing and decides the fastest way to check a user's session is to import { SessionService } from '../iam/...', the build breaks. Billing (payments) physically cannot reach into IAM (login) at runtime by accident. The soft file could only ask the agent not to; this makes the coupling impossible to introduce without turning the CI red first.

3. Typed contracts the schema itself can't drift from

The @app/contracts rule from CLAUDE.md has an enforcement twin. Whenever an endpoint's shape changes, bun run generate:api regenerates the OpenAPI document and the client's typed paths — and a CI job (api-types-fresh) fails the PR if those artifacts are stale. This is the exact class of failure type systems are built for: an agent that renames a field on the server but forgets the client now gets a typecheck failure at the call site instead of a 400 in production. Layer a golden-path Playwright suite gated in CI on top — a real, constrained smoke test the change has to pass — and you have what the pro-dev crowd calls "harness engineering": the repo is an execution environment for the agent, with fast validation and a bounded blast radius, not a trust exercise.

Why enforcement matters more with an agent than with a human

A human engineer reads CLAUDE.md once, internalizes it, and carries it across every session for months. An agent re-derives its plan from a partial view of the codebase every single time, and — as the "context rot" research keeps documenting — converges on its own generic defaults as the session drags on. The soft file fights that with persuasion. Enforcement fights it with a wall in the code path.

That's the asymmetry that makes this the actual moat. A rules file scales with how reliably the agent reads and remembers it — which degrades exactly when you need it most, on long sessions in large codebases. A lint rule, a failing typecheck, a red E2E gate: those fire whether or not the agent read the doc, on turn 1 and on turn 500 identically. The wall is in the code, not in a prompt the agent might have evicted from its context ten tool-calls ago.

The honest version

UseDeploy ships a CLAUDE.md today, not yet an AGENTS.md — supporting the cross-tool standard so Codex, Cursor, and Copilot users get the same brief is a real gap and it's on the roadmap. But notice that admitting it costs the argument nothing: even a perfect, cross-agent AGENTS.md would still be a suggestion. The part that stops the agent from breaking login when it touches payments isn't the file. It's the ESLint boundary, the typed contract, the CI gate — the layers that turn "please don't" into "the build won't let you."

So when you compare boilerplates for agent-driven work, don't stop at "does it have an AGENTS.md." Everyone's file is fine. Ask the harder question: when the agent ignores that file — and it will — what actually catches it before it ships? That's the difference between a document and a moat.

If you're evaluating UseDeploy for exactly this — pointing a coding agent at a base it can't quietly rot — the AI coding agents page lays out the full set of walls, and the bounded-contexts doc shows the layering the ESLint rules above defend. The tenant-isolation guard is the same philosophy one layer down: a runtime wall that throws instead of leaking, because a guardrail an agent can't see is a guardrail that works.