# Turning repeated work into something the agent keeps

> Read three coding sessions that each work out how to regenerate an API client, place each repeated explanation in the instructions, a skill, a hook or a context file, and cut the instruction lines that a hook and a skill now cover.

Page: https://schubergphilis.github.io/ai-training/customizing-agents/repeated-work/
License: CC BY-SA 4.0, https://creativecommons.org/licenses/by-sa/4.0/

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 same explanation in three sessions

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: What came up more than once

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

```sh
python3 find_repeats.py
```

Output:

```text
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
```

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 (Memory and context: context engineering, memory kinds, memory versus RAG, context rot, Agent Engineer Course). Claude Code's guide to
its extensions treats such moments as the signs that a project needs one
more piece of setup (Extend Claude Code, Claude Code documentation). 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 (How Claude remembers your project, Claude Code documentation), so
the agent had the rule. It still started a hand edit in each session.

## A place for each repeated step

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 (Extend Claude Code, Claude Code documentation). "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 (Extend Claude with skills, Claude Code documentation). 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 (Extend Claude Code, Claude Code documentation). 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](https://schubergphilis.github.io/ai-training/customizing-agents/memory-review/).
  The best-practices page gives similar advice for prompts: point the
  agent to an example of the pattern in your code
  (Best practices for Claude Code, Claude Code documentation).

Claude Code also lets a skill hold reference material, such as a style
guide (Extend Claude Code, Claude Code documentation). 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 (Best practices for Claude Code, Claude Code documentation),
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 (Extend Claude with skills, Claude Code documentation):

```markdown
---
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 (Hooks reference, Claude Code documentation). 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](https://schubergphilis.github.io/ai-training/customizing-agents/first-hook/) 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:

```text
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](https://schubergphilis.github.io/ai-training/safety/agent-risk/#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: Where does each step go?

Buckets:

- Project instructions
- Skill
- Hook
- Context file

Items:

- Amounts from the billing API are integer cents, shown with formatMoney()
- The three commands that regenerate the API client, in their order
- No edit to a file under src/api/generated/, on any task
- The useApi() snippet that shows how a page calls the billing API
- The tests run with npm test, and the API tests with npm test -- src/api
- No commit of a file named .env, which holds the local secrets

#### Checkpoint: Where does the staging URL go?

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

- Into the first step of the regeneration skill, as part of the command.
- Into the project instructions, so every session, regeneration or not, starts with the URL.
- Into the agent's auto-memory, which it saves after the user says it.
- Nowhere, so the user states it in the chat whenever the agent asks.

#### Checkpoint: The rule was in the file

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

- Put IMPORTANT in front of the line in the instructions file, so the agent gives the rule more weight.
- Move the line into the regeneration skill, next to the steps that regenerate the client.
- Add a hook that blocks the agent's file edits under src/api/generated/ and says what to do.
- Write the reason next to the rule in the instructions file, so the agent knows why it matters.

## Cut what the hook and the skill now hold

The best-practices page suggests removing any line of the instructions
file that the agent doesn't need to avoid a mistake
(Best practices for Claude Code, Claude Code documentation). 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 (Extend Claude with skills, Claude Code documentation). 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:

```text
- 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:

```diff
 - 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 (Hooks reference, Claude Code documentation). A
hook in your personal settings protects only your sessions, so keep the
full line until the hook is shared.

#### Checkpoint: What happens to each line?

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

Select exactly 2.

- The regeneration paragraph can go, because the skill holds its steps.
- The generated-files line can go, because the hook blocks each change there.
- The generated-files line stays as a short line, for the part the hook misses.
- The lint line can go, because the skill runs the linter at its end.

#### Checkpoint: What does the hook say?

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?

- src/api/generated/ is generated code, and a hand edit there is lost the next time the client is regenerated.
- This folder is protected. Ask the user for permission before you edit anything under src/api/generated/.
- Error: the PreToolUse hook exited with code 2 for an Edit call on a file in src/api/generated/.
- src/api/generated/ is generated from the billing API spec. Run /regenerate-api-client to update the client, or change the spec.

#### Pitfall: The procedure written as one long line

The instructions file in this lesson had the whole regeneration in one
sentence: the `make` target, the variable, the formatting and the tests.
The agent had that sentence in its context in all three sessions, and in
each one it still tried `npm run generate`, a script that doesn't exist. A procedure in a long
line of prose is easy to skim, and nothing in it tells the agent when it
applies. Put steps in a skill, one step per line, with a description that
says when to use it (Extend Claude with skills, Claude Code documentation), and keep the instructions file
for short facts (Best practices for Claude Code, Claude Code documentation).

## 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 (Extend Claude Code, Claude Code documentation).
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
   (Extend Claude with skills, Claude Code documentation).
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
   (Extend Claude Code, Claude Code documentation).
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
   (Best practices for Claude Code, Claude Code documentation).

#### Habit

The next time you type an explanation you have typed to an agent before,
stop and decide where it goes: the instructions, a skill, a hook or a
context file. Move it there before the session ends.

## References

- Memory and context: context engineering, memory kinds, memory versus RAG, context rot, Agent Engineer Course, https://lsimons.github.io/agent-engineer-course/05-memory-and-context/
- Extend Claude Code, Claude Code documentation, https://code.claude.com/docs/en/features-overview
- How Claude remembers your project, Claude Code documentation, https://code.claude.com/docs/en/memory
- Extend Claude with skills, Claude Code documentation, https://code.claude.com/docs/en/skills
- Best practices for Claude Code, Claude Code documentation, https://code.claude.com/docs/en/best-practices
- Hooks reference, Claude Code documentation, https://code.claude.com/docs/en/hooks
