Engineers Corner
|
September 7, 2026
7
min read

Don't Think About Pink Elephants: Automate Your Agent Rules

Negative instructions in AGENTS.md are not enforcement. Turn recurring coding-agent mistakes into fast deterministic checks instead.
In short
Why
Agents keep breaking rules written in AGENTS.md, because instructions to a probabilistic system are not enforcement.
What
Encode every rule a machine can objectively verify into a deterministic check the agent runs itself.
How
Wire a fast lint, typecheck and test script with quiet output into completion hooks and pre-commit, with CI as the real gate.
Negative instructions in AGENTS.md are not enforcement. Turn recurring coding-agent mistakes into fast deterministic checks instead.

If you’ve used coding agents for a while you almost certainly have seen the following happen. Let’s say your project architecture dictates that domain code must not depend on UI or infrastructure code. Maybe this is even stated in a document somewhere. The agent violates the boundary at some point regardless. Now you add this line to AGENTS.md.

DO NOT import UI or infrastructure code from the domain layer

This tends to work for the most part. However, one day you are deep into a session and lo and behold, the domain layer is importing infrastructure. You add more all-caps words and exclamation marks to the MD file and it still keeps happening every now and then. What gives?

Let’s take a quick detour to how the human brain works. Did you succeed in not thinking about pink elephants after reading the title of this blog post? The pink elephant paradox (also known as the white bear or ironic process effect) describes a quirk in human psychology: Trying to suppress a thought can make it recur more often. Interestingly, there is some recent research indicating a similar effect may be happening in AI models.

In Don't Think of the White Bear, researchers tested negative instructions across nine language models and found that suppression became less reliable as more distracting context accumulated between the instruction and the response. A later mechanistic study, Semantic Gravity Wells, found another problem: explicitly naming the forbidden output can strengthen its representation inside the model, sometimes making it more rather than less likely to appear. And the effect isn't limited to language models as Do not think about pink elephant! explored related negative prompting failures in image-generation models.

Does this prove that the negative instruction in your MD file caused the problem? Not really, but the fact is that anyone who has spent enough time using coding agents is extremely familiar with the scenario where the agent just did something forbidden by MD rules and then said "Sorry, my bad" when questioned. Rules in MD files can be useful but ideally we should also enforce them somehow.

From instructions to deterministic verification

The principles to apply to agentic engineering are not really that different from how we used to work in the pre-agent times. Even for humans, you should not be asking nicely to remember formatting rules, but rather configuring a formatter to automatically format files. One way of helping your agents make fewer mistakes is to encode everything that a machine can objectively verify into a deterministic script.

Natural-language instructions to a probabilistic system are not enforcement mechanisms. If an invariant can be mechanically verified, verify it mechanically.

In the "old days" of hand-writing all code, the cost of writing a custom check for every project-specific mistake might have been hard to justify. The economics are different when the same agent that made the mistake can also write the check in a couple of minutes. Agents make it both more important and much cheaper to turn recurring mistakes into automated verification.

Let’s encode our original domain-layer restriction as a linting rule. In this case, we don't even need to write a custom checker. Most ecosystems already have tools for enforcing architectural boundaries.

For example, in a TypeScript project using Biome, this biome.json configuration rejects imports with UI or infrastructure paths from code under src/domain:

{
  "overrides": [
    {
      "includes": ["src/domain/**/*.{ts,tsx}"],
      "linter": {
        "rules": {
          "style": {
            "noRestrictedImports": {
              "level": "error",
              "options": {
                "patterns": [
                  {
                    "group": ["**/ui", "**/ui/**", "**/infrastructure", "**/infrastructure/**"],
                    "message": "Domain code must not depend on UI or infrastructure."
                  }
                ]
              }
            }
          }
        }
      }
    }
  ]
}

In Python, the same kind of rule can be expressed with Import Linter in .importlinter:

[importlinter]
root_package = app

[importlinter:contract:domain-independence]
name = Domain must not depend on outer layers
type = forbidden
source_modules = app.domain
forbidden_modules = 
  app.ui    
  app.infrastructure

A check can miss a dependency pattern or be bypassed (for example, the Biome patterns above do not catch indirect imports through intermediary modules). The goal is to catch recurring mistakes, and the check can grow as new cases appear. Start with the smallest useful check. When the agent repeats a mistake, ask it to reproduce the failure and add a check that catches it.

If your agent can access past sessions you can even ask it to ”Review our sessions from the past month and suggest automatic verification additions that would prevent failure modes we have encountered”.

For coding agents, automatic checks also act as a fast and cheap feedback mechanism. An agent can make a change, run the verification, inspect a failure, fix it and run the verification again without a human or machine-reviewer ever having to spend time on commenting about a broken architectural constraint.

Make verification automatic and cheap

Now let's add verification commands to package.json that enforce our rules.

