Running-example consistency: paths, tasks.json, command collisions (#7,#10,#11) (#57)
Co-authored-by: claude <claude@jpaul.io> Co-committed-by: claude <claude@jpaul.io>
This commit was merged in pull request #57.
This commit is contained in:
@@ -56,30 +56,30 @@ That's fine when *you* are the only one standing on the floor. It falls apart th
|
||||
two things happening at once. Watch it break:
|
||||
|
||||
```bash
|
||||
# Agent A added a `clear` command and committed it on its own branch:
|
||||
git switch -c feature/clear
|
||||
# ...agent A edits the usage line in cli.py to add `clear`...
|
||||
git commit -am "Add clear command"
|
||||
# Agent A added a `wipe` command and committed it on its own branch:
|
||||
git switch -c feature/wipe
|
||||
# ...agent A edits the usage line in cli.py to add `wipe`...
|
||||
git commit -am "Add wipe command"
|
||||
|
||||
# You start Agent B on a fresh branch off main; it begins editing the SAME
|
||||
# usage line to add `count`, and hasn't committed:
|
||||
# usage line to add `remaining`, and hasn't committed:
|
||||
git switch main
|
||||
git switch -c feature/count
|
||||
git switch -c feature/remaining
|
||||
# ...agent B edits cli.py, hasn't committed...
|
||||
|
||||
# You try to hop the working directory back to Agent A's branch to check on it:
|
||||
git switch feature/clear
|
||||
git switch feature/wipe
|
||||
# error: Your local changes to the following files would be overwritten by checkout:
|
||||
# cli.py
|
||||
# Please commit your changes or stash them before you switch branches.
|
||||
```
|
||||
|
||||
Git stops you — correctly. Switching to `feature/clear` would overwrite Agent B's uncommitted edits
|
||||
Git stops you — correctly. Switching to `feature/wipe` would overwrite Agent B's uncommitted edits
|
||||
to `cli.py` with Agent A's committed version of those same lines, so Git refuses rather than silently
|
||||
destroy the work. But now you're stuck choosing between bad options:
|
||||
|
||||
- **Commit half-finished work** just to get it out of the way (pollutes history, and Agent B's
|
||||
`count` command isn't done).
|
||||
`remaining` command isn't done).
|
||||
- **Stash it** (now Agent B's context lives in a stash you have to remember to pop, and Agent B — a
|
||||
long-running session that thinks its files are right there — is now editing files that silently
|
||||
changed under it).
|
||||
@@ -95,17 +95,18 @@ repository, each with its own checked-out branch.** One repo, many checkouts.
|
||||
|
||||
```bash
|
||||
cd ~/workflow-course/tasks-app # your existing repo from Module 2
|
||||
git worktree add ../tasks-app-count -b feature/count
|
||||
git worktree add ../tasks-app-remaining -b feature/remaining
|
||||
```
|
||||
|
||||
That command creates a brand-new folder, `~/workflow-course/tasks-app-count`, containing a full
|
||||
checkout of your project on a new branch `feature/count`. Your original folder is untouched, still
|
||||
on its own branch. You now have two real directories you can `cd` into, edit, and run independently:
|
||||
That command creates a brand-new folder, `~/workflow-course/tasks-app-remaining`, containing a full
|
||||
checkout of your project on a new branch `feature/remaining`. Your original folder is untouched,
|
||||
still on its own branch. You now have two real directories you can `cd` into, edit, and run
|
||||
independently:
|
||||
|
||||
```
|
||||
~/workflow-course/
|
||||
tasks-app/ ← the "main" worktree, on (say) main
|
||||
tasks-app-count/ ← a "linked" worktree, on feature/count
|
||||
tasks-app/ ← the "main" worktree, on (say) main
|
||||
tasks-app-remaining/ ← a "linked" worktree, on feature/remaining
|
||||
```
|
||||
|
||||
Both are backed by **one** repository. There is a single `.git` — a single object store, a single
|
||||
@@ -127,7 +128,7 @@ because there's only one store.
|
||||
|
||||
Think of the shared object store as the project's single, settled past — every commit, on every
|
||||
branch, in one place. Each worktree is a different *present moment* checked out of that past: this
|
||||
folder is "the project as of `feature/count`," that folder is "the project as of `main`." They all
|
||||
folder is "the project as of `feature/remaining`," that folder is "the project as of `main`." They all
|
||||
write to the same past (commits go to the shared store), but each lives in its own present (its own
|
||||
files on disk).
|
||||
|
||||
@@ -149,9 +150,9 @@ git worktree prune # forget worktrees whose folders were
|
||||
|
||||
```bash
|
||||
$ git worktree list
|
||||
/home/you/workflow-course/tasks-app a1b2c3d [main]
|
||||
/home/you/workflow-course/tasks-app-count d4e5f6a [feature/count]
|
||||
/home/you/workflow-course/tasks-app-clear 7g8h9i0 [feature/clear]
|
||||
/home/you/workflow-course/tasks-app a1b2c3d [main]
|
||||
/home/you/workflow-course/tasks-app-remaining d4e5f6a [feature/remaining]
|
||||
/home/you/workflow-course/tasks-app-wipe 7g8h9i0 [feature/wipe]
|
||||
```
|
||||
|
||||
Three folders, one repo, three branches checked out simultaneously. No stashing, no switching, no
|
||||
@@ -197,7 +198,7 @@ behave:
|
||||
once — a feature here, a bugfix there, a doc update in a third. The constraint was never the
|
||||
model; it was that they'd trip over one repo. Worktrees remove the constraint.
|
||||
- **Each worktree is its own durable memory (Module 2).** A fresh agent dropped into
|
||||
`tasks-app-count` reads `git status` / `git diff` / `git log` and gets *that branch's* ground
|
||||
`tasks-app-remaining` reads `git status` / `git diff` / `git log` and gets *that branch's* ground
|
||||
truth — not a blur of three agents' half-finished work. Per-agent isolation makes per-agent
|
||||
"where were we?" actually answerable.
|
||||
- **It keeps parallel AI output reviewable.** Each agent's work lands as its own branch with its own
|
||||
@@ -214,8 +215,10 @@ to run two agents and watch them eat each other's homework.
|
||||
**Lab language:** shell (Git commands), plus two AI edit sessions on the `tasks-app`.
|
||||
|
||||
In this lab you'll run **two AI sessions at the same time** on the same project — one adding a
|
||||
`clear` command, one adding a `count` command — each in its own worktree, and watch them *not*
|
||||
collide. Then you'll merge both back and clean up.
|
||||
`wipe` command, one adding a `remaining` command — each in its own worktree, and watch them *not*
|
||||
collide. Then you'll merge both back and clean up. (We use two commands your carried-forward
|
||||
`tasks-app` doesn't have yet, so neither agent re-adds something that already exists — the lesson is
|
||||
the parallel isolation, not the commands.)
|
||||
|
||||
**You'll need:**
|
||||
|
||||
@@ -226,7 +229,10 @@ collide. Then you'll merge both back and clean up.
|
||||
- **Two** editor-integrated AI sessions you can run at once (Module 4) — two editor windows, or two
|
||||
terminal AI sessions. If you only have a browser chat, you can still do the lab; just treat each
|
||||
worktree folder as a separate copy-paste context.
|
||||
- The starter scripts and prompts in this module's `lab/` folder.
|
||||
- The starter scripts and prompts in this module's `lab/` folder. As established in Module 4, the
|
||||
course's lab scripts live in the course repo under `modules/NN/lab/`, while `tasks-app` is a
|
||||
separate folder — so **copy the scripts into `tasks-app` and run them by name** (`bash
|
||||
setup-worktrees.sh`), using your real course path in place of `/path/to/`.
|
||||
|
||||
### Part A — Feel the collision (1 minute)
|
||||
|
||||
@@ -238,71 +244,75 @@ the edit an agent would make.) In your `tasks-app`:
|
||||
```bash
|
||||
cd ~/workflow-course/tasks-app
|
||||
|
||||
# Agent A's branch: add `clear` to the usage line and commit it.
|
||||
git switch -c feature/clear
|
||||
sed 's/done <index>/done <index> | clear/' cli.py > cli.tmp && mv cli.tmp cli.py
|
||||
git commit -am "Add clear command (demo)"
|
||||
# Agent A's branch: add `wipe` to the usage line and commit it.
|
||||
git switch -c feature/wipe
|
||||
sed 's/done <index>/done <index> | wipe/' cli.py > cli.tmp && mv cli.tmp cli.py
|
||||
git commit -am "Add wipe command (demo)"
|
||||
|
||||
# Agent B's branch, off main: start adding `count` to the SAME line — leave it uncommitted.
|
||||
# Agent B's branch, off main: start adding `remaining` to the SAME line — leave it uncommitted.
|
||||
git switch main
|
||||
git switch -c feature/count
|
||||
sed 's/done <index>/done <index> | count/' cli.py > cli.tmp && mv cli.tmp cli.py
|
||||
git switch -c feature/remaining
|
||||
sed 's/done <index>/done <index> | remaining/' cli.py > cli.tmp && mv cli.tmp cli.py
|
||||
|
||||
# Try to hop the working directory back to Agent A's branch:
|
||||
git switch feature/clear
|
||||
git switch feature/wipe
|
||||
# error: Your local changes to the following files would be overwritten by checkout:
|
||||
# cli.py
|
||||
# Please commit your changes or stash them before you switch branches.
|
||||
```
|
||||
|
||||
Git refuses — moving the one working directory to `feature/clear` would overwrite Agent B's
|
||||
uncommitted edit with `feature/clear`'s committed version of that line. *That* is the wall: one
|
||||
(The `sed` matches `done <index>`, which is still in your usage line no matter how many commands
|
||||
you've added since Module 1, and inserts a new one right after it — so both branches edit the same
|
||||
line.) Git refuses — moving the one working directory to `feature/wipe` would overwrite Agent B's
|
||||
uncommitted edit with `feature/wipe`'s committed version of that line. *That* is the wall: one
|
||||
directory can't hold two agents' in-progress work at once. These two branches existed only to feel
|
||||
the collision, so clean them up before continuing:
|
||||
|
||||
```bash
|
||||
git restore cli.py # drop Agent B's uncommitted edit
|
||||
git restore cli.py # drop Agent B's uncommitted edit
|
||||
git switch main
|
||||
git branch -D feature/clear feature/count # throw away the demo branches
|
||||
git branch -D feature/wipe feature/remaining # throw away the demo branches
|
||||
```
|
||||
|
||||
### Part B — Create two worktrees
|
||||
|
||||
From inside `tasks-app`, run the setup script (or run the commands by hand):
|
||||
Copy the setup script into `tasks-app` (see *You'll need*), then run it from inside the repo (or run
|
||||
the commands by hand):
|
||||
|
||||
```bash
|
||||
bash modules/07-worktrees-running-agents-in-parallel/lab/setup-worktrees.sh
|
||||
cp /path/to/modules/07-worktrees-running-agents-in-parallel/lab/setup-worktrees.sh .
|
||||
bash setup-worktrees.sh
|
||||
```
|
||||
|
||||
It runs:
|
||||
|
||||
```bash
|
||||
git worktree add ../tasks-app-clear -b feature/clear
|
||||
git worktree add ../tasks-app-count -b feature/count
|
||||
git worktree add ../tasks-app-wipe -b feature/wipe
|
||||
git worktree add ../tasks-app-remaining -b feature/remaining
|
||||
git worktree list
|
||||
```
|
||||
|
||||
You now have three folders backed by one repo. Confirm:
|
||||
|
||||
```bash
|
||||
git worktree list # should show main + feature/clear + feature/count
|
||||
git worktree list # should show main + feature/wipe + feature/remaining
|
||||
```
|
||||
|
||||
### Part C — Run two AI sessions in parallel
|
||||
|
||||
This is the part to actually *do simultaneously*, not one then the other.
|
||||
|
||||
1. Open `~/workflow-course/tasks-app-clear` in one editor/AI session. Give it the prompt in
|
||||
`lab/agent-a-prompt.md` — *add a `clear` command that removes all tasks.*
|
||||
2. Open `~/workflow-course/tasks-app-count` in a **second** editor/AI session. Give it the prompt in
|
||||
`lab/agent-b-prompt.md` — *add a `count` command that prints the number of pending tasks.*
|
||||
1. Open `~/workflow-course/tasks-app-wipe` in one editor/AI session. Give it the prompt in
|
||||
`lab/agent-a-prompt.md` — *add a `wipe` command that removes all tasks.*
|
||||
2. Open `~/workflow-course/tasks-app-remaining` in a **second** editor/AI session. Give it the prompt
|
||||
in `lab/agent-b-prompt.md` — *add a `remaining` command that prints the number of pending tasks.*
|
||||
3. Let both work at the same time. While they run, prove the isolation from a third terminal — but
|
||||
use commands that **already exist**. (`clear` and `count` don't yet; the agents are still writing
|
||||
them.) Give each worktree its own task and list it:
|
||||
use commands that **already exist**. (`wipe` and `remaining` don't yet; the agents are still
|
||||
writing them.) Give each worktree its own task and list it:
|
||||
|
||||
```bash
|
||||
cd ~/workflow-course/tasks-app-clear && python cli.py add "from worktree A" && python cli.py list
|
||||
cd ~/workflow-course/tasks-app-count && python cli.py add "from worktree B" && python cli.py list
|
||||
cd ~/workflow-course/tasks-app-wipe && python cli.py add "from worktree A" && python cli.py list
|
||||
cd ~/workflow-course/tasks-app-remaining && python cli.py add "from worktree B" && python cli.py list
|
||||
```
|
||||
|
||||
Each `list` shows only its own task — worktree A never sees "from worktree B" and vice versa. Each
|
||||
@@ -313,8 +323,8 @@ This is the part to actually *do simultaneously*, not one then the other.
|
||||
4. In each worktree, commit the agent's work on its own branch:
|
||||
|
||||
```bash
|
||||
cd ~/workflow-course/tasks-app-clear && git add . && git commit -m "Add clear command"
|
||||
cd ~/workflow-course/tasks-app-count && git add . && git commit -m "Add count command"
|
||||
cd ~/workflow-course/tasks-app-wipe && git add . && git commit -m "Add wipe command"
|
||||
cd ~/workflow-course/tasks-app-remaining && git add . && git commit -m "Add remaining command"
|
||||
```
|
||||
|
||||
Two agents, two commits, two branches — neither ever saw the other's files.
|
||||
@@ -322,12 +332,12 @@ This is the part to actually *do simultaneously*, not one then the other.
|
||||
5. *Now* the new commands exist — run each in its own worktree to watch it work:
|
||||
|
||||
```bash
|
||||
cd ~/workflow-course/tasks-app-clear && python cli.py clear # agent A's new command
|
||||
cd ~/workflow-course/tasks-app-count && python cli.py count # agent B's new command
|
||||
cd ~/workflow-course/tasks-app-wipe && python cli.py wipe # agent A's new command
|
||||
cd ~/workflow-course/tasks-app-remaining && python cli.py remaining # agent B's new command
|
||||
```
|
||||
|
||||
`count` reports only worktree B's task — the one you added in step 3 — because B's `tasks.json` is
|
||||
the only state it can see. The isolation, one last time.
|
||||
`remaining` counts a single pending task — the one you added to worktree B in step 3 — because B's
|
||||
`tasks.json` is the only state it can see. The isolation, one last time.
|
||||
|
||||
### Part D — Merge back and clean up
|
||||
|
||||
@@ -336,8 +346,8 @@ Bring both features home to `main` in your original worktree:
|
||||
```bash
|
||||
cd ~/workflow-course/tasks-app
|
||||
git switch main
|
||||
git merge feature/clear
|
||||
git merge feature/count
|
||||
git merge feature/wipe
|
||||
git merge feature/remaining
|
||||
```
|
||||
|
||||
Both commits are already in the shared object store, so there's nothing to fetch — the merges are
|
||||
@@ -346,10 +356,12 @@ their `elif` branch in the same spot. That's expected, and it's a *merge-time* e
|
||||
parallel-work collision — resolve it with the exact skill from Module 6, then `python cli.py list`
|
||||
to confirm both commands work.
|
||||
|
||||
Now tear down the worktrees:
|
||||
Now tear down the worktrees (copy the cleanup script into `tasks-app` the same way, then run it from
|
||||
inside the repo):
|
||||
|
||||
```bash
|
||||
bash modules/07-worktrees-running-agents-in-parallel/lab/cleanup-worktrees.sh
|
||||
cp /path/to/modules/07-worktrees-running-agents-in-parallel/lab/cleanup-worktrees.sh .
|
||||
bash cleanup-worktrees.sh
|
||||
git worktree list # only the main worktree remains
|
||||
```
|
||||
|
||||
@@ -366,7 +378,7 @@ Worktrees are sharp tools. The honest caveats:
|
||||
(`fatal: 'main' is already checked out at ...`). This is a feature, not a bug — it's exactly what
|
||||
stops two agents from writing the same branch — but it surprises people. One branch, one worktree.
|
||||
- **Uncommitted work is *not* shared.** Only commits go to the shared store. The edits sitting
|
||||
modified-but-uncommitted in `tasks-app-count` exist *only* in that folder. If you
|
||||
modified-but-uncommitted in `tasks-app-remaining` exist *only* in that folder. If you
|
||||
`git worktree remove` a dirty worktree, Git refuses unless you pass `--force` — and `--force`
|
||||
throws that uncommitted work away for good. Commit before you remove.
|
||||
- **Cleanup is a two-part chore.** Deleting a worktree folder with `rm -rf` does *not* tell Git it's
|
||||
|
||||
Reference in New Issue
Block a user