2684095e2f
Co-authored-by: claude <claude@jpaul.io> Co-committed-by: claude <claude@jpaul.io>
305 lines
16 KiB
Markdown
305 lines
16 KiB
Markdown
# Module 5 — Commit the AI's Config, Not Just the Code
|
||
|
||
> **The instructions you give the model are as worth versioning as the code it writes.** Write your
|
||
> project's conventions down once, commit them, and every teammate — and every agent — inherits the
|
||
> same setup instead of each of you hand-tuning your own and quietly drifting apart.
|
||
|
||
---
|
||
|
||
## Prerequisites
|
||
|
||
- **Module 1** — you have the `tasks-app` project, an editor, and a terminal.
|
||
- **Module 2** — you can `commit`, read a `diff`, and treat commits as checkpoints. This module adds
|
||
one more thing worth committing.
|
||
- **Module 4** — the AI now lives in your editor or CLI and reads your files directly. That's the
|
||
whole reason a *committed* instructions file matters: an editor-integrated tool can pick it up
|
||
automatically, where a browser chat never could.
|
||
|
||
---
|
||
|
||
## Learning objectives
|
||
|
||
By the end of this module you can:
|
||
|
||
1. Identify the repo-level instructions file your agentic tool reads, and explain what belongs in it.
|
||
2. Write an instructions file for a real project — conventions, build/test commands, coding
|
||
standards, off-limits files, house style — that an AI will actually act on.
|
||
3. Commit that file so the configuration travels with the repo, not with one person's machine.
|
||
4. Demonstrate the AI obeying the committed instructions, and changing its behavior when you change
|
||
the file.
|
||
5. Explain why committing the config makes AI behavior *reviewable* — a change to how the AI works
|
||
arrives as a diff, like any other change.
|
||
|
||
---
|
||
|
||
## Key concepts
|
||
|
||
### The file your tool is already looking for
|
||
|
||
Open almost any agentic coding tool and, before it does anything, it scans the repo for a
|
||
**committed, repo-level instructions file** — a plain-text (usually markdown) file at the project
|
||
root that tells the AI how *this* project works. Different vendors look for different filenames, and
|
||
the names change; that's noise. The durable fact is the pattern: **your agentic tool reads a
|
||
committed instructions file from the repo, and you control what's in it.**
|
||
|
||
> Throughout this module we'll say "your agentic tool's committed instructions file" rather than name
|
||
> one. Find yours in your tool's docs (look for "project instructions," "rules," "context," or a
|
||
> repo-root config file). Some tools even read more than one filename — point them all at the same
|
||
> content if so. The principle outlives any one vendor's filename.
|
||
|
||
Without this file, you re-explain your project every session: "we use 4-space indent," "the tests are
|
||
`pytest`, run them before you say you're done," "don't touch the generated `tasks.json`." You say it,
|
||
the AI complies, the session ends, the memory evaporates (Module 1's second seam), and tomorrow you
|
||
say it all again. The instructions file is where that knowledge stops being something you retype and
|
||
becomes something the project *carries*.
|
||
|
||
### What goes in it
|
||
|
||
An instructions file is not a prompt and it's not documentation for humans (that's the README). It's
|
||
a briefing for an agent that will edit this code. Keep it to what changes the AI's behavior:
|
||
|
||
- **Project conventions** — language version, layout, naming, the patterns this codebase actually
|
||
uses. "Core logic lives in `tasks.py`; the CLI front end is `cli.py`; state persists to
|
||
`tasks.json`."
|
||
- **Build and test commands** — the exact commands, copy-pasteable. "Run the app with
|
||
`python cli.py <command>`. Run tests with `pytest`. Don't claim a change works until the tests
|
||
pass." This single line stops the AI from inventing a test runner you don't use.
|
||
- **Coding standards** — formatting, typing, error handling, the libraries you do and don't want.
|
||
"Use the standard library only — no third-party packages. Type-hint public functions."
|
||
- **"Don't touch these files."** — the off-limits list. Generated files, vendored code, secrets,
|
||
anything the AI should read but never rewrite. "Never edit `tasks.json` by hand; it's generated."
|
||
- **House style** — the taste calls that otherwise come back wrong every time. "Keep functions
|
||
small. Match the existing style; don't reformat files you're not changing. Prefer clarity over
|
||
cleverness."
|
||
|
||
The test of a good line: would you otherwise have to say it again next session? If yes, it belongs in
|
||
the file. If the AI already gets it right without being told, leave it out — bloat dilutes the
|
||
signal (see *Where it breaks*).
|
||
|
||
### Why commit it instead of keeping it in your head (or your settings)
|
||
|
||
Most tools also let you set instructions *globally* — on your machine, for all projects. That's
|
||
useful for personal preferences, but it's the wrong home for project knowledge, because of where it
|
||
lives: on *your* laptop, invisible to everyone else.
|
||
|
||
Picture a two-person project with no committed instructions file. You've trained your local setup to
|
||
run `pytest` and avoid `tasks.json`. Your teammate's setup hasn't — their agent reformats whole files
|
||
and hand-edits the generated JSON. You're both "using AI on the same repo," but you're getting
|
||
different behavior, and neither of you can see the other's configuration. That's **drift**: the same
|
||
codebase, diverging because the rules live in two heads instead of one file.
|
||
|
||
Commit the file and that collapses. The configuration is now part of the repo. Clone the repo, get
|
||
the rules. A new teammate — or a brand-new agent that's never seen the project — is configured
|
||
correctly on the first run, because the setup travels *with the code* instead of with whoever set it
|
||
up. This is the same move as Module 2's "the repo is durable memory the AI can read," aimed one level
|
||
up: not just the code's history, but the instructions for working on it.
|
||
|
||
### The real unlock: AI behavior becomes reviewable
|
||
|
||
Here's the part that makes this more than a convenience. Once the instructions live in the repo, **a
|
||
change to how the AI works on this project is a change to a tracked file** — so it shows up exactly
|
||
like a code change does:
|
||
|
||
```bash
|
||
git diff
|
||
```
|
||
|
||
When someone tightens "keep functions small" into "no function over 30 lines," or adds
|
||
`infra/` to the don't-touch list, that decision arrives as a *diff* you can read, question, and
|
||
accept or reject. It's no longer an invisible tweak in one person's settings that silently changes
|
||
what the AI does for everyone. The way your team works with AI becomes a reviewable artifact with a
|
||
history — you can `git log` it and see *why* a rule exists and when it was added.
|
||
|
||
The full version of this lands in **Module 10**, where that diff becomes a pull request someone
|
||
actually reviews before it merges, and **Module 8**, where a shared remote means the file reaches the
|
||
whole team. You don't have those yet — so for now the payoff is local: the file is committed, the
|
||
behavior is recorded, and `git diff` already shows changes to it as plainly as changes to any code.
|
||
The habit starts now; the team-scale payoff arrives on schedule.
|
||
|
||
### This course commits its own
|
||
|
||
You don't have to take this on faith — this repo does exactly what the module teaches. At the root of
|
||
*The Workflow* is an `AGENTS.md` file: the committed instructions for the agents that help author the
|
||
course. It states what the repo is, the core promises (model-agnostic, GitHub-as-default-not-
|
||
requirement, the load-bearing dependency chain), the voice, the lab conventions, and a flat "Don't"
|
||
list. Open it:
|
||
|
||
```bash
|
||
git show HEAD:AGENTS.md # or just open AGENTS.md in your editor
|
||
git log --oneline AGENTS.md # its history — every change to how agents work on this repo
|
||
```
|
||
|
||
That file is why every module in this course sounds like one course instead of twenty-seven
|
||
tutorials. It's the worked example for everything below.
|
||
|
||
### Where this is heading: Skills (Module 21)
|
||
|
||
A committed instructions file is the lightweight foundation. It says *how this project works* in
|
||
general — always-on context the AI reads every session. When you find yourself wanting to capture a
|
||
*specific repeatable procedure* ("here's exactly how we cut a release," "here's our playbook for
|
||
adding a new CLI command"), that's the structured big sibling: **Skills (Module 21)**. Same instinct —
|
||
write the knowledge down, commit it, let the AI execute it your way — but packaged as reusable
|
||
playbooks instead of a single always-on briefing. Start with the instructions file; graduate to
|
||
skills when a procedure earns its own page.
|
||
|
||
---
|
||
|
||
## The AI angle
|
||
|
||
This is the course thesis applied to your own configuration. **The model is the cheap, swappable
|
||
part; the setup you build around it is the durable artifact.** When you swap models next quarter —
|
||
and you will — your committed instructions file carries over unchanged. The new model reads the same
|
||
conventions, the same test command, the same don't-touch list, and behaves consistently on day one.
|
||
You configured the *project*, not the model.
|
||
|
||
Three things make this specifically an AI problem, not a generic config chore:
|
||
|
||
- **AI has no memory across sessions, but it reads files.** A committed instructions file is the
|
||
cleanest way to give an ephemeral agent durable, project-specific context — written once, read
|
||
every session, by every model.
|
||
- **AI is confidently inconsistent without a spec.** Unprompted, it'll pick a test runner, a
|
||
formatting style, a place to put new code — and pick differently next time. The instructions file
|
||
is how you make "the way we do it here" the default instead of a coin flip.
|
||
- **AI behavior is otherwise invisible.** A teammate's hand-tuned local rules silently change what
|
||
the AI does. Committing the rules drags that into the open where it can be reviewed — which is the
|
||
whole reason this audience trusts version control in the first place.
|
||
|
||
---
|
||
|
||
## Hands-on lab
|
||
|
||
**Lab language:** shell + markdown, on the `tasks-app` project from Modules 1–2. You'll use your
|
||
editor-integrated AI (Module 4) for the part where the AI obeys the file.
|
||
|
||
**You'll need:**
|
||
|
||
- The `tasks-app` repo from Module 2 (already a Git repo with some history).
|
||
- Your agentic coding tool from Module 4, and knowledge of which filename it reads for repo-level
|
||
instructions (check its docs — see the note in *Key concepts*).
|
||
- Optionally `pytest` (`pip install pytest`) so the AI has a real test command to honor.
|
||
|
||
### Part A — Write the instructions file
|
||
|
||
1. Look up the instructions filename your tool reads. Copy this module's starter,
|
||
`lab/instructions-file-starter.md`, to that filename at the **root of your `tasks-app` repo**.
|
||
(If your tool reads several names, copy it to each, or symlink them.)
|
||
|
||
```bash
|
||
cd ~/workflow-course/tasks-app
|
||
# replace <YOUR_TOOL_FILE> with the name your tool actually reads:
|
||
cp /path/to/modules/05-commit-the-ai-config/lab/instructions-file-starter.md <YOUR_TOOL_FILE>
|
||
```
|
||
|
||
2. Open it in your editor and make it true for *your* project. The starter is filled in for the
|
||
`tasks-app`, but read every line and confirm it matches reality — wrong instructions are worse
|
||
than none. At minimum, set the real test command (or delete the line if you didn't install
|
||
`pytest`).
|
||
|
||
3. Commit it. This is the point of the whole module:
|
||
|
||
```bash
|
||
git add <YOUR_TOOL_FILE>
|
||
git commit -m "Add committed AI instructions for tasks-app"
|
||
```
|
||
|
||
The configuration now travels with the repo.
|
||
|
||
### Part B — Watch the AI obey it
|
||
|
||
4. Start a **fresh** AI session in your editor (so it picks up the file cleanly) and give it a task
|
||
that the instructions constrain. For example:
|
||
|
||
> *"Add a `clear` command that removes all tasks. Then confirm it works."*
|
||
|
||
5. Watch for the file taking effect. A correctly-configured agent should, without you saying any of
|
||
it this time:
|
||
- put the logic where your conventions said it goes (core in `tasks.py`, CLI wiring in `cli.py`);
|
||
- **not** hand-edit `tasks.json` (you marked it off-limits);
|
||
- use the standard library only (no surprise `pip install`);
|
||
- run your stated test/run command before declaring success, instead of inventing one.
|
||
|
||
You're checking that behavior you'd normally have to *dictate every session* now happens by
|
||
default. That delta is the file working.
|
||
|
||
6. If it ignored a rule, that's signal too — tighten the wording, commit the change, and try again.
|
||
Vague instructions get vague compliance; specific, imperative lines ("Never edit `tasks.json` by
|
||
hand — it is generated") land far better than soft ones ("try to avoid editing generated files").
|
||
|
||
### Part C — Make a behavior change reviewable
|
||
|
||
7. Now change *how the AI works* and watch it show up as a diff. Add a house-style rule to the file —
|
||
say, a hard line length:
|
||
|
||
> Add to the instructions file: `Keep functions under 20 lines; split anything longer.`
|
||
|
||
8. Before committing, read the change exactly as a reviewer would:
|
||
|
||
```bash
|
||
git diff
|
||
```
|
||
|
||
That diff *is* the change to your AI workflow — readable, attributable, revertable. Commit it:
|
||
|
||
```bash
|
||
git add <YOUR_TOOL_FILE>
|
||
git commit -m "Require functions under 20 lines"
|
||
```
|
||
|
||
9. Look at the history of just this file:
|
||
|
||
```bash
|
||
git log --oneline <YOUR_TOOL_FILE>
|
||
```
|
||
|
||
Every line is a decision about how the AI behaves on this project — recorded, not lost in someone's
|
||
local settings. (In Module 8 this file reaches your whole team via a remote; in Module 10 that diff
|
||
becomes a PR someone reviews before it lands. The habit you just built is what those modules turn
|
||
into a team workflow.)
|
||
|
||
---
|
||
|
||
## Where it breaks
|
||
|
||
Be honest about what a committed instructions file does and doesn't buy you:
|
||
|
||
- **It's guidance, not a guarantee.** The file biases the model strongly; it does not bind it. An AI
|
||
can still ignore a line, especially a vague one, especially deep in a long session. The enforcement
|
||
that *can't* be ignored — tests that fail the build, scans that block a merge — is **CI
|
||
(Module 14)** and **security scanning (Module 15)**. The instructions file reduces how often the AI
|
||
goes wrong; it doesn't replace the gates that catch it when it does.
|
||
- **Bloat kills it.** A 300-line instructions file is read the way *you* read a 300-line terms-of-
|
||
service: not really. Every line you add dilutes the rest. Keep it to what actually changes behavior,
|
||
and prune lines the model already honors without being told.
|
||
- **Stale instructions are worse than none.** A file that says "tests are `pytest`" after you've moved
|
||
to something else will actively misdirect the AI. The file is code-adjacent — it has to be
|
||
maintained like code, and reviewed like code. That's exactly why committing it (so changes are
|
||
visible) matters.
|
||
- **The team payoff isn't here yet.** On a solo local repo, the "no more drift between teammates"
|
||
argument is theoretical — there's only you. The full value lands with a shared remote
|
||
(**Module 8**) and review (**Module 10**). What you get *now* is the habit and the local history;
|
||
don't oversell the team benefit until the team can actually pull the file.
|
||
- **It is not a security control.** Telling an agent "don't touch `secrets.env`" is a convention, not
|
||
a permission boundary — a sufficiently confused or adversarial agent can still read or write it.
|
||
Real isolation and least-privilege for agents come later (**Modules 16 and 22**). The instructions
|
||
file expresses intent; it doesn't enforce it.
|
||
|
||
---
|
||
|
||
## Check for understanding
|
||
|
||
**You're done when:**
|
||
|
||
- Your `tasks-app` repo has a committed instructions file at the root, filled in to match the actual
|
||
project, and `git log` shows the commit that added it.
|
||
- You've watched a fresh AI session honor a rule from the file — placing code where your conventions
|
||
said, respecting the don't-touch list, or running your stated test command — *without you saying it
|
||
that session*.
|
||
- You've changed a behavior rule, read the change with `git diff`, and committed it — so a change to
|
||
how the AI works is now a reviewable diff with a history.
|
||
- You can explain, in one sentence, why committing the file beats each teammate hand-tuning their own
|
||
setup: the configuration travels with the repo, so nobody drifts.
|
||
|
||
When the AI behaves like it already knows your project the moment you open it — and you didn't say a
|
||
word this session — the file is doing its job. Module 6 takes the safety net further: branches, so the
|
||
AI can try something wild in a sandbox you can throw away.
|