{
  "scripts": {
    "test": "vitest run",
    "test:llm": "vitest run --reporter=dot",
    "lint": "biome lint .",
    "lint:llm": "biome lint . --reporter=concise",
    "typecheck": "tsc --noEmit",
    "verify": "npm run lint && npm run typecheck && npm test",
    "verify:llm": "npm --silent run lint:llm && npm --silent run typecheck && npm --silent run test:llm"
  }
}

Humans have an innate need to see that something is actually happening so a lot of test runners fill the output with fancy animations and 500 rows of PASS. For agents, this provides very little value and consumes precious tokens and context we should be using on something more important. In this example we've set the :llm variants to use quieter output where available. Here, npm's --silent suppresses script announcements, Biome's concise reporter shortens diagnostics, and Vitest's dot reporter shows dots and a summary while retaining failure details.

Quiet modes for each tool and language work differently. I suggest asking your agent to help configure your tools so that they produce minimum output. Don’t overoptimize it and try to make some convoluted output swallowing subprocesses, just figure out what is the low-hanging fruit and “quiet enough”.

The important information (what failed and why) should surface fully. The && chain stops at the first failure, so a lint failure prevents typechecking and tests from running. Everything that passed should produce minimal output. Humans can still run verify for full output.

Then run:

npm --silent run verify:llm

The same setup works in Python. One option is Poe the Poet, which gives you a lightweight task runner in pyproject.toml:

[tool.poe.tasks] lint = "ruff check ."
architecture = "lint-imports"
typecheck = "mypy ."
test = "pytest -q"
verify = ["lint", "architecture", "typecheck", "test"]

The architecture task checks the .importlinter contract above, including indirect dependencies. Run the sequence with:

poe verify

I like to structure my verify scripts so that they are very lightweight and take at most a few seconds to run, so that the agent can run them often and get immediate feedback when it does something wrong. One reason the examples used Biome and Ruff instead of some older more entrenched alternatives is that you really want to prioritize speed of execution when constantly running these checks and the Rust-based Biome and Ruff excel at this.

Examples of what you could put in the fast verification:

  • fast unit tests
  • formatting and linting
  • architectural verification scripts using AST or dependency rules
  • UI design system checks that catch CSS values outside your allowed tokens
  • scripts forbidding strange agent mistakes like touching migration files post-deployment

Then agent instructions become something simple like:

After making changes, run `npm --silent run verify:llm`.

Fix any failures before considering the task complete.

However, we just learned that AGENTS.md instructions are not that reliable and this may not always run, so let's look for an alternative strategy. If your agent harness supports a completion hook, you can configure it to run verification when the agent finishes. This keeps the feedback loop very tight, but also introduces a lot of verification runs.

A useful addition on top of completion hooks is pre-commit.

Install:

pip install pre-commit

.pre-commit-config.yaml:

repos:
  - repo: local
    hooks:
     - id: verify
       name: verify
       entry: npm run--silent verify: llm 
       language: system
       pass_filenames: false
       always_run: true

Then:

pre-commit install

pre-commit gives us an easy way to configure scripts that run automatically at different stages of the Git workflow. Once the hook is installed in a checkout, this configuration runs our verification before commits, whether an agent or a human is doing the committing.

You can also configure a separate pre-push hook, which is a good place for checks that are too heavy to run on every commit, such as your full automated test suite. CI should still be the authoritative gate before anything gets merged, since local hooks can always be bypassed.

Conclusion

Good engineering practice states you should set up your environment so that doing the right thing is easy and making (or in this case repeating) mistakes is hard. Don't overthink it as there is no universal set of checks that will fix agentic engineering for good. Projects are different, agents are changing constantly, and plenty of engineering decisions cannot be reduced to a deterministic rule.

The habit I recommend is quite simple. The next time your agent makes a mistake and you are about to write a sterner instruction, remember the pink elephants and ask whether you could write a deterministic check instead.

Author
Engineers Corner
Negative instructions in AGENTS.md are not enforcement. Turn recurring coding-agent mistakes into fast deterministic checks instead.
Testimonials
Quote
Softlandia logo

name

I really appreciate Mikko! He improved LlamaIndex's Qdrant integration by fixing critical issues in the QdrantVectorStore API—enhancing query accuracy, reliability and performance of LlamaIndex.

Jerry Liu

CEO & Co-founder, LlamaIndex

Mikko is awesome! He built a prompt support system for Guardrails AI back when OpenAI's API only supported basic text completion. His solution improved the quality of language model outputs.

Shreya Rajpal

CEO and Co-founder, Guardrails AI

Working with Softlandia was great! Mikko and Henrik built a Slack bot integrated with real-time RAG pipelines, delivering instant and accurate answers to questions. The bot was created during a live 2-hour session streamed on YouTube.

Zander Matheson

CEO & Co-founder, Bytewax

We love Olli-Pekka! He added support for dynamic Bearer Token authentication in the Qdrant client, enabling customers to integrate seamlessly with Azure and other platforms.

Andre Zayarni

CEO & Co-founder, Qdrant

Other cases