Skip to content

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:

  1. python pre_commit.py (through the Git wrapper) returns a nonzero exit for the bad staged change and prints the finding; the saved blocked.txt records the command and output.
  2. The corrected staged change commits with exit 0 and the saved passed.txt contains the PASS line. git show --stat --oneline HEAD confirms 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.