Skip to content

Turning repeated work into something the agent keeps

This lesson closes the course with one decision. We read three sessions in which a coding agent works on the same small project, and we find what the user had to explain in more than one of them. Each repeated explanation then moves to the project instructions, a skill, a hook or a context file, where the agent finds it next time. Last, we cut the lines of the instructions file that a hook and a skill now cover. The file gets shorter, because the hook and the skill hold what it used to say.

The files are in the course repository at site/examples/customizing-agents/repeated-work/. The session logs are in sessions/, and instructions-before.md is the project’s instructions file as it was during the sessions. The course wrote the logs for this lesson, and no model produced them. find_repeats.py is a short Python script that only reads the logs. Where the lesson describes Claude Code, the facts were checked against the vendor’s documentation on the date at the end of the lesson. Other coding agents have the same mechanisms under other names.

The project is the frontend of a small web shop. Its pages get their data from a billing API through a client that a generator writes into src/api/generated/. In each of the three sessions the billing API has a new field, so the agent must regenerate the client before it can use the field. Each session works out again how to do that. The user corrects the agent in each session, and some of the corrections are the same each time.

The script compares the three logs. It prints each message the user sent in more than one session, and each command the agent ran in all three.

Example · run it

Run this in site/examples/customizing-agents/repeated-work/, and compare with the output below.

Terminal window
python3 find_repeats.py
Output
Said by the user in more than one session:
  sessions 1, 2, 3: Don't edit files in src/api/generated/ by hand. Change the spec or regenerate the client.
  sessions 1, 2, 3: Set SPEC_URL to https://billing.example.com/staging/openapi.json, that's the staging spec.
  sessions 1, 3: Amounts from the billing API are in cents. Use formatMoney() from src/lib/money.ts, it divides by 100 and adds the currency.
  sessions 2, 3: Call the billing API the way this snippet does, through useApi(), so the page gets the loading and error states:
Run by the agent in every session:
  npm run generate
  make api-client
  SPEC_URL=https://billing.example.com/staging/openapi.json make api-client
  npm run lint
  npm run format -- src/api/generated
  npm test -- src/api
  npm test

Output verified in CI from site/examples/customizing-agents/repeated-work/find_repeats.py.

Read the output as a list of costs. Each line under the first heading is something the user typed again that a file could have said once. The commands under the second heading are the agent’s search for the procedure. The first attempt is npm run generate, a script that doesn’t exist. The make target comes next, without the variable it needs, and only after that the command that works. A lint error then shows that the generated files must be formatted. The same search happens in each of the three sessions.

That’s the symptom to look for in your own sessions: the same explanation, given twice. The fix is turning repeated work into reusable knowledge. You move the explanation to a place the agent reads without being told, so that no later session needs it. The Agent Engineer Course calls knowledge of how to do a task procedural memory, and it is often written into the agent’s instructions [1]. Claude Code’s guide to its extensions treats such moments as the signs that a project needs one more piece of setup [2]. When the agent has broken the same convention twice, add the convention to CLAUDE.md. When you have given the agent the same steps three times, write them as a skill. And when something has to happen on every occasion, with no exception, use a hook.

One line of the output needs a closer look. The instructions file already says “Never edit files under src/api/generated/”, and the user still had to say it in all three sessions. Claude Code reads the project’s instructions file at the start of every session [3], so the agent had the rule. It still started a hand edit in each session.

Each kind of repeated step has its own place. The choice depends on what the step is and what one miss costs.

  • A fact that every task needs goes into the project instructions. The instructions file is loaded at the start of every session, so its lines cost context in every session [2]. “Amounts from the billing API are integer cents” is such a fact. It matters on any page that shows money, whatever the task is.
  • A procedure goes into a skill. Steps that must happen in order are a procedure. A skill puts only its description in the context, and the steps load when a task matches the description or when you type the skill’s name after a slash [4]. The regeneration is three commands in a fixed order, and the agent needs them only when the client must change.
  • A rule where one miss costs too much goes into a hook. An instruction is a request. The agent weighs it against everything else in its context. A hook runs on its event every time, whatever the model remembers [2]. A hand edit under src/api/generated/ is lost the next time anyone regenerates the client, and nobody notices when it disappears.
  • An example to copy goes into a context file. Context files are files the team keeps for the agent to read, reviewed like code. The useApi() snippet is a pattern to copy. It belongs in a short file such as docs/examples/billing-call.md, with one line in the instructions that says when to read it. This placement is the convention of this course, from the lesson on reviewing memory. The best-practices page gives similar advice for prompts: point the agent to an example of the pattern in your code [5].

