# Tests and Linters

Course: [Harness Engineering](https://www.devtutorials.ai/learn/harness-engineering) · Section: Foundations
Source: https://www.devtutorials.ai/learn/harness-engineering/foundations/sensors

> Why an agent says "done" while a team rule is broken, how a definition of done makes it run the checks first, and how a lint message that names the fix lets the agent repair its own mistake. Includes sensors for testing, UI, planning, APIs and CI.

## What Tests and Linters Do for an Agent

A **sensor** is a check that runs after the agent acts. It looks at the result and says if something is wrong.

In this lesson, you will learn how to make the agent run its sensors.

You will also learn how to make their messages useful.

Tests are sensors. So are lint, type checks and builds.

**Lint** = an automatic check of code style and patterns. A **linter** is the tool that runs it, such as ESLint.

For example, the team has a rule for dates:

- React: components must use `formatDate()`, so every user sees the same date.
- ASP.NET Core: code must not read the clock with `DateTime.Now`, so tests can control the time.

The agent is asked to show a date. It takes the quick way, which breaks the rule.

The tests still pass. So it says "Done".

### A Real-World Example

Think about a car's dashboard.

A warning light that only says "!" is not much help.

You know something is wrong. You do not know what to do.

A light that says "Tyre pressure low, front left. Add air." tells you what to fix.

And a light only helps if it works before you drive. A light that wakes up after the trip is too late.

Agent sensors work the same way:

1. They must run **before** the agent says it is done.
2. Their message must say **what to do**, not just "wrong".

Click the button and watch the run in the Harness Lab.

- **The edit:** the agent uses a quick date call.
- **The tests:** they pass, so it says "Done".
- **Then:** open the **Terminal** tab and type `pnpm lint` (or `dotnet build -warnaserror`). The check fails. The agent never ran it.

**Simple lesson:** green tests do not mean every team rule holds.

## What You Will Learn

In this lesson, you will learn:

1. What a sensor is.
2. How to write a definition of done.
3. Why a vague check message leaves the agent stuck.
4. How to write a lint message that teaches the fix.
5. Where CI fits.

## Sensors Run Before "Done"

### Guides and Sensors

**Guide** = something that tells the agent what to do before it acts.

**Sensor** = something that checks the result after it acts.

The rules file can hold both. "Use `formatDate()`" is a guide.

"Run `pnpm lint` before done" turns the lint into a sensor the agent always uses.

### The Definition of Done

**Definition of done** = the list of checks that must all pass before the agent may say it is finished.

Without one, the agent decides by itself when it is done. It usually stops at the first green test run.

Watch the same job twice. First without the lint in "done", then with it:

Step through all five frames.

- **Frame 1:** the rules list only `pnpm test` before done.
- **Frame 2:** the agent's transcript. A red line breaks the rule. Then it says "Done".
- **Frame 3:** the lint, run after "done". It fails.
- **Frames 4 and 5:** a definition of done with both commands. Now the agent runs the lint and does not claim done.

### How to Write It

Write it in the rules file, in plain words, with exact commands in backticks:

```markdown
- Definition of done: `pnpm test` and `pnpm lint` both pass. Run them before you say you are done.
```

```markdown
- Definition of done: `dotnet test` and `dotnet build -warnaserror` both pass. Run them before you say you are done.
```

**The `-warnaserror` flag matters.**

Without it, a banned API is only a warning. The build still says "Build succeeded".

**Simple lesson:** list every check by its exact command, and say "before you say you are done".

## A Sensor That Does Not Teach

### Stuck on "Not allowed."

Now the lint is part of "done". The agent runs it and sees that it fails.

But the message only says "Not allowed."

Click the button and read the end of the run.

- **The lint line:** it says "Not allowed." and nothing more.
- **The last thought:** the agent knows something is wrong.
- **The finish:** it does not know what to use instead, so it gives up.

### Messages That Teach the Fix

OpenAI's team writes lint rules whose error text tells the agent how to fix the problem. The message is written for the agent, too.

A teaching message has three parts:

1. **What is wrong:** "Do not format dates in components."
2. **What to use instead:** "Use `formatDate()` from `src/lib/dates.ts`."
3. **Why:** "So every user sees the same date."

Watch the same agent with each message:

Step through all five frames.

- **Frame 1:** two lint files. Only the message is different.
- **Frame 2:** the exact error line the agent reads.
- **Frame 3:** what it does next. Vague: it gives up. Teaching: it edits the import.
- **Frame 4:** the full runs. Only the teaching side ends with the lint passing.
- **Frame 5:** the same thing in .NET, with `BannedSymbols.txt`.

### A Real Lint Rule

Here is a real rule in each format. Each bans one call, and its message names the fix.

```javascript
// eslint.config.js
export default [
  {
    files: ["src/components/**/*.tsx"],
    rules: {
      "no-restricted-syntax": ["error",
        { selector: "CallExpression[callee.property.name='toLocaleDateString']",
          message: "Do not format dates in components. Use formatDate() from src/lib/dates.ts, so every user sees the same date." },
      ],
    },
  },
];
```

The banned-API analyzer reads this file. It reports rule RS0030:

```text
# Pricing.Api/BannedSymbols.txt
P:System.DateTime.Now;Do not read the clock directly. Use TimeProvider.System.GetUtcNow(), so tests can control the time.
```

### Lab Exercise: Wire the Sensors Into Done

Make two changes in the lab:

1. In the rules file, add a definition of done with the lint (`pnpm lint` or `dotnet build -warnaserror`).
2. In the lint file (`eslint.config.js` or `Pricing.Api/BannedSymbols.txt`), rewrite the message so it names the fix.

Click the button, make both edits, then press **Check my harness**.

- **First try:** the agent still makes the quick mistake.
- **Then:** the lint fails, and the agent reads your message.
- **The check:** it must fix its own code on all 3 versions.

Here is what both edits do, on every job and both stacks:

Step through all four frames.

- **Frames 1 and 3:** the starting harness. Every job says "Done" with the rule broken.
- **Frames 2 and 4:** the two edits. Every job ends with lint and tests passing.

**The quick first try is not a bug in the lab.**

Real agents take shortcuts too.

A good harness does not hope the agent gets it right the first time. It makes sure the agent finds out, and knows how to fix it.

**Simple lesson:** run the sensor before done, and make its message say the fix.

## Sensors for Each Part of the Team

The same idea fits any check a tool can run: coverage, colour tokens, accessibility, API contract tests and plan checks.

The lab runs unit tests, lint and end-to-end checks. It has no CI server and no contract tests. Set those up in your own project.

**CI** (continuous integration) = the full set of checks, run on every change, on a clean machine.

**Keep "done" fast.** A slow or flaky check gets skipped. Put fast checks in the definition of done, and the slow ones in CI.

**Simple lesson:** fast checks before done, slow checks in CI.

## TL;DR

1. **Sensor** = a check that runs after the agent acts, such as tests, lint, type checks and builds.
2. **Definition of done** = the exact commands that must pass before the agent says done.
3. **Red build claimed as done** = the agent said done, but a check it never ran fails.
4. **Teaching message** = what is wrong, what to use instead, and why.
5. **ESLint `no-restricted-syntax`** = the React way to ban a call, with a message.
6. **`BannedSymbols.txt`** = the .NET way to ban an API. With `-warnaserror`, RS0030 fails the build.
7. **Lint** = an automatic check of code style and patterns.
8. **CI** = every check, run on each change, on a clean machine.

## Common mistakes

| Mistake | Wrong idea | Correct idea | What goes wrong |
|---|---|---|---|
| No definition of done | "It will run the checks it needs." | List every check in the rules file. | It stops at the first green test run. |
| Lint not in "done" | "The lint runs in CI anyway." | Run it before done, too. | The agent says done with a broken rule. |
| Vague messages | "Not allowed." | "Use `formatDate()` from `src/lib/dates.ts`." | The agent sees the error but cannot fix it. |
| Plain `dotnet build` | "The build will catch banned APIs." | Use `dotnet build -warnaserror`. | RS0030 is only a warning, and the build passes. |
| Only unit tests | "Tests pass, so it works." | Add lint, contract and CI checks. | Rules and contracts break without anyone noticing. |
| Slow checks in "done" | "Run everything every time." | Fast checks in "done", slow ones in CI. | The checks get skipped. |

## Check Your Understanding

## Exercises

These exercises practise the two sensor habits from this lesson.

Press **Check** when you are ready. Use **Show Hint** if you get stuck.

## What to Remember

- A sensor checks the result after the agent acts.
- A sensor must run before "done". Write a definition of done with exact commands.
- In .NET, use `dotnet build -warnaserror`, so a banned API fails the build.
- A sensor must teach: what is wrong, what to use instead, and why.
- Real formats work: ESLint `no-restricted-syntax` with a message, or .NET `BannedSymbols.txt`.
- Keep the checks in "done" fast. Slow ones go in CI.

## Further Reading

- [Harness engineering: leveraging Codex in an agent-first world, by OpenAI](https://openai.com/index/harness-engineering/): custom lints whose error messages tell the agent how to fix the problem.
- [Harness engineering for coding agent users, on martinfowler.com](https://martinfowler.com/articles/harness-engineering.html): sensors as feedback that the agent can act on.
- [awesome-harness-engineering, by walkinglabs](https://github.com/walkinglabs/awesome-harness-engineering): evals, checks and guardrails.

Next: **Hooks and Permissions**. You will turn "never" rules into walls the agent cannot get past.
