The three files an Angular monorepo needs before an agent writes a line
One file the agent must read, one it must pass, one it must stay inside. An afternoon of work, and the oversized diff fails before review.

The first part ended with a team whose agent lands inside the fence on its first try. This part builds the fence. It is three files: one the agent must read, one it must pass, one it must stay inside. An afternoon of work, and the rules that lived in people's heads become something the agent has to obey.
1. The file it must read
Every agent reads a project-level instructions file before it acts, and each tool has its own: CLAUDE.md, GEMINI.md, .github/copilot-instructions.md, .cursor/rules/. Several of them now also read AGENTS.md. Many teams leave these files empty, paste in the README, or write four copies that drift apart.
The agent sees your code, not your reasons. The rules file is where the reasons live.
Write one file, AGENTS.md, and make every tool file a single line that points to it. In the demo repo CLAUDE.md is @AGENTS.md plus the block the tooling adds, and GEMINI.md tells the agent to read and follow AGENTS.md before anything else. One source of truth, any agent. A tool's extras, like permissions or hooks, go in that tool's own settings; the rules never move.
You do not have to write the part yourself. The Angular team publishes it: a best-practices.md on angular.dev/ai, and in angular/skills. In the demo repo they sit in .agents/skills/, a folder several agents read, and the Angular and Atlas skills are linked into .claude/skills/ for . Your AGENTS.md keeps only what the Angular team cannot know: your boundaries, your legacy, your definition of done.
What goes in it is short, concrete and mostly negative. The agent does not need your architecture vision. It needs the fence. Condensed from the demo repo's AGENTS.md:
# Atlas Bank: rules for coding agents (excerpt)
## Layout
- libs/domain pure TypeScript, no Angular imports
- libs/data-access/* stores and services; may import domain
- libs/ui presentational only; never imports data-access
- libs/feature/* routed features; may import ui, data-access, domain
## Non-negotiables
- New components are standalone and use signals. No new NgModules.
- Never edit libs/legacy/** unless the task file says migrate. Wrap it.
- Crossing a library boundary needs an ADR link in the PR.
## Before you say you are done
- npm run lint:arch passes
- npm run test passes without changed assertions
- The diff is ≤ 300 lines unless the task file raises the limitOne word there deserves a line. An is an architecture decision record, a short dated note saying why a boundary exists. Asking the agent to link one is asking it to justify the crossing, and it usually cannot, which is the point.
2. The file it must pass
Instructions are suggestions. A failing command is not. The same boundaries become a , with @nx/enforce-module-boundaries in an Nx workspace or in a plain Angular CLI project. From the demo repo's flat config:
// eslint.config.mjs (excerpt)
const depConstraints = [
{ sourceTag: 'type:domain', onlyDependOnLibsWithTags: ['type:domain'] },
{ sourceTag: 'type:data-access', onlyDependOnLibsWithTags: ['type:data-access', 'type:domain'] },
{ sourceTag: 'type:ui', onlyDependOnLibsWithTags: ['type:ui', 'type:domain'] },
{ sourceTag: 'type:feature', onlyDependOnLibsWithTags: ['type:feature', 'type:ui', 'type:data-access', 'type:domain'] },
];
// ...
'@nx/enforce-module-boundaries': ['error', { depConstraints }],Now the rules file can say "run lint:arch before you finish", and an agent that runs it will usually retry until it passes. "Please respect the architecture" has become a command that fails.
A data-access import in a ui component, and the build says no before a reviewer has to.
3. The file it must stay inside
The third file is per task, not per repo: the ask, written down before the agent reads it, with a scope, an out-of-scope and a "done when". It can be a Jira ticket, a GitHub issue with fixed headings, or a Markdown file in docs/tasks/. The form does not matter. What matters is one sentence:
Scope: libs/feature/accounts only. Done when lint:arch and tests pass and the diff is under 300 lines.The size limit is the part that changes the most. Agents are excellent at satisfying explicit constraints and terrible at guessing implicit ones. With 300 in the task, a diff of well over a thousand lines fails the done-when before anyone reads it.
Read AGENTS.md. Then open libs/ and list, as a numbered list, every rule you would need to do a feature in libs/feature/accounts that the file does not state: which imports are allowed, which store to use, how tests are written, what "done" means. Do not write code. Do not propose rules; only list what is missing.
Run it once per quarter. The list is your rules file's backlog.
What changes when the three files exist
- The agent's first attempt usually lands inside the fence, so review drops from "read everything" to "read the decision".
- The fence teaches: the lint error explains a boundary better than a wiki page.
- The files are a real artefact of the team's architecture, which most teams never had before.
On Monday
- Write
AGENTS.md, about a page, mostly "never". Point every tool file at it. - Turn the layout list into a lint rule and add
lint:archto "done". - Put a size limit in the next task you hand to an agent.
Exercise: module 1 in the demo repo. Start without the files: git switch track1/m1-start. Then the same task with them: git switch -c m1/you main. Reference: origin/track1/m1-solution. Bring your own agent.
Who writes that third file, and how fast, is the next part: the agent drafts it from the ticket, and a person decides.
Track 1, part 2 of the free series behind the Fast and safe: AI in your Angular codebase workshop.
Fast and safe: AI in your Angular codebase
Let agents write Angular under rules, modernise legacy code without breaking production, and keep it secure, in three days, on an enterprise-shaped codebase.
See the agenda