Claude Code also lets a skill hold reference material, such as a style guide [2]. This lesson keeps skills for procedures and context files for examples, so the question “what’s this?” has one answer.

The staging URL is a fact that the regeneration needs and other tasks don’t. The best-practices page lists required environment variables among the things to put in the instructions file [5], and that suits a variable that many tasks use. Only one command uses SPEC_URL, the first step of the regeneration. So the URL goes into that step, and a change to the URL is one edit in one file. A default in the Makefile itself (SPEC_URL ?= ...) is the other good fix, because then nobody has to know the URL.

Here is the skill, as .claude/skills/regenerate-api-client/SKILL.md in the project [4]:

---
name: regenerate-api-client
description: Regenerate the billing API client in src/api/generated/. Use when the billing API has changed, or when a field the API returns is missing from the client.
---
1. Run `SPEC_URL=https://billing.example.com/staging/openapi.json make api-client`.
2. Run `npm run format -- src/api/generated`. The generator's output fails the lint rules until it is formatted.
3. Run `npm test -- src/api`. If a test fails, stop and report the failure.
Done when `npm run lint` reports no problems and the API tests pass.

The hook is a PreToolUse hook with the matcher Edit|Write, so Claude Code runs it before each call to its Edit and Write tools. The hook gets the tool call as JSON, and for these tools tool_input.file_path is always the absolute path of the file. For a path with /src/api/generated/ in it, the hook writes a message to standard error and exits with code 2, which stops the edit and gives Claude the message as the reason [6]. The matcher compares tool names, so this hook sees only those two tools. A shell command that writes the file, such as sed -i or cat >, goes through the Bash tool, and this hook never sees it. Writing a hook that blocks a mistake builds a hook of this kind step by step. The message is all the agent learns about the rule when it is blocked, so it says what to do:

src/api/generated/ is generated from the billing API spec, and a hand edit is lost at the next regeneration. Run /regenerate-api-client to update the client, or change the spec in the billing API.

The user in the logs was a human approval step for this rule. They stopped the agent three times, and the answer was the same each time. Human in the loop says to keep approvals for the few moments that matter, so that each one gets your attention. A rule with one fixed answer doesn’t need your judgment, and the hook can give that answer. Your attention stays on the steps that do need judgment, such as reading the diff of the regenerated client before it is committed.

Checkpoint · sort

Checkpoint · choice

The team writes the regeneration skill. Where should the staging spec URL go?

Checkpoint · choice

The instructions file already had the rule, and the agent started a hand edit in all three sessions. What do you change?

The best-practices page suggests removing any line of the instructions file that the agent doesn’t need to avoid a mistake [5]. The same page warns that a long instructions file makes the agent miss the rules that matter.

Apply this to the long regeneration paragraph first. The skill holds the procedure now, with each step on its own line, and its description is in the context of every session [4]. Without the paragraph the agent still finds the steps, so the paragraph goes.

The line “Never edit files under src/api/generated/” is different. The hook blocks the agent’s Edit and Write calls on those files, and its message says what to do. It doesn’t see a shell command, and an agent that is blocked on one tool may try the same change through another. So the hook covers only part of the rule, and a line for the rest stays. It can be short, and it can say where the change goes instead:

- Change files under `src/api/generated/` only by regenerating the client. A hook blocks direct edits.

The line also helps the agent plan. Without it, the agent learns the rule only when a hook blocks an edit it has already planned, and it loses a turn to plan again. The skill’s description helps there too, because it names the case of a missing field.

The fact about amounts in cents and the pointer to the example file come in. Here is the change to the instructions file:

