style(no-slop): remove em-dashes + banned words from syllabus and blog
Apply the no-ai-slop standard to the two remaining reader-facing surfaces: - the-workflow-syllabus.md: 91 em-dashes -> 0 (all content preserved; headers normalized to periods/colons, en-dash ranges kept). - blog/ (17 posts + README): 670+ em-dashes -> 0, banned words removed. Filled course URLs and [insert a screenshot] placeholders preserved; blog voice intact. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TfzV5QvtPDz8LJS3Pu5VLT
This commit is contained in:
@@ -12,17 +12,17 @@ Tags: AI, developer workflow, git, worktrees, parallel agents, ver
|
||||
|
||||
I hit this wall the first time I tried to be greedy with AI.
|
||||
|
||||
I had one agent halfway through adding a feature, and a bug report came in that I wanted a *second* agent to chew on while the first one kept going. Two tasks, one machine, no reason I couldn't do both at once — the model's fast and I'm not. So I pointed a second session at the same folder and let it rip.
|
||||
I had one agent halfway through adding a feature, and a bug report came in that I wanted a *second* agent to chew on while the first one kept going. Two tasks, one machine, no reason I couldn't do both at once. The model's fast; I'm not. So I pointed a second session at the same folder and let it rip.
|
||||
|
||||
Within about ninety seconds they were overwriting each other's edits to the same file, neither one aware the other existed. I'd turned two competent agents into one confused mess. The fix wasn't a better prompt or a smarter model. It was a piece of plumbing Git has shipped since 2015 that almost nobody talks about: **worktrees.**
|
||||
|
||||
This is the last post in the first unit of [The Workflow](https://git.jpaul.io/justin/ai-workflow-course), my free course on the engineering scaffolding that makes AI-assisted coding actually work. In the [last post](https://git.jpaul.io/justin/ai-workflow-course) we covered branches — letting one agent try something risky on its own line of history with zero danger to `main`. Worktrees are the natural next step: the move that turns "I run an agent" into "I run *agents*."
|
||||
This is the last post in the first unit of [The Workflow](https://git.jpaul.io/justin/ai-workflow-course), my free course on the engineering scaffolding that makes AI-assisted coding actually work. In the [last post](https://git.jpaul.io/justin/ai-workflow-course) we covered branches: letting one agent try something risky on its own line of history with zero danger to `main`. Worktrees are the natural next step: the move that turns "I run an agent" into "I run *agents*."
|
||||
|
||||
## Where branches alone run out
|
||||
|
||||
Branches give you *logical* isolation. Two lines of history that don't affect each other — spin one up, let the agent do something wild, keep it or throw it away. Great.
|
||||
Branches give you *logical* isolation. Two lines of history that don't affect each other. Spin one up, let the agent do something wild, keep it or throw it away. Great.
|
||||
|
||||
But there's a physical fact branches don't change: **a repo has exactly one working directory, and only one branch can be checked out in it at a time.** The files on disk are *the* files. When you `git switch other-branch`, Git rewrites those same files in place to match the other branch. One floor — and switching branches yanks it out and lays a different one down.
|
||||
But there's a physical fact branches don't change: **a repo has exactly one working directory, and only one branch can be checked out in it at a time.** The files on disk are *the* files. When you `git switch other-branch`, Git rewrites those same files in place to match the other branch. One floor, and switching branches yanks it out and lays a different one down.
|
||||
|
||||
That's fine when *you're* the only one standing on the floor. It falls apart the instant two things happen at once. Watch it break:
|
||||
|
||||
@@ -33,7 +33,7 @@ git switch -c feature/wipe
|
||||
git commit -am "Add wipe command"
|
||||
|
||||
# Agent B starts on a fresh branch off main, editing the SAME line
|
||||
# to add `remaining` — and hasn't committed yet:
|
||||
# to add `remaining` and hasn't committed yet:
|
||||
git switch main
|
||||
git switch -c feature/remaining
|
||||
# ...edits cli.py, uncommitted...
|
||||
@@ -45,7 +45,7 @@ git switch feature/wipe
|
||||
# Please commit your changes or stash them before you switch branches.
|
||||
```
|
||||
|
||||
Git stops you, correctly — switching would silently destroy Agent B's in-progress work. But now you're stuck choosing between bad options: commit half-finished work just to get it out of the way, stash it and hope you remember to pop it (while Agent B keeps editing files that changed under it), or run both agents in the same folder and watch them clobber each other.
|
||||
Git stops you, correctly: switching would silently destroy Agent B's in-progress work. But now you're stuck choosing between bad options: commit half-finished work just to get it out of the way, stash it and hope you remember to pop it (while Agent B keeps editing files that changed under it), or run both agents in the same folder and watch them clobber each other.
|
||||
|
||||
The branch was never the problem. The single working directory is. You need two floors.
|
||||
|
||||
@@ -66,11 +66,11 @@ That creates a brand-new folder, `~/ai-workflow-course/tasks-app-remaining`, wit
|
||||
tasks-app-remaining/ ← a "linked" worktree, on feature/remaining
|
||||
```
|
||||
|
||||
Here's the part that makes it click. Both folders are backed by **one** repository. There's a single `.git` — one object store, one history, one set of branches. The linked worktree doesn't get a *copy* of the history; it gets its own copy of the *files* and a pointer back to the shared `.git`. The line I keep in my head:
|
||||
Here's the part that makes it click. Both folders are backed by **one** repository. There's a single `.git`: one object store, one history, one set of branches. The linked worktree doesn't get a *copy* of the history; it gets its own copy of the *files* and a pointer back to the shared `.git`. The line I keep in my head:
|
||||
|
||||
> **A clone copies the history. A worktree copies the working files and shares the history.**
|
||||
|
||||
A clone is a second repository you sync with push/pull. A worktree is the *same* repository wearing two outfits. A commit you make in one worktree is instantly an object in the shared store — no pushing, no pulling, it's just *there*, because there's only one store. Think of it as one settled past, many present moments: this folder is "the project as of `feature/remaining`," that folder is "the project as of `main`," both writing to the same history.
|
||||
A clone is a second repository you sync with push/pull. A worktree is the *same* repository wearing two outfits. A commit you make in one worktree is instantly an object in the shared store. No pushing, no pulling; it's just *there*, because there's only one store. Think of it as one settled past, many present moments: this folder is "the project as of `feature/remaining`," that folder is "the project as of `main`," both writing to the same history.
|
||||
|
||||
The whole command surface is small:
|
||||
|
||||
@@ -99,15 +99,15 @@ Three folders, one repo, three branches checked out at once. No stashing, no swi
|
||||
|
||||
A generic devops course would mention worktrees as a niche convenience for the human who hates stashing. For AI work they're closer to essential, and the reason is specific to how agents behave:
|
||||
|
||||
- **An agent assumes its working directory is stable.** It reads files, reasons about them, and writes them back over a session that runs for many minutes. If a second agent (or you, switching branches) rewrites those files underneath it, the first agent is now operating on a reality that silently changed — the worst kind of bug, because nothing errors. The work just comes out wrong. A worktree pins each agent to a folder nobody else will touch.
|
||||
- **Parallelism is the whole point of cheap agents.** 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.
|
||||
- **An agent assumes its working directory is stable.** It reads files, reasons about them, and writes them back over a session that runs for many minutes. If a second agent (or you, switching branches) rewrites those files underneath it, the first agent is now operating on a reality that silently changed. That's the worst kind of bug, because nothing errors. The work just comes out wrong. A worktree pins each agent to a folder nobody else will touch.
|
||||
- **Parallelism is the whole point of cheap agents.** 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.
|
||||
- **It keeps the output reviewable.** Each agent's work lands as its own branch with its own clean history, instead of a tangle of interleaved edits on one branch that no human could ever review.
|
||||
|
||||
You don't reach for worktrees because you read about them. You reach for them the first time you watch two agents eat each other's homework.
|
||||
|
||||
## The hands-on version
|
||||
|
||||
The course lab has you run two AI sessions *simultaneously* on the `tasks-app` — one adding a `wipe` command, one adding `remaining` — each in its own worktree. Set up:
|
||||
The course lab has you run two AI sessions *simultaneously* on the `tasks-app`: one adding a `wipe` command, one adding `remaining`, each in its own worktree. Set up:
|
||||
|
||||
```bash
|
||||
cd ~/ai-workflow-course/tasks-app
|
||||
@@ -123,7 +123,7 @@ cd ~/ai-workflow-course/tasks-app-wipe && python cli.py add "from worktree A" &&
|
||||
cd ~/ai-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." Each worktree even has its own `tasks.json` runtime state — separate files, separate state, while both agents work. Total isolation. When they're done, each commit lands on its own branch, and bringing both home is trivial because it's all already in one repo:
|
||||
Each `list` shows only its own task. Worktree A never sees "from worktree B." Each worktree even has its own `tasks.json` runtime state: separate files, separate state, while both agents work. Total isolation. When they're done, each commit lands on its own branch, and bringing both home is trivial because it's all already in one repo:
|
||||
|
||||
```bash
|
||||
cd ~/ai-workflow-course/tasks-app
|
||||
@@ -132,22 +132,22 @@ git merge feature/wipe
|
||||
git merge feature/remaining
|
||||
```
|
||||
|
||||
No fetching, no syncing — the commits are already in the shared store, so the merges are local and instant.
|
||||
No fetching, no syncing. The commits are already in the shared store, so the merges are local and instant.
|
||||
|
||||
## Where it breaks (because I like to be honest)
|
||||
|
||||
Worktrees are sharp tools. The caveats I'd want you to know:
|
||||
|
||||
- **You can't check out the same branch in two worktrees.** Git refuses (`fatal: 'main' is already checked out at ...`). That's a feature — it's exactly what stops two agents writing the same branch — but it surprises people. One branch, one worktree.
|
||||
- **Uncommitted work is *not* shared.** Only commits go to the shared store. Edits sitting modified-but-uncommitted in a worktree exist *only* in that folder, and `git worktree remove` on a dirty worktree refuses unless you `--force` — which throws that 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 gone — you'll have a stale entry in `git worktree list` until you run `git worktree prune`. Prefer `git worktree remove <path>`, which does both.
|
||||
- **One shared object store means one shared fate.** Every linked worktree depends on the main repo's `.git`. Delete or move the main worktree and all of them break. Worktrees are *not* independent backups — they're one repository.
|
||||
- **They don't prevent merge conflicts, they defer them.** Two agents editing the same lines will still conflict *when you merge*. What worktrees buy you is that the conflict happens once, calmly, on your terms — instead of two live agents corrupting each other's files in real time. Isolation during work; resolution after.
|
||||
- **You can't check out the same branch in two worktrees.** Git refuses (`fatal: 'main' is already checked out at ...`). That's a feature (it's exactly what stops two agents writing the same branch), but it surprises people. One branch, one worktree.
|
||||
- **Uncommitted work is *not* shared.** Only commits go to the shared store. Edits sitting modified-but-uncommitted in a worktree exist *only* in that folder, and `git worktree remove` on a dirty worktree refuses unless you `--force`, which throws that 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 gone; you'll have a stale entry in `git worktree list` until you run `git worktree prune`. Prefer `git worktree remove <path>`, which does both.
|
||||
- **One shared object store means one shared fate.** Every linked worktree depends on the main repo's `.git`. Delete or move the main worktree and all of them break. Worktrees are *not* independent backups; they're one repository.
|
||||
- **They don't prevent merge conflicts, they defer them.** Two agents editing the same lines will still conflict *when you merge*. What worktrees buy you is that the conflict happens once, calmly, on your terms, not as two live agents corrupting each other's files in real time. Isolation during work; resolution after.
|
||||
|
||||
## That closes out Unit 1
|
||||
|
||||
That's the whole local foundation: version control as undo for the AI, getting the AI editing real files, committing its config, branches for safe experiments, and now worktrees so you can run more than one agent without a coordination nightmare. When "run two agents at once" feels like "open two folders" instead of "orchestrate a stash dance," you've got it.
|
||||
|
||||
The model is the cheap, swappable part. The workflow around it is the skill that lasts — and this unit is the part of that workflow that lives entirely on your own machine.
|
||||
The model is the cheap, swappable part. The workflow around it is the skill that lasts, and this unit is the part of that workflow that lives entirely on your own machine.
|
||||
|
||||
Next unit we get the work off this one machine: hosting, remotes, and reviewing code you didn't write. If you've run agents in parallel and hit something I didn't cover here — or found a sharp edge of your own — drop a comment. I read them, and the rough spots you hit are exactly what makes the course better.
|
||||
Next unit we get the work off this one machine: hosting, remotes, and reviewing code you didn't write. If you've run agents in parallel and hit something I didn't cover here, or found a sharp edge of your own, drop a comment. I read them, and the rough spots you hit are exactly what makes the course better.
|
||||
|
||||
Reference in New Issue
Block a user