Build-your-own harness labs
Each labNN-* folder is a standalone snapshot of a small Python harness,
called harness. The snapshots build on each other, one capability per lab.
The goal is a harness that is small enough to read in full and still does
real work, so you can see what each capability adds. It is not meant to be a
production replacement for Claude Code or Codex.
The Claude Code track covers the same capabilities using an existing harness. Both tracks use the same practice app: a synthetic pet-store order calculator that you build in Lab 2A and keep extending. That makes it straightforward to compare the two harnesses on the same work.
Working in labs/app/
labs/
├── app/ # Lab 0, gitignored: YOUR practice app (its own git repo)
├── lab00-setup/ # each lab: its own .venv, .env, harness code and checks
├── lab02b-hooks/
│ └── assets/ # files you copy into app/ (hooks, rules, ...)
└── ...
Each lab is installed on its own, from its own folder, into its own .venv
with its own .env. You use each lab’s harness from app/:
cd labs/lab02-tool-loop
python -m venv .venv
. .venv/bin/activate # Windows: .\.venv\Scripts\Activate.ps1
pip install -e '.[dev]'
cp .env.example .env # non-secret Foundry settings only (Entra-only)
az login
cd ../app
harness ask
The harness command finds the .env file in the lab folder where it was
installed, even when you run it from app/. The tools run in the current
directory (or --repo PATH), so the agent only works on the practice app.
Checkpoints are git tags in app/. Each lab ends with
git tag labNN-done, and each lab README says which tag it starts from. The
model’s output varies between runs, so your code will differ from other
learners’ code. That is expected: the prompts describe goals, not exact
code. To redo a lab, return to its starting tag:
cd labs/app
git switch main
git reset --hard lab02a-done # the tag the lab starts from
git clean -fd
Labs and the practice app
| Lab | Harness capability | What you do to the app |
|---|---|---|
| 0 | Foundry setup, two provider APIs | Create app/ as a git repo; make a live call |
| 1 | Bare model call | Ask about the app: a bare model can’t see it |
| 2A | Tool loop with file, test, Git and shell tools | Build the app; find a regression with git history |
| 2B | Hooks and scoped rules | Pricing rule; hooks that block catalog edits and run tests |
| 3 | Sessions: continue, resume, fork | Low-stock threshold across sessions |
| 4 | Plan mode and todos | Plan, approve and build discount codes |
| 5 | HARNESS.md project memory |
Record conventions; add gift wrapping |
| 6 | Permission rules and approval prompts | Generate a price list from a poisoned catalog; ask, allow and deny rules |
| 7 | Trace events and harness trace |
Trace adding a stats command; read cost and timings |
| 8 | Skills and MCP servers | Release-notes skill; synthetic orders MCP server |
| 9 | Subagents and background agents | Delegate a review and tests to subagents; coverage report in a background worktree |
| 10 | Compaction and ranged reads | Investigate a large log; /compact and /clear |
| 11 | Stop hooks and iteration limits | Bulk discount that must pass a protected spec test |
| 12 | Routing graph | harness route tickets to bug, question or feature specialists |
| 13 | Planner, generator, evaluator | harness pge a feature with a human gate and revisions |
| 14 | Cross-harness comparison | Same task in this harness and another; compare traces |
Safety
The tools run with your operating-system permissions. Lab 2A is
intentionally unrestricted. From Lab 2B on, hooks deny shell and
destructive Git commands, but that is a teaching policy, not a sandbox.
Project hooks in .harness/settings.json are commands the harness runs for
you, so only use a project you trust. Use the synthetic data these labs
create. Never put keys or tokens in .env, prompts or the app.