- Run the tests with `npm test`, and `npm run lint` before you commit.
- Commit messages follow Conventional Commits.
-- Never edit files under `src/api/generated/`. They are generated from the billing API spec.
-- The API client is generated from the billing API's OpenAPI spec, and when the billing API changes you need to regenerate it, which is done with the api-client target in the Makefile, but that target needs the SPEC_URL variable set to the spec of the right environment (usually staging), and afterwards the generated files don't pass our lint rules, so format them with the format script before you run the API tests, and don't commit until those pass.
+- Change files under `src/api/generated/` only by regenerating the client. A hook blocks direct edits.
+- Amounts from the billing API are integer cents. Show them with `formatMoney()` from `src/lib/money.ts`.
+- Before a page calls the billing API, read `docs/examples/billing-call.md` and follow it.
- Pages live in `src/pages/`, shared components in `src/components/`.

Delete a rule from the file only when a hook enforces the whole rule, and shorten it when a hook enforces a part. The lint line stays whole, because no hook in this project runs the linter. A line you cut in favor of a hook also depends on that hook being registered on each machine. A hook in the project’s .claude/settings.json is committed with the code, and each clone runs it once the folder is trusted [6]. A hook in your personal settings protects only your sessions, so keep the full line until the hook is shared.

Checkpoint · multi-choice

Which two of these are right for the instructions file now?

Select exactly 2.

Checkpoint · scenario

The instructions file now says only that generated files change by regeneration. The hook blocks a hand edit under src/api/generated/ when the agent tries one. Which message should it write?

Exercise

Copy instructions-before.md and the three logs to a folder of your own, so you can change the copy freely. Read the three logs. Make a list of each step that came up in more than one session, and next to each step write where it goes (instructions, skill, hook or context file) and a one-line reason. Then edit your copy of the instructions file: delete or shorten the lines that a hook or a skill now covers, and add the lines that point to the new places. Write the skill’s description and the hook’s message as well. This is work on paper, and it takes about ten minutes. It practices the question to ask the next time you type an explanation you have typed before.

A good result places the repeated steps as this lesson did: the regeneration and its staging URL, the rule about generated files, the amounts in cents and the useApi() example. Your file keeps the lines for Node, the tests and the linter, the commit messages and the folders, and a short line about generated files. Compare with the files in model-answer/. Your wording will differ, and the places should be the same. If you placed a step somewhere else, write the reason down and check it against the four rules in “A place for each repeated step”. Which explanation did you give an agent more than once this week, and where would it go?

Stretch: Do the same for a project of your own. Read the last three sessions you had with an agent on it, list what you said in more than one of them, sort each item the same way, and write the new instructions file. Try any hook in a branch you can throw away first.

Recap

  1. The sign to look for is the same explanation given twice. Each one can move to a place the agent reads without being told, so no later session needs it [2].
  2. A fact that most tasks need goes into the project instructions, and a procedure goes into a skill, which loads only when a task needs it [4].
  3. A rule where one miss costs too much goes into a hook, which runs every time. An instruction is a request, even one the agent has read [2].
  4. In this course, an example to copy goes into a context file, with one line in the instructions that says when to read it.
  5. A hook enforces only the tool calls it sees. Delete a line that a shared hook or a skill holds in full, and shorten a line that a hook covers in part, such as a rule a shell command can still break. The instructions file gets shorter, and the rules left in it stand out [5].

You can now

  • Manages what the agent carries between sessions
  • Adds a hook that enforces a rule the instructions cannot

  1. Addy Osmani, Ivar Soares Urdalen, Leo Simons. Memory and context: context engineering, memory kinds, memory versus RAG, context rot. Agent Engineer Course. Course. AEC-05
  2. Anthropic. Extend Claude Code. Claude Code documentation. Reference. Claude Code features
  3. Anthropic. How Claude remembers your project. Claude Code documentation. Reference. Claude Code memory
  4. Anthropic. Extend Claude with skills. Claude Code documentation. Reference. Claude Code skills
  5. Anthropic. Best practices for Claude Code. Claude Code documentation. Reference. Claude Code best practices
  6. Anthropic. Hooks reference. Claude Code documentation. Reference. Claude Code hooks