Agent Engineering
Module 05 · Steering/Lesson 5.5/3 min

Standards the agent cannot talk past

Convert conventions into checks. A rule in a file depends on attention; a rule in the build does not.

Every convention you can move from prose into a check gets three upgrades at once: it stops costing context, it stops depending on attention, and it starts failing inside the agent's own correction loop — so it self-heals without you.

The ladder

LevelMechanismReliability
1You say it in the sessionThis session, while the context is fresh
2A line in the briefMost sessions, degrading with length
3A lint rule or typeEvery time, deterministically
4Impossible by constructionCannot be violated at all

Push everything as far down this ladder as it will go.

Level 3 in practice

eslint.config.js — an architectural boundary, enforced
{
  files: ['src/domain/**'],
  rules: {
    'no-restricted-imports': ['error', {
      patterns: [
        { group: ['react', 'express', 'next/*'],
          message: 'src/domain must stay framework-free. Move this to src/server.' },
        { group: ['../server/*', '@/server/*'],
          message: 'Domain cannot depend on the server layer.' }
      ]
    }]
  }
}

The message is written for whoever reads it next — agent or human — and it says what to do, not just what is wrong. That turns a failing check into a usable instruction.

Level 4: make it unrepresentable

The strongest move is structural. If a mistake cannot be expressed, no amount of context degradation can produce it.

  • Branded types so a UserId cannot be passed where an OrgId is expected.
  • Parse at the boundary so internal code only ever holds validated values.
  • Exhaustive switches with a never check, so adding a variant breaks the build everywhere it must be handled.
  • Private-by-default modules with a single public entry point, so there is nothing to import incorrectly.

Each of these was already good engineering. What is new is the return on it: an agent generating code at volume will find every gap your type system leaves open, far faster than a team of humans would.

Custom checks are cheap now

A project-specific rule used to be too much work to justify. It is now a fifteen-minute session:

prompt
Write a custom ESLint rule that fails when a file under src/domain imports
anything from src/server. Include tests for the rule (valid and invalid cases)
and wire it into eslint.config.js. Then run 'npm run check'.

Every such rule is a brief line you get to delete.

Watch out

A check the agent can silence is not a check. Ban blanket suppressions in review — eslint-disable, @ts-ignore, # type: ignore, skipped tests — and put every one of them in the danger-grep from Lesson 3.3. The rule and the ability to bypass it must not both be available.

Try it

Pick the brief line you have violated most often. Have the agent write the check that enforces it, with tests. Then delete the line from your brief.

Takeaways

  • Push every convention down the ladder: session, brief, check, impossible.
  • Write check messages that say what to do, not just what is wrong.
  • Custom rules are now cheap enough to write for a single project convention.
  • Ban suppressions, or your checks are advisory.
Why does a check outperform a brief line specifically in long sessions?

Because a brief line competes for attention budget, and attention degrades as the window fills — so the rule is weakest exactly when the session is most likely to break it. A check runs deterministically at the same strength at turn 40 as at turn 1.

A course by Pieter Zandbergen