← Course page Harness Engineering
0/8 modules

Why Capable Agents Still Fail

Module 1 · AI Agents and Harness Engineering

🔑 Key Thesis

Model capability does not equal execution reliability. When a task goes beyond a single simple prompt, the bottleneck is almost always the harness (the agent's environment), not the model itself.

Metaphor: "It's the saddle, not the horse."
The smartest horse stumbles on an uneven track without proper gear. The same model—a test winner—will fail repeatedly on real tasks if the environment isn't set up.

🧪 Controlled Experiment

The lecture shows the same project—building a 2D game from scratch—with two different approaches:

❌ Without harness

  • Time: ~20 minutes
  • Cost: ~$9
  • Result: broken code
  • Agent independently inferred conventions, forgot them, violated them

✅ With full harness

  • Time: ~6 hours
  • Cost: ~$200
  • Result: playable game
  • Harness guided the agent at every step
30× cost difference—and that's the gap between "broken" and "working." More expensive doesn't mean smarter; more expensive means the agent didn't go in circles.

🧱 5 Defensive Layers: Failure Model

Every agent failure can be mapped to one of five layers. Diagnose the layer—and you'll know what exactly to fix.

Layer 1 — Vague Task Specification

There is no explicit, verifiable Definition of Done. The agent finishes when it feels the task is done—without an objective completion criterion.

Sign: "Implement authorization" instead of "POST /login returns 200 with a token; test test_login_ok passes."

Layer 2 — Unknown Conventions / Architecture

The project has implicit rules that cannot be inferred from the local file context. The agent violates them without knowing they exist.

Sign: Agent writes syntactically correct code that violates hidden architectural agreements.

Layer 3 — Incomplete Dev Environment

It's unclear how to build, run, and test the project. Environment variables, dependencies, and configuration are undocumented.

Sign: Agent runs tests "however it happens"—they pass randomly or fail for external reasons.

Layer 4 — Missing Verification Mechanisms

Gate commands (linters, tests, convention checks) are either absent or the agent doesn't run them before shipping.

Sign: Agent reports "done," but no automatic check was ever run.

Layer 5 — Context Loss Between Sessions

In every new session, the agent re-infers project knowledge from scratch. Decisions made yesterday are unknown today.

Sign: Agent asks the same questions or makes the same inferences over and over.

📏 Verification Gap

Verification gap — the distance between "the agent said 'done'" and "the result is actually correct."

gap = false_done / N
N — number of tasks; false_done — how many times the agent reported "done" while external verification showed a failure. Measured on a real corpus of runs.
On a corpus of 21 runs in the notify experiment, the gap was 4/21 ≈ 19%. Almost every fifth task—a false "done."

🔄 Diagnostic Loop

The main mechanism for improving harness is the iterative diagnosis cycle:

  1. Reproduce the failure — repeat the conditions under which the agent erred.
  2. Map to a layer — which of the 5 layers caused it?
  3. Fix that layer — only that one, leave the rest alone.
  4. Rerun — make sure the failure no longer reproduces.
  5. Repeat 3–5× — until all systemic issues are resolved.
Diagnostic loop is not debugging the agent's code. It's debugging the environment. You don't fix the model; you fix the track.

🧩 Interactive 1: Map the Failure to a Layer

Real scenarios from the notify experiment (lesson 1). For each—choose the correct layer.

📋 Agent used import urllib.error in channels.py, violating the hidden convention "HTTP only through notify.http.post_json." It didn't know about this convention. Which layer?
Layer 1 — vague specification
Layer 2 — unknown conventions
Layer 3 — incomplete dev environment
Layer 4 — missing verification
🔍 Linter scripts/conventions.py existed in the repo, but the agent never ran it and shipped a violation, considering the task complete. Which layer?
Layer 2 — unknown conventions
Layer 3 — incomplete dev environment
Layer 4 — verification existed but wasn't run
Layer 5 — context loss
⚙️ Tests require NOTIFY_ENV=test, but this is documented nowhere. The agent ran them without the variable—they passed randomly (would have failed in CI). Which layer?
Layer 1 — vague specification
Layer 2 — unknown conventions
Layer 3 — incomplete dev environment
Layer 5 — context loss
🔁 Every new session, the agent re-infers the same project conventions from scratch, wasting time and sometimes reaching different conclusions. Which layer?
Layer 1 — vague specification
Layer 2 — unknown conventions
Layer 4 — missing verification
Layer 5 — context loss between sessions

🧮 Interactive 2: Verification Gap Calculator

Enter your run corpus parameters—and find out how large the gap is between "agent said done" and "actually correct."

Example from the notify experiment (lesson 1): on one specific task the gap was 1/1 = 100%—the agent reported "done," but the only check showed a failure. On a corpus of 21 runs: 4 false "done" → gap ≈ 19%.

🔭 What's Next?

Now you know why agents fail and how to classify failures. In the next module we'll break down what a harness is made of—specific tools and practices for each of the five layers.

Now you see: the problem is almost always the environment, not the model. You can map any agent failure to one of 5 defensive layers and know how to measure verification gap on your own task corpus.

🔧 What a Harness Is Made Of

5 subsystems—anatomy of an agent's engineering infrastructure

In Module 1 we dissected 5 failure layers—the places where agents break. Now let's break down what a well-built harness is filled with so those failures don't happen. Key definition:

Harness — everything in the engineering infrastructure outside the model weights. The model is a black box you don't touch. Harness is everything around it.

The repository IS the harness specification. If something isn't in the repo, it doesn't exist for the agent.

🍳 Kitchen Metaphor: 5 Subsystems

Imagine a professional kitchen. The cook (agent) is brilliant, but without the right infrastructure even the best chef produces chaos.

📋 1. Instructions — Recipe Shelf

AGENTS.md / CLAUDE.md — roughly 100 lines. Contains:

  • Project goal in one sentence
  • Stack and dependency versions
  • Bootstrap commands to get started
  • Hard constraints and conventions

Details go in docs/, read on demand. Don't try to cram everything into one file.

🔪 2. Tools — Knives

Adequate access to shell, CLI, network. Principle: least-privilege, but not "everything off".

An agent forbidden from pip install cannot install a dependency—it simply gets stuck. Tools must match the task.

🍳 3. Environment — Stove

Self-describing runtime: lock files, .python-version / .nvmrc, Docker / devcontainer.

If the agent cannot reproduce the environment on its own— every launch becomes a version lottery.

🗂 4. State — Prep Table

PROGRESS.md: done / in-progress / blocked. Written at the end of a session, read at the start of the next.

Bridge between sessions. Without it, the agent starts from scratch every time, spending tokens to re-discover what was already done.

✅ 5. Feedback — QC Window

Explicit verification commands: tests, types, lint—and a single entry point:

make check
or python check.py — one call runs all checks

Without explicit Feedback the agent doesn't know if the result is ready. It says "done"— and is wrong.

⚡ Order of Attack: Feedback First

When building a harness from scratch—don't start with the most obvious. Start with what gives maximum ROI at minimum cost:

  1. Feedback — one verification script. The agent finally understands "done" vs "broken."
  2. Instructions — a brief AGENTS.md. Project context from the first seconds.
  3. StatePROGRESS.md. Memory between sessions.
  4. Environment — lock files and .python-version.
  5. Tools — permissions, CLI access. Usually already there, needs tuning.
Feedback is the cheapest subsystem to implement and the most expensive to lack. Even one test is better than none.

🔗 Linking Two Lenses: Failure Layers → Subsystems

In Module 1 you learned to diagnose by mapping a failure to one of 5 layers. Now—fix: each fix lands in one of 5 subsystems.

Failure Layer (M1) Harness Subsystem
L1 — no task context 📋 Instructions
L2 — conventions violated 📋 Instructions
L3 — can't reproduce environment 🍳 Environment + 🔪 Tools
L4 — doesn't verify result ✅ Feedback
L5 — loses context between sessions 🗂 State

📊 Interactive 1: Harness Effect on a Real Project

Case study: TypeScript + React, ~20,000 lines, GPT-4o model. The model did not change. Harness subsystems were added—and results grew.

From 20% to 80–100% successful runs—without changing the model. This is harness engineering: the environment makes the model smarter than it "is" on its own.

🧩 Interactive 2: Failure Layer → Which Subsystem Fixes It?

Three scenarios—determine which harness subsystem needs investment.

❶ "Agent sends a pull request marked 'done,' but tests fail—it didn't know." Which harness subsystem is not set up?
📋 Instructions
✅ Feedback
🗂 State
🍳 Environment
❷ "Every new session the agent asks again: 'What have we done? Which files should I not touch?'—spending 10 minutes on this." What's missing?
🔪 Tools
✅ Feedback
🗂 State
🍳 Environment
❸ "The convention 'all errors must inherit NotifyError' is documented nowhere. The agent creates exceptions arbitrarily." What needs to be added?
📋 Instructions
✅ Feedback
🗂 State
🍳 Environment

🔬 Interactive 3: Ablation Calculator

Isometric model control — experimental method: hold the model constant, disable (ablate) one subsystem at a time, measure the drop in success rate. This tells you which subsystem matters most for your specific project.

The biggest drop when disabling a subsystem → invest harness work there first. Don't guess—measure.
Now you know the 5 harness subsystems: Instructions, Tools, Environment, State, Feedback. You can diagnose failure through the layer (M1) and route the fix to the right subsystem. Start with Feedback—cheap, high ROI. Next—why the repository must become a system of record (single source of truth about the project).

🗄️ The Repository as a System of Record

Module 3 · Harness Engineering—only what lands in the repo exists for the agent

Single Source of Truth for the Agent

The agent operates in an isolated information bubble: it sees the system prompt, the task text, repository files, and tool output. Everything else—Slack threads, Jira tickets, Confluence pages, verbal agreements—does not exist for it.

🔑 Central Thesis of This Module

"Information not in the repository does not exist for the agent."

This means: the repo must become a system of record—an authoritative source for decisions, constraints, current project state, and verification standards. Not a secondary archive, but the single place where the agent (and any new team member) looks for the truth.

Compare with a database: a row you didn't write is lost forever. Knowledge that "everyone knows" but no one ever committed to a file—is lost for the agent forever.

Cold-Start Test—Five Questions

The best way to check a repository is to imagine a fresh agent session walking into it, remembering nothing from past conversations. Can the repo answer all five questions?

❶ What is this system?

Purpose, domain, primary users. → README.md

❷ How is it structured?

Architecture, modules, interfaces. → ARCHITECTURE.md

❸ How do I run it?

Dependencies, commands, env variables. → CLAUDE.md

❹ How do I verify it?

Tests, lint, acceptance criteria. → AGENTS.md

❺ What is the current state?

What's in progress, what's blocked, what decisions have been made. → PROGRESS.md

A gap in any of the five = a knowledge gap in the repo. The agent will either get stuck or invent an answer—and be wrong.

🧮 Interactive 1 — Cold-Start Scorer

Score your repository for each question (0 = no info, 1 = exhaustive answer). The calculator will show the total score and approximate KVG (Knowledge Visibility Gap).

📊 Real Measurements (harness engineering course):
  • Repo with AGENTS.md + PROGRESS.md: 4.7 / 5, KVG 6%
  • Basic repo (code only): 3.2 / 5, KVG 36%

Goal: KVG < 10%. At KVG > 30% the agent systematically hallucinates critical details.

Three Repository Health Metrics

KVG — Knowledge Visibility Gap

The share of project-critical decisions and constraints living outside the repository: in people's heads, Slack, email.

KVG = (decisions_outside_repo / all_critical_decisions) × 100%
Goal: KVG < 10%. Higher—and the agent systematically makes decisions without needed context.

Discovery Cost — Cost of Discovery

How many tokens of the agent's context budget are spent to find what it needs, even if it exists in the repo. Information may exist but be buried in a giant file or scattered across dozens of folders.

Principle of "knowledge near code": ARCHITECTURE.md and CONSTRAINTS.md in every module → agent finds context in 1 step. One mega-document → 10+ search steps.

Knowledge Decay Rate — Speed of Staleness

The share of documents drifting from the code over time. Code evolves— documentation often stays in place.

Stale documentation is worse than missing documentation. When documentation is absent—the agent stops and asks. When it exists but is wrong— the agent confidently goes down the wrong path. Mis-route costs more than stopping.

Solution: place docs next to the code they describe (src/payments/ARCHITECTURE.md, not docs/legacy/payments.md). Then a PR changing code inevitably passes the documentation—and the reviewer notices the mismatch.

ACID for Agent State Management

ACID principles from databases apply to the git repository as a store of agent work state.

A — Atomicity

Each logical step = one git commit. Incomplete work goes to git stash, not the working directory. Rollback is always possible.

git add -p          # only needed changes
git commit -m "feat: add retry logic"
git stash           # if you need to interrupt
C — Consistency

Verification (tests / lint) gates every commit. Broken states don't enter history. Pre-commit hook is the simplest way.

# .pre-commit-config.yaml
repos:
  - repo: local
    hooks:
      - id: run-tests
        entry: pytest -q
I — Isolation

Parallel agents use separate branches or git worktree. State files are named per agent to avoid conflicts.

git worktree add ../agent-b feature/b
# agent A: main
# agent B: ../agent-b (isolated)
D — Durability

Cross-session knowledge lives in tracked repo files, not chat history. Chat history disappears when a new session starts.

PROGRESS.md      ✓ survives session
DECISIONS.md     ✓ survives session
chat history     ✗ disappears
git init → Atomicity (commit units) + Isolation (branches/worktree)
The first command in a new project automatically enables two of the four ACID dimensions. Consistency comes from pre-commit hook; Durability—from PROGRESS.md in the first commit.

Knowledge Near Code

One mega-document in the root is an antipattern. As the repo grows, the agent spends its entire budget searching for the needed paragraph. Rule:

  • Every module / service / library—its own ARCHITECTURE.md and CONSTRAINTS.md
  • Root CLAUDE.md — navigation and run/check commands only
  • Root PROGRESS.md — live state: what's in progress, what's blocked, what decisions were made today
project/
├── CLAUDE.md              # navigation + commands
├── PROGRESS.md            # current state
├── src/
│   ├── payments/
│   │   ├── ARCHITECTURE.md   ← agent reads immediately on entering module
│   │   ├── CONSTRAINTS.md    ← constraints next to code
│   │   └── *.py
│   └── auth/
│       ├── ARCHITECTURE.md
│       └── *.py
└── tests/
    └── AGENTS.md          # test acceptance criteria

🧠 Quiz

git init on a project automatically raises which two ACID dimensions?
Atomicity + Isolation
Consistency + Durability
Durability only
None of the four
Why is stale documentation worse than its absence?
Takes up extra disk space
It actively misleads the agent (mis-route), creating false confidence
No difference—absence and staleness are equally bad
It slows down git operations
Now you know: the agent only sees what's in the repository. The Cold-Start Test (5 questions) reveals knowledge gaps. KVG < 10% is the goal. Discovery Cost drops when documentation lives next to code. Stale docs actively harm—place them next to the code they describe. ACID git principles (Atomicity + Isolation "out of the box") make agent state atomic, consistent, and reproducible between sessions.

Next—why one giant instruction file starts to hurt and how to properly structure agent context.

M4 📄 Why One Giant Instruction File Fails

Instruction Bloat, Lost-in-the-Middle, and the Progressive Disclosure Principle

In M1–M3 we established that the presence of a rule in AGENTS.md is critical. Now let's look at the flip side: if you keep piling everything into one file, it starts to hurt. A file of ~300+ lines slows the agent, reduces accuracy, and blurs priorities.

🗻 The Instruction Bloat Phenomenon

Instruction Bloat — when the instruction file grows so large that it takes up a significant share of the context window. Empirical rule: an instruction file > ~10–15% of the context window starts pushing out working task information.

Mechanics: modern models have a ~128–200 K token window. System prompt, chat history, file code, diff—everything competes for that space. A 600-line AGENTS.md ≈ 4–6 K tokens. On a task with a large diff or multiple files, instructions literally squeeze working content to the periphery of the window.
bloat_ratio = len_instructions / context_window · 100%
If bloat_ratio > 10–15% — time to split the file. At 128 K window that's ≈ 13–19 K tokens (~1,900–2,700 words, ~150–200 lines of dense text).

📍 Lost in the Middle (Liu et al., 2023)

Research showed that LLMs better use information at the beginning and end of long context. Information in the middle "sinks." Practical conclusion: critical rules cannot be buried in the middle of a long file.

Honest empirical angle. The lost-in-the-middle effect was tested by hand (15 Sonnet runs, three positions of a critical rule in a 288-line AGENTS.md): result — 0 out of 15 violations. The positional effect did not appear at ~300 lines with modern Sonnet.

To get a signal you need: ~1,000+ lines, a rule that overrides default behavior (rather than reinforcing it), a weaker model, or a competing false alternative in the middle.

Conclusion: the claim about rule position is calibrated for bloated files (~600+ lines). At ~300 lines modern Sonnet does not lose a rule in any position. But the M1 takeaway—that presence of a rule matters—still holds.

📊 Signal-to-Noise Ratio of Instructions

Instruction SNR — the share of file items relevant to the current task. If the agent is adding a new notification channel, and AGENTS.md has 37 items of which only 30 relate to this task, SNR = 81%. If it's running tests, only 18–19 items are relevant—SNR = 50%.

Low SNR is noise: the agent spends attention on irrelevant rules and may apply them inappropriately ("webhook rule unexpectedly applies to refactoring").

🔢 Interactive 1 — SNR Calculator

Enter the number of relevant items and total items in the file— get the SNR. In Lesson 4, items "always relevant" across all 5 task types were 9 out of 37—this is the skeleton of the future routing file (skeleton SNR = 100%).

📊 Interactive 2 — SNR by Task Type

The same AGENTS.md yields different SNR depending on task type. Average across 5 types ≈ 48%—almost half the instructions in any task are noise.

Lesson 4 data: T1 — add channel, T2 — debug errors, T3 — write tests, T4 — refactoring, T5 — update dependencies. Average SNR ≈ 48%.

🗺️ Routing File: Table Instead of Prose

Routing File — a short file (50–200 lines) that does not contain details but points to thematic documents. Essentially a table: condition → document.

Example Routing File structure:
## Conditions → documentation

| Condition                        | Document                     |
|----------------------------------|------------------------------|
| Adding a new channel             | docs/channels.md             |
| Changing DB schema               | docs/db-migrations.md        |
| Writing or changing tests        | docs/testing-guide.md        |
| Deploy / CI                      | docs/deploy.md               |
| Dependency changes               | docs/deps-policy.md          |
| Any task (safety rules)          | docs/safety-critical.md      |

The main routing file itself—no longer than 50–80 lines. Details live only in thematic docs.

Scope thematic docs by code area (channels, db, deploy), not by topic (security, performance). The agent works with code—it knows what it's changing, but doesn't always know which "topic" it belongs to.

🔭 Progressive Disclosure

Progressive Disclosure — the principle of "overview now, details on demand." The routing file gives quick orientation; detailed docs load only when needed. The agent doesn't "read the whole textbook" before every task—it looks for the needed chapter.

❌ Monolith (antipattern)
  • AGENTS.md — 600 lines
  • All rules in one place
  • Agent reads everything in full
  • SNR ≈ 30–50% for any task
  • Critical rules drown in the middle
✅ Routing + thematic docs
  • AGENTS.md — 50–80 lines (table)
  • Details in separate files
  • Agent reads only the needed doc
  • SNR ≈ 80–100% for each doc
  • Safety rules in a separate file, always loaded

📖 Case Study: SaaS Team, 50 → 600 Lines → Refactoring

The team started with a 50-line AGENTS.md. Over a year, adding rules one by one, the file grew to 600 lines. Here's what happened:

  • Task success: 45% (agent applied rules to wrong contexts)
  • Safety rule compliance: 60% (critical rules ended up in the middle)
  • Team complaints: "agent ignores our deploy requirements"

After refactoring: routing file (80 lines) + 6 thematic documents:

  • Task success: 45% → 72%
  • Safety rule compliance: 60% → 95% (critical rules moved to a separate doc that's always loaded)
Key change—safety rules moved from the middle of a 600-line monolith into a separate docs/safety-critical.md file that the routing file explicitly points to for any task.

🧪 Interactive 3 — Check Your Understanding

Question 1. When is it time to refactor AGENTS.md into a routing file + thematic docs?
Immediately, always—routing file is better than any monolith
When file > ~150 lines OR frequent-task SNR < 50%
When file > 1,000 lines
Never—one file is always more convenient
Question 2. Was the "lost-in-the-middle" effect reproduced on a 288-line file with Sonnet (experiment, 15 runs)?
Yes—the rule in the middle was regularly ignored
Yes—the rule in the middle was violated in about 50% of cases
No—0/15 violations, rule position did not matter at this size
The experiment was not conducted—this is a theoretical claim
Now you can diagnose Instruction Bloat and Instruction SNR, know that "lost-in-the-middle" is calibrated for ~600+ lines and not reproduced at ~300 lines with modern Sonnet models, and can design a Routing File with Progressive Disclosure: a short overview table file + thematic docs scoped by code area.

Next—the session lifecycle: initialization and context continuity between agent runs.

Session Lifecycle

Initialization as a separate phase · Context continuity between sessions

In previous modules we covered how an agent picks tasks and builds a plan. Now—about what happens inside a single session and at the boundary between sessions: initialization and context handoff.

A. Initialization as a Separate Phase

Typical mistake: the agent immediately writes business code without laying the foundation. Tools aren't configured, tests don't run, paths aren't created—and the first import breaks everything. This is mixing Initialization and Implementation.

Mixing phases creates multi-criteria optimization: the agent simultaneously thinks about code and infrastructure, prioritizes visible progress—and leaves a fragile foundation.

Analogy: Foundation and Walls

Pouring foundation and building walls simultaneously is a guarantee of rework. First the foundation sets, then the walls. Same with an agent: first the environment is checked, then code is written.

Bootstrap Contract — 4 Mandatory Conditions

Initialization is considered complete when all four items are satisfied:

  1. Can run — environment starts without errors
  2. 🧪 Can test — at least one test passes green
  3. 📊 Progress is visible — there's a measurable artifact (file, log, DB row)
  4. 🔗 Can pick up — the next agent (or next session) understands what's done and what's next
Warm start (template infrastructure, ready-made Makefile / pyproject.toml / AGENTS.md) reduces initialization manyfold. An empty folder is the slowest option.

TTFV — Time To First Verification

Key metric of initialization efficiency. The faster the agent achieves the first green test, the lower the risk it will spend context on an incorrect foundation.

TTFV = t(first_green_test) - t(session_start)
Low TTFV → Bootstrap Contract fulfilled quickly → Implementation phase starts on solid ground.

🧩 Interactive: Bootstrap Contract

Check your understanding of the four Bootstrap Contract conditions.

What does NOT belong in the Bootstrap Contract?
Environment starts and runs without errors
Next session understands what's done and what's next
Beautiful README with coverage badges and build status
At least one test passes green

B. Context Between Sessions

Every new session starts with a clean context. Without structured handoff the agent reads state from scratch and risks making decisions already made earlier—differently.

Recovery cost

Tokens spent rebuilding the mental model at the start of a session. Without continuity artifacts this is reading all files sequentially hoping to understand "what's going on here."

With good PROGRESS.md and git checkpoints recovery cost approaches zero: the agent reads one file and immediately knows where it is.

Context Anxiety

Anthropic finding: Sonnet 4.5 near the context limit exhibits premature convergence—choosing simpler solutions, skipping verification, rushing to close the task. This is not a behavior bug; it's a rational response to resource pressure.

If the task is large—split into sessions before Context Anxiety hits, not after. The gap between what the agent knows and what's actually in the repository is Drift. It accumulates unnoticed.

Continuity Artifacts

📄 PROGRESS.md

What's done, what's temporary, why this particular decision was chosen. Preserves why, not just what.

📋 DECISIONS.md

Architectural decisions and their rationale. Prevents reinventing the wheel in the next session.

🔖 Git Checkpoints

Clean commits at the end of each significant phase. The next session can run git log --oneline and instantly understand the chronology.

Honest Takeaway: When Is PROGRESS.md Actually Needed?

PROGRESS.md is insurance, not acceleration. It is load-bearing only for constraints that code itself CANNOT express. Code-expressible state (helpers, patterns, interfaces) flows forward through code—the next session finds it by simply reading files.
  • Lesson 2 ablation: removing PROGRESS.md broke refactoring—a shim was marked "temporary, remove," but this marker wasn't in the code. Without the record the shim survived as dead weight.
  • Lesson 5: on a code-expressible task, arm without PROGRESS.md was cheaper on all metrics—the code itself carried the decisions.
Rule: write in PROGRESS.md only what code cannot express. Everything else—into code.

🧩 Interactive: Is PROGRESS.md Needed Here?

Three scenarios from real worklogs. For each—decide if PROGRESS.md is needed.

Scenario 1. Session 1 extracted helper _post_with_retry into channels.py; session 2 continues adding channels.
No — the decision is expressed in the code itself; the next session will find the helper by simply reading the file
Yes, mandatory — without a record the next agent won't understand why the helper is needed
Yes — better to duplicate in PROGRESS.md for safety
Scenario 2. A backward-compatibility shim is temporary—it must be removed after migration, but in code it looks permanent.
No — the shim can be found by filename
Yes — "temporariness" cannot be expressed in code; without a record the shim survives as dead weight (real case from lesson 2)
No — a TODO comment in code is enough
Scenario 3. We chose wire-key JSON "message" instead of "body" because the HTTP contract requires it—and there is NO test checking this.
No — the key name is visible in code
No — just add a test
Yes — non-obvious decision without code carrier; otherwise the next session will "clean up" and silently break the contract

📊 Cost of Splitting: Marathon vs Multiple Sessions

Data from lesson 5: the same 3-channel task was solved three ways. Metric—total tokens.

Loading chart…
Split-vs-marathon rule: split when one session would overflow context—not "always split." At small scale marathon (one subagent for everything) is 3–4× cheaper than splitting. Every handoff costs tokens: writing artifact + reading it in the next session.
When to split ✂️
  • Task clearly exceeds context window
  • Different agent specializations needed
  • Independent subtasks for parallelism
When marathon 🏃
  • Task fits in one session
  • High connectivity—agent carries full context in head
  • Savings on handoff artifacts are substantial
Separate Initialization and Implementation—Bootstrap Contract (run, test, progress, handoff) and low TTFV are signs of healthy initialization. PROGRESS.md is needed only for what code cannot express: temporary decisions, non-obvious choices without tests. Code-expressible state flows through code itself. Marathon is 3–4× cheaper than splitting on small tasks—split only when context actually overflows.

Next—why agents take on too many tasks at once and finish too few (WIP=1).

Overreach and Under-finish: Why WIP=1

Agents activate too many tasks per session and complete too few. Let's break the mechanics and the antidote.

Overreach: Too Many Tasks in Flight

The agent starts a session with an ambitious plan: "I'll implement 5 features at once." Each task activates—a branch is created, first lines of code written. But by the end of the session none have passed verification. This is overreach: scattered partial implementations instead of one completed feature.

📊 Case: REST API with 8 Features

No limits (WIP=∞)

  • Session 1: 5 features activated
  • ~800 lines, ~12 files
  • E2E pass: 20%
  • After 3 sessions ready: 3/8 features
  • Final VCR: 37.5%

WIP=1

  • Session 1: 1 feature activated
  • ~200 lines, ~4 files
  • E2E pass: 100%
  • After 4 sessions ready: 7/8 features
  • Final VCR: 87.5%

Under-finish and VCR

Under-finish — the share of activated tasks that failed verification despite generated code. Overreach directly causes under-finish: the more tasks in flight, the higher the chance each won't be brought to executable proof.

VCR = verified_passing / activated
VCR (Verified Completion Rate) — the share of tasks that passed executable verification. Block new activations while VCR < 1.0.

Proof of Completion: Executable Evidence

"By eye" doesn't count. A task is complete only when there is executable condition that can be run and passes.

❌ Not proof

  • "Code looks correct"
  • Code review passed
  • Linter found no errors
  • "I checked manually"

✅ Executable proof

  • pytest tests/test_feature.py -v → PASSED
  • curl -s /api/v1/item | jq '.id' → not null
  • assert result == expected in CI
  • Integration test run automatically

Little's Law: The Math of WIP

Little's Law—a fundamental result of queueing theory. For a stable system:

L = λ · W
L = WIP (tasks in flight) · λ = throughput (features/day) · W = average cycle time of one task. Hence: W = L / λ.

If λ = 1 feature/day and L = 5 (five tasks simultaneously), then W = 5 days per task. Keep L = 1 → W = 1 day → probability of error accumulation is minimal.

🧮 Interactive 1 — Cycle Time Calculator (Little's Law)

Little's Law: W = L / λ
Move sliders to calculate

📊 Interactive 2 — The Price of WIP=1 Discipline

Data from a real experiment (lesson 7, worklog): ~700 lines, 6 features, Sonnet. WIP=1 is more expensive in tool calls—but catches what the gate misses.

Tool calls: no limits vs WIP=1

⚠ Honest Empirical Takeaway

At small scale (~700 lines / 6 features / Sonnet) the VCR differential from the lecture did not reproduce—both modes gave 100% on the gate. What really differed: WIP=1 caught 2 test-invisible bugs that the no-limits mode shipped silently.

🐛 Test-invisible bugs from a real worklog

Bug 1 — wrong exception inheritance:

# No limits—shipped silently:
class DispatchNotFound(Exception): ...   # ← WRONG

# WIP=1 — caught during careful walkthrough:
class DispatchNotFound(NotifyError): ... # ← CORRECT

# Callers with "except NotifyError" miss
# DispatchNotFound — regex linter doesn't see inheritance.

Bug 2 — loss of config overrides on retry:

# retry() recreates Dispatcher.from_config() with fresh
# defaults — config overrides from the original send are lost.
# Test passed because it tested happy-path without overrides.
Real value of WIP=1 at small scale is not raw VCR, but three things:
1. Insurance against test-invisible gaps — agent focuses on one task and notices subtle mismatches.
2. Bisectable git history — one commit per feature, easy to roll back and localize issues.
3. Predictable cycle time — per Little's Law W = L/λ, lower WIP = fewer days per feature.

WIP=1 in Practice: Activation Rules

  1. One active task at a time. The next task starts only after the current one passes executable proof of completion.
  2. Block when VCR < 1.0. If the previous task didn't pass verification—don't activate a new one, finish the current first.
  3. Commit per feature. After passing verification—a commit with a clear message. This is both bisectable history and an explicit completion point.
  4. Proof of completion defined upfront. Before activating a task define the specific executable test—otherwise "done" remains subjective.

🧠 Quiz

When does WIP=1 really pay off?
Always and at any scale—VCR is always higher
When there are test-invisible conventions (which the gate doesn't catch) and/or larger scale
Never—it's just slower without visible benefit
Only on frontend where tests are harder to write
Little's Law: WIP doubled at the same throughput λ. What happened to cycle time W?
Unchanged—λ didn't change
Doubled
Halved—more tasks, faster completion
Quadrupled—nonlinear overload effect
Now you know: overreach → under-finish by the mechanics of Little's Law (W = L/λ). WIP=1 + executable proof of completion = predictable cycle and protection from test-invisible bugs. At small scale the main benefits are focus, bisectable history, and insurance against convention gaps—not just raw VCR. Next—features as data structures and multi-level validation.

📋 Feature Lists as Data Structures + Multi-Level Validation

Module 7 · AI Agents and Harness Engineering

In modules 1–6 we talked about what to check and how to build gates. Here—about where to store scope and why agents declare victory too early. Both questions are connected: poor feature data structure = blind gate = false victory.

A. Feature List — Data Structure, Not a Planner

Typical mistake: the feature list lives in chat, Trello, or the manager's head. In the harness approach the feature list is a repo artifact that automation operates on. It has a strict schema, is versioned, and serves as input for the verification pipeline.

🔷 Triplet — Mandatory Record

Each line in the feature list carries exactly three fields. Missing even one—the record is incomplete and cannot enter the pipeline:

{ description, verify_cmd, state }
description — what the system should do (behavior, not task); verify_cmd — command the harness executes to check; state — current state in the state machine.
Example complete record:
{
  "description": "POST /api/send returns 200 and writes message to DB",
  "verify_cmd":  "pytest tests/test_send.py::test_send_200 -x -q",
  "state":       "passing"
}

A record without verify_cmd is a wish, not a feature. A record without state is untracked.

🔄 Feature State Machine

Each feature passes through 4 states. Transition → passing happens only on successful verify_cmd result. State passing is irreversible: regression means a new bug, not a rollback.

  not_started ──→ active ──→ passing  (irreversible)
                    ↕
                 blocked
  • not_started — feature is in scope but work hasn't started
  • active — in progress
  • blocked — there's a blocker (waiting for dependency / decision)
  • passingverify_cmd returned exit 0 in CI; only this way
The agent cannot move a feature to passing itself. Only the harness—after executing verify_cmd. This is the State Passing Gate: proof, not self-assessment.

📌 SSoT — Single Source of Truth

All scope information comes from one feature list. It's a chain: source → derived → executable. Each link can drift—if PROGRESS.md diverges from code, the harness loses orientation.

SSoT discipline matters most on the most frequently edited artifact—usually PROGRESS.md, not the largest file. Fixing violation is not "delete duplicates" but explicitly mark the source and ensure derivation: every derived artifact is labeled "generated from X, do not edit manually."

📊 State Pressure — Readiness Metric

The number of features not in passing is State Pressure. Zero = project complete. This is a quantitative, objective readiness metric—unlike subjective "feels like it's done."

pressure = total_features − passing_features
Team goal—drive pressure to zero. Harness dashboard shows pressure in real time.

🧮 State Pressure Calculator

B. Why Agents Declare Victory Too Early

Agents judge by "the code I wrote looks correct," not "the system E2E satisfies the spec." This is a systematic error, reinforced by math.

📐 Calibration Bias

Guo et al. (ICML 2017) showed: modern neural networks are systematically overconfident—stated confidence exceeds actual accuracy. The model says "95% sure it's done" where real accuracy is 70%.

Practical conclusion: agent confidence ("looks done," "should work") is not an operational definition of "done." The operational definition is the result of verify_cmd.

📊 Worklog data: weak vs strict checking

Lesson 8 — 11 deliberately planted bugs. Two checking modes:

FPR by checking mode
What these numbers mean. Smoke (weak) only checks: "module imports + one call doesn't crash." This catches only complete crashes. Of 11 planted bugs—0 caught (FPR = 100%). Strict (unittest)—9% FPR (1 of 11 missed). Even strict has blind spots: bug F01—clear() call inside send(), but the test does one send and doesn't see degradation on subsequent calls. Conclusion: smoke as the only gate is operationally useless.

📊 Worklog data: false-done and gate blindness

Lesson 9 — 21 runs, calibration bias = 24% (5/21 false-done). Key covariate: gate coverage:

false-done % by gate coverage
Main module takeaway: bias ≠ model overconfidence per se—it's gate blindness infection. Where the gate covers everything → 0% false-done (0/16). Where the gate has a blind spot (rule invisible to tests) → 80% false-done (4/5). "Gate is green" = operational definition of "done." Expand the gate → reduce false victories.

🔒 Dual Verification-Validation Gateway

For victory to be honest, you need two independent gates:

L1 — Verification
"Does code implement the spec?"
unit tests, static analysis, contracts
L2 — Validation
"Does the system meet E2E requirements?"
integration, E2E scenarios, load

Both are mandatory. L1 without L2 = checked details, missed system. L2 without L1 = catching symptoms, not causes.

🏗️ 3-Level Validation

  1. Syntax / static analysis — lint, type checker, compilation. Fast, local, cheap. Always mandatory.
  2. Runtime behavior — tests execute, app starts, critical paths pass. Detects logic errors.
  3. System confirmation — E2E, integration, acceptance criteria. The only level that confirms the whole system works.

An agent can pass levels 1 and 2 and still fail level 3. This is not an exception—it's the norm for complex systems.

⚡ Completion Priority Constraint

Strict priority order:

functional → performance → style
Refactoring, optimization, and style improvements are forbidden until functionality is confirmed on all three levels. An agent doing refactoring before passing E2E violates this rule.
Classic trap: agent cleans code and improves architecture while a critical E2E scenario fails. Harness detects this through State Pressure—pressure doesn't drop even though work is "happening."

🎯 Quiz

Question 1: What moves a feature to passing state?
Agent self-assessment "looks done"
Only successful execution of its verification command
Code review
Deadline passed
Question 2: How many of 11 planted bugs did weak smoke checking catch (lesson 8)?
0 (FPR 100%)
All 11
9
5
Now you can: store features as Triplet (description + verify_cmd + state) in the repo, not in chat; track State Pressure as a quantitative readiness metric; understand that agent overconfidence is gate blindness, not model character; build Dual L1+L2 gate and follow the functional → performance → style priority.

Next—E2E, runtime observability, and clean session exit.

Module 8: E2E Testing, Runtime Observability, and Clean Session Exit

Course synthesis: calibrate harness by pressure, not by fashion

A. Only E2E Testing Changes the Result

Unit tests check each component in isolation. But an agent is a system where components interact. Inter-component failures are invisible to unit tests:

  • Interface mismatch — module A expects a string, B returns a dict: both tests are green, system is broken.
  • State propagation — an error in step 2 silently changes step 4's result; each step is tested independently and passes.
  • Environment dependencies — test mocks the filesystem, but in real environment the path is different.

🔺 3-Level Validation Architecture

  1. Level 1 — Syntax / Static Analysis
    "Code parses": linter, type-checker, python -m py_compile. Necessary but not sufficient. An agent can generate syntactically correct code that does the wrong thing.
  2. Level 2 — Runtime Behavior
    Tests execute, app starts, critical paths pass (unit + integration). Much better—but inter-component interactions remain in the blind spot.
  3. Level 3 — System Confirmation (E2E)
    Full user scenario: from input data to observable result. Only here is it checked that the system works as a whole.
An agent whose verification gate stops at level 1–2 will repeatedly report "done"—and repeatedly be wrong in production.

Anthropic Case: Retro Game Editor

Anthropic compared two approaches to the same task (identical prompt—develop a retro game editor):

🤖 Bare Single Agent
  • Time: ~20 minutes
  • Cost: $9
  • Result: broken editor
⚙️ Harness: planner + generator + evaluator
  • Time: ~6 hours
  • Cost: $200
  • Result: fully playable editor

30× in cost—and it's a justified investment: the cheap run produces a broken artifact, the expensive one—a working one. The evaluator agent in the harness performed E2E checks after each iteration.

📊 Cost Comparison: Bare Agent vs Harness

Run cost, $

B. Make Agent Runtime Observable

An agent without observability operates as a "black box": it reports its own state, and its self-assessment is systematically biased toward completion. As in M3 we talked about ACID: you can't trust the agent that a transaction is complete—you need an external observer.

🔍 Observability Tools

  • Leveled logs — structured records of every agent step. Allow retrospectively understanding what actually happened, not what the agent thought happened.
  • Process states — explicit statuses: pending / running / done / failed. No intermediate "almost done."
  • Health-checks — periodic automatic probes: "is the service responding?", "was the file created?", "does DB schema match?"
  • Side-effect probes — check not just return value, but what changed in the environment: filesystem, network, external service.
Observability turns "seems to work" into checkable signals. Only objective signals can serve as a basis for completion conclusions.

Practically: add to your check.py (from M2) not just unit tests, but a minimal E2E smoke: start the app, walk the critical path, ensure output matches expected. This is runtime observability in minimal form.

python check.py --e2e --smoke-only 2>&1 | tail -20
Run E2E smoke test from verification gate; last 20 lines show the critical path result.

C. Every Session Must Leave a Clean State

As in ACID-Durability (M3): a transaction is either fully applied or rolled back. An agent session is a transaction on the codebase. You cannot leave half-done state, a red gate, or undocumented decisions.

A bad session exit is technical debt paid at the next start: the new session spends time understanding what was done, what wasn't done, and why this or that decision was made. This is context loss, which we discussed in M5.

✅ Session Exit Checklist

Check each item before closing the session:

Done: 0 / 5

D. Course Synthesis — Meta-Takeaway

🧭 Main Meta-Takeaway: Harness Engineering Is Calibrated by Pressure

Across all eight modules ran one thread: the harness engineering methodology is calibrated for a mode where harness pressure is real.

At small scale (≤ ~700 lines, clean code, strong model like Sonnet, one session) many quantitative lecture claims do not reproduce:

  • M4: rule position in AGENTS.md doesn't change result
  • M5: PROGRESS file doesn't speed up work
  • M6: dedicated init yields fewer features than without it
  • M7: VCR differential is insignificant
  • M8: 45% quality differential is not observed

These are null results—they don't disprove the methodology. They speak to its boundary of applicability.

Two classes of things ALWAYS pay off—at any scale:
  1. Presence of key artifacts: verification gate + explicit verifiable Definition of Done. Even if the gate never turned red—it sets a contract the agent cannot silently violate.
  2. Working with test-invisible conventions: what the gate physically doesn't catch—is exactly where all false-done live. Style, architectural decisions, implicit expectations—these can't be checked automatically, but can be made explicit through documentation and checklists.
Practical rule: invest in harness where pressure is real— large or dirty code, weaker model, multi-session, multi-agent. Don't cargo-cult full harness onto toy projects.

🧪 Final Quiz: Course Synthesis

Main meta-takeaway of the course about harness engineering methodology?
Always apply full harness at any cost
Harness isn't needed with strong models
It's calibrated by scale: presence of gate + explicit DoD and working with test-invisible conventions always pay off, while quantitative lecture differentials reproduce only under real harness pressure
It's all about model size
What do unit tests systematically miss, requiring E2E?
Syntax errors
Inter-component failures: interface mismatch, state propagation, environment dependencies
Typos in strings
Code style
You've covered all 5 harness engineering layers (verification gate, context subsystem, initialization subsystem, runtime observability, E2E confirmation) and 5 harness framework subsystems. Now you can calibrate harness by scale: always set gate + explicit DoD, always work with test-invisible conventions— and build up the rest only where pressure is real.

Done 🎉

8 modules completed. Here's what's in your toolkit now:

  • See that the bottleneck is not the model but the harness; map every failure to one of 5 defensive layers.
  • Distinguish 5 harness subsystems (Instructions / Tools / Environment / State / Feedback) and fix Feedback first.
  • Make the repository a system of record: Cold-Start Test, KVG, ACID state.
  • Split bloated AGENTS.md into routing file + topical docs (SNR, lost-in-the-middle).
  • Maintain context between sessions: init phase, continuity artifacts, handoff.
  • Introduce WIP=1 and executable proof of completion; calculate VCR and Little's Law.
  • Build feature lists as data structures and multi-level validation (weak vs strict).
  • Require E2E, make runtime observable, and leave clean session state.
  • Calibrate methodology by scale—distinguish claims that reproduce from those that work only under real harness pressure.

What to Read Next

  • Walking Labs — "Learn Harness Engineering." Course primary source: walkinglabs.github.io/learn-harness-engineering
  • Liu et al. (2023) — "Lost in the Middle." Empirics on LLM attention degradation to the middle of long context.
  • Guo et al. (ICML 2017) — "On Calibration of Modern Neural Networks." Why neural networks systematically overestimate their confidence.
  • Anthropic — engineering notes on agents and context. Context anxiety, multi-agent harness, init phase.