Week 4: Customizing your agent and repository
Learning objectives
By the end of the lesson, you can:
- LO2: write an instruction file whose rules name the failure each rule prevents;
- LO2, LO9: distinguish a request an agent may skip from a hook gate that blocks the commit event, and verify both a blocked and an accepted commit;
- LO2, LO3: give a subagent a role with a separate instruction, a small tool allowlist, and a hand-back contract, then check the hand-back against the contract;
- LO3: carry the same boundaries into the technical specification due this week.
The examples are course-written and synthetic. They are prompts and checks for practice, not records of a real model run or a repository's production policy.
Minimal example
Start with a failure, then write the rule
A repository has a Python command that reads a configuration file. A rushed agent makes three tempting mistakes:
| Failure to prevent | Instruction rule | Evidence that can check it |
|---|---|---|
| The command is changed without its regression test | “For every behavior change, name the test that distinguishes old and new behavior.” | The change note names a test and the test runs |
| A generated command writes outside the checkout | “Use paths below the repository root; stop and ask when a path is outside it.” | A negative path case is refused |
| A commit changes the lockfile while fixing a parser | “Do not change dependency files in a parser change.” | The staged path list contains no lockfile |
The rule is useful because it predicts a failure. “Be careful” is a request with no observable check. Write the rule as a sentence that an agent can act on and a person can inspect.
Instruction files are scoped context
Create AGENTS.md at the repository root and, when a subdirectory needs a stricter rule,
place a more local instruction file there. Keep the file short enough to read. A good
entry names the scope, the allowed command, the expected evidence and the stop condition:
# Repository instructions
- Scope: Python files under `src/`; keep public function names stable.
- Before editing: read the named function and its tests; report file and function names.
- After editing: run `python -m pytest tests/test_parser.py -q` and report the result.
- Stop: do not change `pyproject.toml`, lockfiles or files outside this repository.
The request “please run the parser tests” can be skipped by a hurried agent. The stop rule can be checked by a reviewer, but it is still an instruction until a gate enforces it. An instruction file gives context and a decision procedure; it does not make the filesystem or Git refuse an action by itself.
A request and a gate have different jobs
A request is a message to the agent: “run the focused test and report the output.” A gate is attached to an event the repository controls: “before a commit, refuse staged Python files with an unused import.” The agent may omit the request. Git invokes the hook even when the agent forgets the request. A hook is therefore a narrow invariant, not a replacement for tests or review.
The studio uses the course-written hook in example/pre_commit.py. It accepts a JSON event,
checks protected paths and reports an unused Python import without importing or running the
file. The studio wraps it as .git/hooks/pre-commit; the wrapper supplies the staged path
list. It returns exit code 2 for a finding and exit code 0 for a clean list.
Generated proposal: add import json to report.py, stage it, and commit. The hook
should stop the commit because json is unused. Verification: read the hook output,
fix the import, stage again and commit; the second run should print PASS and exit 0.
Keep both records. A blocked run is evidence that the gate fired; it is not evidence that
the final change is correct.
Subagent roles are separate contracts
A role is more than a label such as “reviewer.” Give the subagent its own instruction, tools and output contract. For a bounded parser change, use three roles:
| Role | Instruction it receives | Allowed tools | Required hand-back |
|---|---|---|---|
| Researcher | Locate the parser and its callers; change nothing | read files, search, run one existing test | research.md with file/function references and open questions |
| Implementer | Change only the named function and preserve the contract | read, edit that file, run the named test | diff plus test output and a remaining risk |
| Reviewer | Judge the issue, diff and evidence without editing | read diff, read evidence, run the named test | each finding marked confirmed, refuted or cannot tell |
The parent agent or person chooses whether to continue after each hand-back. The subagent cannot enlarge its allowlist, declare its own work correct or silently replace a missing artifact. A generated hand-back is still a proposal: open every cited location, run the stated check and record what the check does not establish.
Generated proposal: ask a researcher to “find the parser.” Verification: reject a
hand-back that names no file and function. Ask for the bounded report again or stop. Then
pass only research.md and the named files to the implementer. A reviewer who sees the
issue, diff and test evidence in a fresh context can check the contract rather than trust
the implementer's summary.
Hands-on exercise
Follow the studio. You will write an instruction file for your own
repository, install a hook that blocks one intentionally bad staged change, and retain one
blocked and one passing commit record. Use ordinary git and python; do not put keys or
employer data in the practice repository.
For the second session, use a guest's workflow or complete the studio continuation. Bring the same instruction file and gate to your project's technical specification. Contribution 1 and the technical specification are due this week; link the evidence you actually have.
Verification
The studio has two independent checks:
python pre_commit.py(through the Git wrapper) returns a nonzero exit for the bad staged change and prints the finding; the savedblocked.txtrecords the command and output.- The corrected staged change commits with exit 0 and the saved
passed.txtcontains thePASSline.git show --stat --oneline HEADconfirms which files entered the commit.
For the role exercise, compare each hand-back with its contract. A complete report names locations, allowed tools, the exact command and its result. It does not claim that a model run happened when you used a course-written example.
Verification boundaries
- A blocked hook run proves that Git presented the recorded staged paths to this hook and that the hook returned a nonzero status. It does not prove the application behaves correctly.
- A passing hook run proves only that this input satisfied this hook's narrow checks. Run the focused test and inspect the diff separately.
- A role hand-back is evidence only for the fields it names. Open every cited location and rerun the named check; mark an unsupported claim cannot tell.
Common mistakes
- Treating an instruction as a permission boundary. Add a testable hook or a human review step for the invariant; keep the request for useful context.
- Writing a hook that edits files or runs arbitrary commands. Read and parse the staged paths, refuse malformed input, and return a clear nonzero status.
- Giving every subagent the whole repository and every tool. Narrow its files and tools, then inspect the hand-back in a fresh context.
- Recording only the passing commit. Preserve the blocked output too, and label what each record proves and what it leaves open.
- Copying a generated snippet without reading it. Compare the diff, run the check and make the decision yourself.