diff --git a/06-branches-sandboxes-for-experiments.md b/06-branches-sandboxes-for-experiments.md
index 6d6516e..8270f33 100644
--- a/06-branches-sandboxes-for-experiments.md
+++ b/06-branches-sandboxes-for-experiments.md
@@ -42,8 +42,8 @@ By the end of this module you can:
2. Let the AI make a bold, multi-commit change on a branch while `main` stays untouched and runnable.
3. Decide the experiment's fate and have the agent carry it out: **merge** it into `main` to keep it,
or **delete the branch** to throw it away with zero trace. You make the call and check the result.
-4. Read a merge conflict (the `<<<<<<<`/`=======`/`>>>>>>>` markers) and hand it to the AI to
- resolve, then verify the resolution is right before the merge completes.
+4. Recognize a merge conflict (the `<<<<<<<`/`=======`/`>>>>>>>` markers) when you see one, and
+ verify the AI's resolution even when the agent resolved it silently and you never saw a marker.
5. Tell the difference between a fast-forward merge and a merge commit, and know which one you got.
---
@@ -187,13 +187,25 @@ Read it like this:
Resolving isn't picking a side mechanically. It's deciding what the line *should* say. Often that's
one side; sometimes it's a blend of both (here, a usage string that lists *both* `stats` and `purge`).
This is the kind of bounded reasoning task the AI is good at: it sees both versions and the
-surrounding code, so you hand it the conflict and let it produce the combined version. Once the file
-is correct and marker-free, telling Git the conflict is settled is two more commands the agent runs
-(`git add cli.py` to mark the file resolved, then `git commit` to complete the merge).
+surrounding code. Once the file is correct and marker-free, telling Git the conflict is settled is
+two more commands the agent runs (`git add cli.py` to mark the file resolved, then `git commit` to
+complete the merge).
-`git status` during a conflict is your map; it lists every file still "unmerged." Your job is the
-verify: read the resolution, confirm it's what you meant, and check `git status` comes back clean. If
-things go sideways, `git merge --abort` rewinds to before the merge with no harm done.
+Here's the part that has changed under your feet, and it's the real lesson of this module's lab. The
+markers above are what a conflict looks like *if you ever see one*. Tell a current frontier
+editor-agent to "merge `feature/stats` into `feature/purge`" and it usually never stops: it reads
+both sides, resolves the collision, completes the merge, and reports a clean result, all in one turn.
+You never saw a marker. From your seat the conflict simply did not happen. That is convenient right
+up until the silent resolution is wrong (it can keep the worse of the two sides, or blend them into a
+line that satisfies neither), and now a bad merge is sitting in your history with nothing that looked
+like an error.
+
+So the skill is no longer "edit the markers by hand." It is two things: **know what a conflict is**
+(so you recognize one when an agent does surface it) and **check `git diff` after every merge** (so a
+silent resolution can't slip a wrong line past you). `git status` during a conflict is your map; it
+lists every file still "unmerged." If you want to *see* the markers before the agent touches them,
+tell it to stop on conflict and show you (you'll do exactly that in the lab). And if things go
+sideways, `git merge --abort` rewinds to before the merge with no harm done.
---
@@ -213,12 +225,15 @@ Everything above is standard Git. Here's why it matters *more* in an AI-assisted
- **Compare, don't commit-and-hope.** Ask the AI for approach A on one branch and approach B on
another. Run both. Keep the winner, delete the loser. You're using branches as cheap A/B
experiments on implementation, something that's painful without them and trivial with them.
-- **Conflicts are a great place to put the AI to work.** A merge conflict is a small, perfectly
+- **The AI resolves conflicts so well you may never see one.** A merge conflict is a small, perfectly
bounded reasoning task: here are two versions of the same lines and the surrounding code; produce
- the correct combined version. The AI can see both sides and the intent. You still decide whether
- its resolution is right (it can absolutely merge two changes into something that satisfies neither),
- but "explain this conflict and propose a resolution" is one of the highest-hit-rate uses of an
- editor-integrated agent. You'll do exactly this in the lab.
+ the correct combined version. A current editor-agent is good enough at this that, told to "merge X
+ into Y," it usually resolves the collision and completes the merge in the same turn, no markers
+ shown, no question asked. That's the highest-hit-rate convenience of the tool and its sharpest trap:
+ you still decide whether the resolution is right (it can absolutely merge two changes into something
+ that satisfies neither), except now you might not even know there *was* a conflict to second-guess.
+ The defense is mechanical and non-negotiable: read `git diff` after every merge. You'll feel both
+ the convenience and the trap in the lab.
---
@@ -227,8 +242,9 @@ Everything above is standard Git. Here's why it matters *more* in an AI-assisted
**Lab language:** shell (Git commands), driving the `tasks-app` from Modules 1–2 with your
editor-integrated AI from Module 4.
-You'll do three things: let the AI try a bold change on a branch, decide its fate, and then
-deliberately create and resolve a merge conflict, using the AI to help resolve it.
+You'll do three things: let the AI try a bold change on a branch, decide its fate, and then engineer
+a merge conflict so you can see one once, undo it, and watch the AI resolve it silently while you do
+the one job that's still yours: verify the result.
**You'll need:**
@@ -371,20 +387,22 @@ Merge conflicts have an outsized reputation for difficulty. You'll engineer a gu
Both branches changed the same `usage:` line, each adding a *different* command. Git won't be able
to auto-merge that line.
-3. Now trigger the conflict. Tell the agent:
+3. **Witness the conflict first.** If you tell a current agent to just "merge them," it will resolve
+ the collision and finish the merge in one turn, and you'll never see a marker (you'll do exactly
+ that in step 5, on purpose). So this once, ask it to stop and show you instead, the same way
+ Module 26 does it:
- > *"You're on `feature/purge`. Merge `feature/stats` into it."*
+ > *"You're on `feature/purge`. Merge `feature/stats` into it. If it conflicts, stop and show me the
+ > conflict; do not resolve it yet."*
- Git stops with a conflict. Confirm the conflict state yourself:
+ The merge stops on the usage line. Confirm the conflict state yourself, then open `cli.py` and find
+ the markers (your usage string will be longer (it carries the commands from earlier modules), but
+ the collision is exactly this: both branches appended a different new command to the same line):
```bash
git status # cli.py listed under "Unmerged paths"
```
-4. Open `cli.py` and find the conflict markers around the usage line (your usage string will be
- longer (it carries the commands from earlier modules), but the collision is exactly this: both
- branches appended a different new command to it):
-
```python
<<<<<<< HEAD
print("usage: python cli.py [add
| list | done | purge]")
@@ -393,50 +411,63 @@ Merge conflicts have an outsized reputation for difficulty. You'll engineer a gu
>>>>>>> feature/stats
```
- (The command bodies for `stats` and `purge` touch different lines, so Git merged *those* cleanly
- on its own; the only collision is the usage string both branches edited.)
+ This is the whole point of the step: *see one real conflict* so you can recognize the shape. `HEAD`
+ is your current branch (`feature/purge`); the block below the `=======` is what `feature/stats`
+ wants. (The command bodies for `stats` and `purge` touch different lines, so Git merged *those*
+ cleanly on its own; the only collision is the usage string both branches edited.)
-5. **Resolve it with the AI.** This is exactly the bounded task the agent is good at. Ask:
+4. **Undo it.** You've seen the conflict; now rewind so the AI can handle it from scratch. Tell the
+ agent (or run it yourself, it's the safe-undo from the Key concepts section):
- > *"`cli.py` has a merge conflict on the usage line. I want the final version to list BOTH the
- > `stats` and `purge` commands. Resolve the conflict and remove the markers."*
+ > *"Abort the merge."*
- It should produce a single, marker-free line listing both commands, e.g.:
+ ```bash
+ git merge --abort
+ git status # clean again, back on feature/purge, no merge in progress
+ ```
+
+ You're now exactly where you were before step 3, mid-experiment with two colliding branches and no
+ merge underway.
+
+5. **Now let the AI do it for real, and watch it auto-resolve.** This time, no stop-on-conflict guard.
+ Direct it the way you actually would in a real workflow:
+
+ > *"You're on `feature/purge`. Merge `feature/stats` into it. The usage line collides; the final
+ > version should list BOTH the `stats` and `purge` commands."*
+
+ Notice what happens: the agent hits the same conflict you just saw, resolves it, and completes the
+ merge in one turn. It probably never shows you a marker. From your seat the merge just "worked." It
+ should have produced a single, marker-free line listing both commands, e.g.:
```python
print("usage: python cli.py [add | list | done | stats | purge]")
```
- **Verify its work; this is the part the AI can get subtly wrong.** A conflict resolver can
- confidently drop one side, leave a stray marker, or "blend" the lines into something that runs but
- means the wrong thing. Read the result and run it:
+ **Here is the punchline of the whole module: you have no idea yet whether that's right, so verify.**
+ The conflict was invisible, which means a wrong resolution would have been invisible too. A resolver
+ can confidently drop one side, leave a stray marker, or "blend" the lines into something that runs
+ but means the wrong thing. The only thing standing between you and a silently-bad merge is the
+ `git diff` you run *after every merge*, conflict or not:
```bash
- git diff # check ONLY what you intended changed; no markers remain
+ git diff HEAD~1 # what the merge actually changed; confirm no markers remain
+ git log --oneline --graph # the fork-and-join: this is a merge commit
python cli.py # run with no args, see the merged usage string
python cli.py stats # both commands actually work
python cli.py purge
```
-6. Once you've verified the resolution, have the agent finish the merge:
-
- > *"The resolution looks right. Stage `cli.py` and complete the merge."*
-
- Then confirm the merge landed as a merge commit:
-
- ```bash
- git log --oneline --graph # the fork-and-join: this is a merge commit
- ```
-
- You just resolved a real merge conflict: you directed it, the agent did the plumbing, and you
- verified the result. The marker syntax is identical no matter the file or the project. Once you can
- read those three lines and check the resolution, a conflict is a short, routine task.
+ If the usage line lists both commands and both run, the AI's silent resolution was correct. If it
+ dropped one, you just caught a bug that left no error message behind, which is precisely why the
+ check isn't optional. You directed the merge, the agent did the plumbing *and* the resolution, and
+ the verify was yours. That last part is the skill: not reading markers by hand, but knowing a
+ conflict can happen and checking the AI's work even when it never tells you one did.
> **Guaranteed-conflict generator.** AI edits are nondeterministic, so if the agent didn't touch the
> same line on both branches and you *didn't* get a conflict in step 3, run the helper script to
-> manufacture one deterministically, then practice steps 4–6 on it. Copy it into your `tasks-app`
-> first (the course's lab scripts live in the course repo, not in `tasks-app`; see Module 4's
-> *You'll need*), then run it from inside the repo:
+> manufacture one deterministically, then practice the witness-and-verify flow on it. Copy it into
+> your `tasks-app` first (the course's lab scripts live in the course repo, not in `tasks-app`; see
+> Module 4's *You'll need*), then run it from inside the repo:
>
> ```bash
> cp ~/ai-workflow-course/the-workflow-course/modules/06-branches-sandboxes-for-experiments/lab/make-conflict.sh .
@@ -463,11 +494,14 @@ The honest limits, so you don't over-trust the sandbox:
branch isn't shared, backed up, or visible to anyone else until there's a remote; that's
**Module 8**. Right now `git branch -D` deletes work that exists nowhere else, permanently. Treat
an unpushed branch as exactly as fragile as the rest of your local-only repo.
-- **The AI can resolve a conflict into something plausible and wrong.** It sees both sides and the
- intent, which makes it good at this, but "good" isn't "trusted." A resolution that runs cleanly can
- still mean the wrong thing (silently keeping the worse of two changes, or merging two behaviors
- into one that satisfies neither). The `git diff` + run-it check in the lab isn't optional ceremony;
- it's the actual safeguard. Reviewing AI output is its own discipline; that's Module 10.
+- **The AI can resolve a conflict into something plausible and wrong, and you may never know one
+ happened.** It sees both sides and the intent, which makes it good at this, but "good" isn't
+ "trusted." Worse, a current agent resolves silently: told to merge, it fixes the collision and
+ finishes the merge in one turn, so a resolution that runs cleanly but means the wrong thing
+ (silently keeping the worse of two changes, or merging two behaviors into one that satisfies
+ neither) leaves no marker, no prompt, no error behind. That invisibility is exactly *why* the
+ post-merge `git diff` is the safeguard, not optional ceremony: it's the only thing that surfaces a
+ conflict the agent already swallowed. Reviewing AI output is its own discipline; that's Module 10.
- **Long-lived branches drift and conflict harder.** The longer a branch lives away from `main`, the
more `main` moves underneath it and the gnarlier the eventual merge. The defense is the same as
"commit often": branch small, merge soon, delete promptly. A branch that's been open for three
@@ -488,9 +522,9 @@ The honest limits, so you don't over-trust the sandbox:
trace, and you have **merged** one in and seen it land on `main`.
- You can explain, in one sentence, why creating a branch costs essentially nothing (it's a movable
pointer, not a copy).
-- You deliberately created a merge conflict, read the `<<<<<<<`/`=======`/`>>>>>>>` markers, had the
- AI resolve it to a marker-free file that runs, verified the result, and let the agent complete the
- merge.
+- You saw a real merge conflict at least once (the `<<<<<<<`/`=======`/`>>>>>>>` markers), then let
+ the AI merge for real and resolve it silently, and you verified the result with `git diff` even
+ though no marker was ever shown to you, confirming the merged file runs.
- You can name the limit: a branch isolates tracked files, not your database, ignored files, or the
outside world.