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.
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
🧱 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.
🔄 Diagnostic Loop
The main mechanism for improving harness is the iterative diagnosis cycle:
- Reproduce the failure — repeat the conditions under which the agent erred.
- Map to a layer — which of the 5 layers caused it?
- Fix that layer — only that one, leave the rest alone.
- Rerun — make sure the failure no longer reproduces.
- Repeat 3–5× — until all systemic issues are resolved.
🧩 Interactive 1: Map the Failure to a Layer
Real scenarios from the notify experiment (lesson 1). For each—choose the correct layer.
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?
scripts/conventions.py existed in the repo, but the agent never ran it and shipped a violation, considering the task complete. Which layer?
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?
🧮 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."
🔭 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.
🔧 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:
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
python check.py — one call runs all checksWithout 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:
- Feedback — one verification script. The agent finally understands "done" vs "broken."
- Instructions — a brief
AGENTS.md. Project context from the first seconds. - State —
PROGRESS.md. Memory between sessions. - Environment — lock files and
.python-version. - Tools — permissions, CLI access. Usually already there, needs tuning.
🔗 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.
🧩 Interactive 2: Failure Layer → Which Subsystem Fixes It?
Three scenarios—determine which harness subsystem needs investment.
🔬 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 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.
"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.
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?
Purpose, domain, primary users. → README.md
Architecture, modules, interfaces. → ARCHITECTURE.md
Dependencies, commands, env variables. → CLAUDE.md
Tests, lint, acceptance criteria. → AGENTS.md
What's in progress, what's blocked, what decisions have been made. → PROGRESS.md
🧮 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).
- 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%
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.
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.
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.
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
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
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)
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)
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.mdandCONSTRAINTS.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?
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.
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%
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.
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.
## 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.
🔭 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.
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
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)
docs/safety-critical.md file that the routing file
explicitly points to for any task.
🧪 Interactive 3 — Check Your Understanding
AGENTS.md into a routing file + thematic docs?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.
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:
- ✅ Can run — environment starts without errors
- 🧪 Can test — at least one test passes green
- 📊 Progress is visible — there's a measurable artifact (file, log, DB row)
- 🔗 Can pick up — the next agent (or next session) understands what's done and what's next
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)
🧩 Interactive: Bootstrap Contract
Check your understanding of the four Bootstrap Contract conditions.
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.
Continuity Artifacts
What's done, what's temporary, why this particular decision was chosen. Preserves why, not just what.
Architectural decisions and their rationale. Prevents reinventing the wheel in the next session.
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?
- Lesson 2 ablation: removing
PROGRESS.mdbroke 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.mdwas cheaper on all metrics—the code itself carried the decisions.
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.
_post_with_retry into channels.py; session 2 continues adding channels.
"message" instead of "body" because the HTTP contract requires it—and there is NO test checking this.
📊 Cost of Splitting: Marathon vs Multiple Sessions
Data from lesson 5: the same 3-channel task was solved three ways. Metric—total tokens.
- Task clearly exceeds context window
- Different agent specializations needed
- Independent subtasks for parallelism
- Task fits in one session
- High connectivity—agent carries full context in head
- Savings on handoff artifacts are substantial
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
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→ PASSEDcurl -s /api/v1/item | jq '.id'→ not nullassert result == expectedin 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
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)
📊 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.
⚠ Honest Empirical Takeaway
🐛 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.
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
- One active task at a time. The next task starts only after the current one passes executable proof of completion.
- Block when VCR < 1.0. If the previous task didn't pass verification—don't activate a new one, finish the current first.
- Commit per feature. After passing verification—a commit with a clear message. This is both bisectable history and an explicit completion point.
- Proof of completion defined upfront. Before activating a task define the specific executable test—otherwise "done" remains subjective.
🧠 Quiz
📋 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": "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 startedactive— in progressblocked— there's a blocker (waiting for dependency / decision)passing—verify_cmdreturned exit 0 in CI; only this way
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.
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
🧮 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%.
verify_cmd.
📊 Worklog data: weak vs strict checking
Lesson 8 — 11 deliberately planted bugs. Two checking modes:
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:
🔒 Dual Verification-Validation Gateway
For victory to be honest, you need two independent gates:
"Does code implement the spec?"
unit tests, static analysis, contracts
"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
- Syntax / static analysis — lint, type checker, compilation. Fast, local, cheap. Always mandatory.
- Runtime behavior — tests execute, app starts, critical paths pass. Detects logic errors.
- 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
🎯 Quiz
passing state?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
-
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. -
Level 2 — Runtime Behavior
Tests execute, app starts, critical paths pass (unit + integration). Much better—but inter-component interactions remain in the blind spot. -
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.
Anthropic Case: Retro Game Editor
Anthropic compared two approaches to the same task (identical prompt—develop a retro game editor):
- Time: ~20 minutes
- Cost: $9
- Result: broken editor
- 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
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.
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
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:
python check.py / gate is green
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.
- 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.
- 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.
🧪 Final Quiz: Course Synthesis
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.