Harness Engineering Cheat Sheet

By the Syncfusion DevTutorials team · Last updated

One page to come back to: where each harness file lives in Claude Code, Codex and Cursor, the lab's budgets, hook events and guard files, lint formats, definition-of-done and long-task templates, the evaluator rubric, the architecture test, the gardening skill, a failure-to-fix table, a module index and a glossary.

How to Use This Page

This page puts every file, format and checklist from the course in one place.

The lessons explain why. This page shows what.

For example:

  • Where a skill lives in Claude Code, Codex and Cursor.
  • The exact definition of done for each stack.
  • A rules file, a skill and a rubric you can copy.

The examples use both course projects:

  • Taskboard = the React app. It uses pnpm.
  • Pricing API = the ASP.NET Core app. It uses dotnet.

Tool file paths and hook events change over time.

This page was checked on 2026-10-15.

Check each tool's docs before you rely on a path. The Codex subagent format is still a draft.

Simple lesson: look things up here, learn them in the lessons.

Where Each File Lives

Each tool reads the same kinds of files. Only the paths differ.

PartClaude CodeCodexCursor
RulesCLAUDE.md (add @AGENTS.md to share one file)AGENTS.md, read from the git root down. AGENTS.override.md wins in its folder.AGENTS.md, plus .cursor/rules/*.mdc
Skills.claude/skills/<name>/SKILL.md.agents/skills/<name>/SKILL.md.cursor/skills/<name>/SKILL.md
Hooks"hooks" in .claude/settings.json.codex/hooks.json or [hooks] in config.toml.cursor/hooks.json (camelCase events)
Permissions"permissions" allow and deny in .claude/settings.jsonapproval policy and sandbox mode in config.tomlapproval settings, and .cursor/cli.json for the CLI
MCP servers.mcp.json[mcp_servers.<id>] in .codex/config.toml.cursor/mcp.json
Subagents.claude/agents/<name>.md.codex/agents/<name>.toml.cursor/agents/<name>.md
Ignorepermissions.deny on readssandbox scope.cursorignore
Starter files/init/initsettings UI for rules

Codex loads project hooks only when you trust the .codex/ folder.

Click a tab to see one part in your tool.

  • Path: where the file goes.
  • Note: the extra detail for that tool.

Simple lesson: same parts, different paths.

Lab Numbers

The Harness Lab uses a few fixed numbers. They are the lab's simplified limits, not real tool settings.

NameValueWhat it means
Root rules budget1200 tokensThe lab agent keeps this much of the root rules file. Real tools usually load the whole file, but long files dilute attention.
Doc budget800 tokensThe same limit for each docs/ file a pointer loads.
Skill match2 shared wordsThe lab's simple word match. Real tools let the model read each description and decide.
Session budget14 stepsThe steps one lab session can use. Real sessions end when you close them, hit a limit, or get compacted.
Session count5The sessions in one long build.

What real tools do instead:

  • Rules file: usually loaded in full. Codex caps the size of its AGENTS.md files, by default at 32 KiB.
  • Long chats: usually compacted into a summary. The rules file is normally kept. Details of the work are what get lost.

Simple lesson: the lab makes the limits easy to see. Real tools hide them, but the same habits help.

Hook Events

A hook runs at a set moment. The course uses two moments.

MomentClaude Code and CodexCursor
Before a tool call (can block it)PreToolUse with a matcherpreToolUse for edits, beforeShellExecution for commands
When the agent wants to stopStopstop

The matcher names the tools a hook watches:

WatchesClaude CodeCodex
File editsEdit|Write|MultiEditapply_patch|Edit|Write
Shell commandsBashBash|shell

A hook blocks when it exits with code 2. Its message goes back to the agent.

So write the message like a lint message. Say what is wrong and what to do instead.

Simple lesson: a hook is a wall that runs in code.

Guard Files

Here is one guard plan in each tool's file shape.

The plan has four guards:

  • block a skipped test
  • protect .env.production
  • block destructive commands
  • run the tests before stop

Claude Code, .claude/settings.json:

{
  "hooks": {
    "PreToolUse": [
      { "matcher": "Edit|Write|MultiEdit",
        "hooks": [
          { "type": "command", "command": "scripts/hooks/block-skip.sh" },
          { "type": "command", "command": "scripts/hooks/protect.sh .env.production" } ] },
      { "matcher": "Bash",
        "hooks": [{ "type": "command", "command": "scripts/hooks/block-destructive.sh" }] }
    ],
    "Stop": [{ "hooks": [{ "type": "command", "command": "scripts/hooks/require-tests.sh" }] }]
  },
  "permissions": { "allow": [], "deny": ["Bash(rm:*)", "Edit(.env.production)"] }
}

Codex, .codex/hooks.json. Same shape, other matchers. Codex has no deny list here, so hooks do that job.

{
  "hooks": {
    "PreToolUse": [
      { "matcher": "apply_patch|Edit|Write",
        "hooks": [
          { "type": "command", "command": "scripts/hooks/block-skip.sh" },
          { "type": "command", "command": "scripts/hooks/protect.sh .env.production" } ] },
      { "matcher": "Bash|shell",
        "hooks": [{ "type": "command", "command": "scripts/hooks/block-destructive.sh" }] }
    ],
    "Stop": [{ "hooks": [{ "type": "command", "command": "scripts/hooks/require-tests.sh" }] }]
  }
}

Cursor, .cursor/hooks.json and .cursor/cli.json:

{
  "version": 1,
  "hooks": {
    "preToolUse": [
      { "command": "scripts/hooks/block-skip.sh", "matcher": "Write" },
      { "command": "scripts/hooks/protect.sh .env.production", "matcher": "Write" }
    ],
    "beforeShellExecution": [{ "command": "scripts/hooks/block-destructive.sh" }],
    "stop": [{ "command": "scripts/hooks/require-tests.sh" }]
  }
}
{ "permissions": { "allow": [], "deny": ["Shell(rm)", "Write(.env.production)"] } }

For ASP.NET Core, protect Pricing.Api/appsettings.Production.json instead.

Simple lesson: one plan, three file shapes.

Rules File Template

Keep the root file short. It is a map into docs/.

# Rules for agents (map)

Short on purpose. Details live in docs/.

## Always
- Read the failing test and the file you plan to change before you edit anything.
- Run `pnpm test` after every change, and before you say you are done.

## Where things are
- Conventions: docs/conventions.md
- Ticket statuses: docs/statuses.md

## Done and never
- Definition of done: `pnpm test` and `pnpm lint` both pass. Run them before you say you are done.
- Never edit `.env.production`. Never switch off a test. Never delete files.

For ASP.NET Core, use dotnet test and dotnet build -warnaserror.

Simple lesson: exact commands, exact files, pointers for the rest.

Definition of Done

Each line names exact commands. The agent must run them before it says done.

StackKind of taskDone line
ReactOne sessionDefinition of done: `pnpm test` and `pnpm lint` both pass. Run them before you say you are done.
ASP.NET CoreOne sessionDefinition of done: `dotnet test` and `dotnet build -warnaserror` both pass. Run them before you say you are done.
ReactLong taskFlip "passes" to true only after `pnpm test` and `pnpm e2e` both pass.
ASP.NET CoreLong taskFlip "passes" to true only after `dotnet test` and `dotnet test --filter Category=E2E` both pass.

Simple lesson: done means the commands ran and passed.

Skill Template

A skill is a recipe for one repeat job.

---
name: add-ticket-status
description: Use when someone asks to add, create or need a new ticket status.
---

1. Add the status and its label to src/lib/status.ts.
2. Add a test case for it in src/lib/status.test.ts.
3. Add a row for it to docs/statuses.md.
4. Run `pnpm test` and make sure it passes.
  • Description: says when the skill loads. Use the words people use in requests.
  • Steps: say how to do the job.

Simple lesson: the description picks the skill, the steps run it.

Lint Messages That Teach

A lint message reaches the agent. So make it teach the fix.

React, in eslint.config.js:

"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." }]

ASP.NET Core, in Pricing.Api/BannedSymbols.txt. One symbol;message per line:

P:System.DateTime.Now;Do not read the clock directly. Use TimeProvider.System.GetUtcNow(), so tests can control the time.

A teaching message has three parts:

  • What is wrong.
  • What to use instead.
  • Why.

Simple lesson: "Not allowed." teaches nothing.

Long-Task Pack

Four files carry a long task across sessions.

init.sh starts the app the same way every session.

# init.sh (React)
#!/usr/bin/env bash
# Start the dev environment the same way every session.
set -e
pnpm install --frozen-lockfile
pnpm dev --port 5173 &
# init.sh (ASP.NET Core)
#!/usr/bin/env bash
set -e
dotnet restore
dotnet run --project Pricing.Api --urls http://localhost:5080 &

feature_list.json lists every feature. Each one starts as false.

A team may add an optional "owner" field.

[
  { "id": "filter", "description": "The user picks a status and only those tickets show.", "passes": false },
  { "id": "search", "description": "Typing in the search box shows only tickets whose title matches.", "passes": false }
]

progress.md is the handoff note.

# Progress

One entry per session. Newest at the bottom.

Add these lines to the rules file:

## Long tasks
- At the start of every session, run `bash init.sh`, then read progress.md and run `git log --oneline -5`.
- Pick the next item from feature_list.json where "passes" is false. Work on one feature per session.
- Never change a feature's description in feature_list.json. Only flip "passes".
- Flip "passes" to true only after `pnpm test` and `pnpm e2e` both pass.
- Before you stop: run `pnpm test`, commit, and append what you did and what is next to progress.md.

Simple lesson: start the same, do one thing, leave a note.

Evaluator Rubric Template

The evaluator is a separate agent. It did not write the code.

# Review rubric

You are the evaluator. You did not write this code. Answer PASS or FAIL.
FAIL if any line below is false.

- The unit tests pass.
- The end-to-end check passes in the running app.
- No test was skipped or switched off.
- The feature description in feature_list.json is unchanged.
- Only files for this feature changed; no unrelated edits.

Save it as evaluator/rubric.md. Every line is something a checker can see.

Simple lesson: a judge needs a checklist, not a feeling.

Architecture Test Template

An architecture test fails when one layer uses a layer it must not use.

// src/architecture.test.ts (lab helper; in a real repo use dependency-cruiser or eslint-plugin-boundaries)
forbidImport("src/components", "src/db", "Components must not read the database. Go through src/services instead.");
// Pricing.Tests/ArchitectureTests.cs (lab helper; in a real repo use NetArchTest or ArchUnitNET)
[Fact]
public void ControllersDoNotUseData() =>
    Arch.ForbidDependency("Pricing.Api.Controllers", "Pricing.Api.Data", "Controllers must not use the database directly. Go through Pricing.Api.Services instead.");

Simple lesson: the message names the right way through the layers.

Subagent and MCP Templates

A subagent keeps noisy work out of the main context. An MCP server gives the agent real facts.

# .claude/agents/log-searcher.md (Cursor: .cursor/agents/log-searcher.md)
---
name: log-searcher
description: Searches the log files and returns only the lines that explain an error.
tools: Read, Grep
---

Read logs/*.log. Return the cause in at most 5 lines. Never edit files.
# .codex/agents/log-searcher.toml (draft format, check the docs)
name = "log-searcher"
description = "Searches the log files and returns only the lines that explain an error."
instructions = "Read logs/*.log. Return the cause in at most 5 lines."
{ "mcpServers": { "orders": { "command": "npx", "args": ["-y", "orders-api-mcp"] } } }

That JSON goes in .mcp.json (Claude Code) or .cursor/mcp.json (Cursor). Codex uses .codex/config.toml:

[mcp_servers.orders]
command = "npx"
args = ["-y", "orders-api-mcp"]

Name both in the rules file:

- For any search through logs/, use the log-searcher subagent. Do not read the logs yourself.
- Look up orders API URLs with the orders MCP server. Never guess a URL.

Simple lesson: a file alone is not enough. The rules must name it.

Gardening Skill Template

Docs go stale. A gardening skill finds them and reports them.

---
name: garden-docs
description: Use when someone asks to clean up, find or fix stale docs that no longer match the code.
---

1. List every file in docs/.
2. For each file path a doc mentions, check that the file exists.
3. For each command a doc mentions, check it is a real script in package.json or a real dotnet command.
4. Do not delete or rewrite docs. Report each stale doc and why, as one pull request description.

Simple lesson: report stale docs, never delete them.

Failure to Fix Layer

Find what you see. Then fix the gap in the cheapest layer that works.

What you seeGapLayer that fixes it
Runs the wrong test commandguideone line in the rules file
Ignores half of a long rules fileguidecut it to a map, move details to docs/
Does a repeat job a different way each timeguidea skill with a clear description
Says "done" while a team rule is brokensensorlint in the definition of done
Sees an error but cannot fix itsensora message that names the fix
Switches off a test, deletes files, edits productionwallhook or permission
Repeats work in a new sessionguideprogress.md and the handoff lines
Says the app is done too earlyguidefeature_list.json with every item false
Edits a description to make the job easierguide + sensora "never change a description" line, and an evaluator check
Leaves a broken build at the end of a sessionguideone feature per session, and a clean exit line
Unit tests green, but the feature is never turned onsensore2e in the "flip passes" line
Marks its own work as perfectsensora separate evaluator with a rubric
Fills its context with logscontexta subagent
Guesses an API URLtoolan MCP server
UI layer reads the databasesensoran architecture test
Docs point to files that are gonesensora gardening skill on a schedule
Harness grows with dead linesupkeepablation, then remove what changes nothing

The cheapest layers come first: rules, then skill, then test, then hook.

Simple lesson: every mistake points to one missing part.

Module Index

All 15 lessons, in order.

PartLessonWhat you build
FoundationsCoding AgentsThe read, plan, act, observe loop, and a two-line rules file.
FoundationsFailure GallerySix failures, each sorted as a guide gap or a sensor gap.
FoundationsRules FilesA short rules file that works as a map into docs/.
FoundationsAgent SkillsA SKILL.md whose description loads it and whose steps guide it.
FoundationsTests and LintersA definition of done, and lint messages that name the fix.
FoundationsHooks and PermissionsGuards the agent cannot pass, and your v1 harness export.
Long-Running WorkContext WindowsA reset with a progress.md handoff instead of compaction.
Long-Running WorkFeature Listsinit.sh, a feature list, and rules that lock it.
Long-Running WorkIncremental SessionsOne feature per session, with a clean exit.
Long-Running WorkEnd-to-End TestingA check that uses the running app, front and back.
Long-Running WorkEvaluator AgentsA separate judge with a calibrated rubric.
Scale and MaintainSubagents and MCPA small main context, and no guessed URLs.
Scale and MaintainArchitecture TestsLayer rules as tests, and a doc-gardening skill.
Scale and MaintainContinuous ImprovementThe incident loop, and ablation for a lean harness.
Scale and MaintainCapstone ProjectOne harness that passes every check, exported.

Simple lesson: each lesson adds one part to the same harness.

Before You Export

Check each part before you export a harness.

PartCheckLesson
Rules fileShort map, exact commands at the topRules Files
Definition of doneTests and lint, by exact commandTests and Linters
Lint messagesEach one names the fixTests and Linters
GuardsBlock skipped tests, deletes and production editsHooks and Permissions
Long-task packinit.sh, feature_list.json, progress.md, the long-task linesFeature Lists
End-to-end checkInside the "flip passes" lineEnd-to-End Testing
Architecture testFails with a teaching messageArchitecture Tests
Subagent and MCPBoth named in the rules fileSubagents and MCP

Before you add a new part, write down the incident that made you want it.

No incident? You probably do not need the part yet.

Simple lesson: every part earns its place.

Day 1 in Your Own Repo

From Rules Files and Hooks and Permissions:

  1. Create the rules file. Claude Code and Codex have an /init command that drafts one. In Cursor, write it yourself or ask the agent to draft it.
  2. Trim it to a short map that points into docs/.
  3. Add the exact test command, and when to run it.
  4. Add one "never edit" line for the file that matters most.
  5. Add one or two guards: no deletes, no switched-off tests.
  6. Commit the files, then give the agent one small real bug and read the diff.

Reviewing the Agent's Work

From Evaluator Agents:

  1. Read the whole diff. Too big to read? Ask for a smaller change.
  2. Check which files changed: unexpected files, protected files, test files.
  3. Look for tests switched off, deleted or weakened.
  4. Run the tests and the app yourself.
  5. Check that the feature list and progress.md match the diff.
  6. Turn each catch into a rubric line or a check.

Safety and Cost

From Hooks and Permissions, Subagents and MCP and Continuous Improvement:

Risk or costWhat to doWhy
Secrets in the repoKeep them out. Deny reads of env files.The agent can read what it can reach.
Orders hidden in files or web pagesTreat that text as data. Keep guards on.This is prompt injection.
Auto-approve modesUse them only in a sandbox or container.Nothing stops a bad call.
MCP serversInstall only trusted ones, with the least access.Each one runs with your access.
Evaluators, e2e runs, subagentsAdd them after a real failure shows the need.Each one costs time and money.
A small or solo repoStart with rules, the test command, one never line and one or two guards.More parts mean more upkeep.

Glossary

  • Ablation = remove one part at a time and run every check again.
  • Agent = a model in a loop. It reads, plans, acts with tools, and looks at the result.
  • Architecture test = a test that fails when one layer uses a layer it must not use.
  • Auto-approve mode = a mode that runs every tool call without asking. Use it only in a sandbox.
  • Calibrate = test a rubric on known samples until it agrees with a person.
  • CI = continuous integration: every check, run on each change, on a clean machine.
  • Clean exit = tests green, committed, nothing half-done, note updated.
  • Compaction = the tool replaces older chat with a short summary and carries on. The rules file is usually kept. Details get lost.
  • Context budget = in the lab, how much of the rules file the agent keeps.
  • Context rot = attention drops as the context fills up.
  • Context window = everything the model can see at one time.
  • Decoy = a request that should not load a skill.
  • Definition of done = the exact commands that must pass before the agent says done.
  • e2e = short for end-to-end.
  • End-to-end check = a test that uses the running app like a user or a client.
  • Evaluator = a separate agent that answers PASS or FAIL with a rubric.
  • Feature list = feature_list.json. Every feature, each with a "passes" flag.
  • Garbage collection = cleanup that runs again and again, for stale docs and dead code.
  • Generator = the agent that builds one feature, then asks for review.
  • Guard = a hook or a permission. It runs in code, before the tool acts.
  • Guide = a part that steers the agent before it acts.
  • Handoff = progress.md plus the git log, read at the start of each session.
  • Harness = everything around the model: rules, skills, hooks, checks and tools.
  • Hook = a script the tool runs at a set moment. Exit code 2 blocks.
  • Incident = one real mistake, logged so you can fix it once.
  • init.sh = a script that starts the app the same way every session.
  • Initializer phase = the first session in a real project. It writes the plan and the setup, not features.
  • Invariant = a rule that must always be true, checked by a test.
  • Lean harness = only the parts a check needs.
  • Lint = an automatic check of code style and patterns. A linter is the tool that runs it.
  • Load-bearing = a part whose removal makes a check fail.
  • MCP = Model Context Protocol. A way to give the agent a real tool, such as an API lookup.
  • Migration = a small script that updates the database tables (EF Core in .NET).
  • Permission = an allow or deny rule for a tool call. The tool enforces it.
  • Planner = the agent that turns a goal into feature_list.json.
  • Pointer = a rules line that names a docs file, so the agent can load it.
  • progress.md = the handoff file. Each session reads it first and writes to it last.
  • Prompt injection = text in a file or page that tries to give the agent orders.
  • Reset = start the next session fresh, with a handoff.
  • Rubric = the evaluator's checklist. Every line is something a checker can see.
  • Rules file = CLAUDE.md or AGENTS.md. The short map the agent reads every run.
  • Sensor = a check that runs after the agent acts.
  • Session budget = in the lab, the steps one session can use.
  • Skill = a SKILL.md with a description (when) and steps (how).
  • Subagent = a helper agent with its own context. It returns a short answer.
  • Token = a small piece of text the model reads, about 4 characters.
  • Wiring = the line that turns a feature on, such as a route in Program.cs.

Simple lesson: if a word is new, look it up here.

Further Reading

Back to: Coding Agents, to start the course again from the beginning.