When two agents work on a project at the same time, we need to keep their unfinished changes separate. We also need to review each feature before combining it with accepted work. We'll see how worktrees give agents separate working folders, and how branches track each feature's commits. Then two agents will add dark mode and a restart control to our game. We'll review each feature and merge it into "main", first with a fast-forward and then with a merge conflict to resolve.
In a previous video, one agent worked in one folder and added a pause control to the game. Its commits formed one line of history, because we finished one task before starting the next.
Now we want two agents to work at the same time. Each agent needs its own working files, so that its unfinished edits do not change the files the other agent is using. We also need to track each feature's commits separately. That lets us review one feature without the other feature's work mixed in.
We collect the work we accept on the branch called "main". Each feature stays separate until we decide to merge it there. A worktree gives an agent a separate working folder, and a branch keeps track of a line of committed work. A merge brings another branch's work into the branch we choose. First, let's see why separate tasks still need separate working files.
Suppose both agents work in the same folder, and both read the same version of the game file. Agent A starts adding dark mode, and agent B starts adding restart. Agent B writes its restart code into the shared file. Agent A is still working on the copy it read earlier, and that copy does not contain the restart code.
Agent A now writes its copy back to the shared file, with dark mode added. That copy never contained the restart code, so the restart code disappears. In this example, the later write replaces the earlier contents without warning either agent. Agent B still believes its restart code is in the file.
Agent B continues by adding the key that should call the restart function. That function is gone, so the key now refers to code that is missing. The file also contains dark-mode changes that agent B did not make or account for. Giving the agents separate tasks did not give them separate files. Each agent needs a working folder of its own.
In a previous video, we called the files we open and edit the working tree. Git supports additional working trees in separate folders attached to the same repository. Each additional working folder is called a worktree. We give the dark-mode agent one worktree and the restart agent another. Each agent edits the files in its own folder, so writing its game file does not replace the other agent's game file. Our original folder remains unchanged, so there are now three working folders. We also need a way to track each feature's committed work.
A branch is a named line of committed work. Git identifies its latest commit with the branch name. We name one branch "dark-mode" and another "restart" to track each feature's work. We use "main" for the work we accept. At the start, all three branch names point to the same commit, the pause commit. Creating these branches has not added either feature yet.
Creating a branch only adds a name that points at an existing commit. It does not make another copy of the history. Each worktree has its own working files and staging area. Each agent can edit and stage its feature independently, while all three worktrees share one repository.
The working tree contains the files the agent reads, edits, and runs. The branch identifies the commit that work builds on. Editing changes the working tree, while committing moves the branch name to the new commit. Give each agent its own branch and worktree. We'll create both before either agent starts editing the game.
Before the agent starts on dark mode, we ask it to create a branch and a worktree for the feature. We also tell it to do the work in that new folder.
The agent runs "git worktree add", using dash b to create the branch "dark-mode". The path names the new folder, "game-dark", next to our project. The final argument, "main", supplies the starting commit.
The command also checks out the new branch into that folder. That means it puts the branch's files there, ready to edit.
The output confirms that the new worktree starts at the pause commit. The agent must work in that folder to keep its edits separate.
We make the same request for restart. The agent runs "git worktree add" again, creating the branch "restart" and the folder "game-restart". Both new folders start from the pause commit, because the branch "main" points at it. The two agents can now edit at the same time, and the files in our own folder stay unchanged.
When an agent commits on its branch, Git records a new version and moves that branch name to the new commit. The other branch names stay where they were. The dark-mode agent changed two files, the game logic and the colours. In its own worktree, it stages them with "git add", then runs "git commit" with the message "Add dark mode". The branch "dark-mode" now points at the new commit, which links back to the pause commit. The branches "main" and "restart" have not moved.
The restart agent finishes too. In its own worktree, it stages the game file with "git add", then runs "git commit" with the message "Add restart control". The branch "restart" moves to its new commit, which also links back to the pause commit. The branch "dark-mode" stays on its commit, and "main" still points to the pause commit. Both features were built from the same starting version. Committing a feature on its branch does not add it to "main" or to the other feature branch.
All three worktrees use the same repository, so we can look at these commits from any of the three folders. The history is shared, even though the working files are separate. Our own folder is still on "main", with pause and neither new feature. The restart folder has pause and restart, without dark mode. Seeing a commit in the history does not put its changes into a folder.
The dark-mode commit is recorded, but we still need to review the feature before accepting it into "main". We compare its files with the version the agent started from. In the dark-mode worktree, we run "git diff main" ourselves and read the output. It compares the current tracked files with the commit named by "main". Here, "main" still points to the pause commit, where this feature started. This excerpt shows the controls text gaining "D: dark mode". The whole change touches two files, the game logic and the colours. Read the changes in both files before trying the game.
Then run the game from the dark-mode worktree, so that you test the files you just reviewed. Check that the colours change, that D toggles the theme, and that P still pauses. A feature is ready to merge when its changes make sense and its behaviour matches what we asked for. Now we can bring this reviewed work into "main".
A merge brings another branch's committed work into the current branch. We want dark mode in "main", so we ask the agent to run the merge in our original folder, where "main" is checked out. In the history graph, the branch "main" still points to the pause commit. The dark-mode commit was built directly from that commit, so it includes everything "main" has, plus the feature.
The agent runs "git merge dark-mode". Since "main" has gained no commits of its own, the dark-mode commit already includes all its work. Git moves "main" to the existing dark-mode commit without creating a new commit. This is called a fast-forward. Git also updates our folder's files, so the game there now has dark mode. Try dark mode and pause in our own folder after the merge. The restart feature is still separate, and we review it next.
The restart agent began from the pause commit, but the branch "main" now points to the dark-mode commit. Comparing restart with "main" would also show differences caused by dark mode. In the restart worktree, we run "git diff" with the pause commit's identifier shown on the slide. This compares the current tracked files with the version where restart began. The diff adds "R: restart" to the controls text, defines the restart function, and connects the R key to it. Run the game from this worktree. Check that R restarts the game and P still pauses and resumes it.
The two branches now contain different work. The branch "main" has dark mode without restart, and "restart" has restart without dark mode. Moving "main" to the restart commit would leave dark mode out of its current version. Git therefore cannot fast-forward to combine the features. It needs a new version that includes both.
To combine the branches, Git compares each branch's version with their shared starting version, the pause commit. This identifies the changes made by each feature. Git combines both sets of changes into one version and records it as a new commit. This is called a merge commit. Most merges finish without a conflict. In our example, there is a line of code that both agents changed: both of their features changed the text that explains the game controls. Git will combine the other changes automatically and stop at that line for us to resolve.
The agent runs "git merge restart" in our original folder on "main". Git combines most of the changes automatically. But, both features made different edits to the controls line, so git cannot combine these changes on its own. It stops and reports a merge conflict. The merge commit has not been created yet, so we must resolve the conflict before finishing the merge.
Both features started from controls text that listed only "P: pause". The version on "main" adds "D: dark mode", and the restart version adds "R: restart". Git cannot decide which version is right, so it leaves that decision to us. We want to keep pause and list both new controls, so the result must combine the two additions.
Git writes conflict markers into the game file in our original folder. The first angle-bracket line opens the conflict. Its label, "HEAD", marks our current version on "main" here.
The equals-sign line separates the version on "main" from the version on "restart". The final angle-bracket line closes the conflict and is labelled "restart".
The code between the markers shows the two alternatives we need to reconcile. Replace the whole conflict block with the intended code, removing the marker lines.
Tell the agent to keep both features and list all three controls. Then read its resolution to check that it matches that instruction. The resolved line lists pause, dark mode, and restart, and the conflict markers are gone. Next, we check that all three controls work in the combined game.
Run the combined game from our own folder. Check that P pauses and resumes, D toggles the theme, and R restarts the game.
After checking the combined game, we ask the agent to finish the merge. It runs "git add" on the resolved game file to stage its contents and mark the conflict as resolved. The agent then runs "git commit" with the message "Merge restart and dark mode". This records the combined version as a merge commit. A parent is a commit that another commit was built from. This merge commit has two parents, the dark-mode commit and the restart commit. The branch "main" now points to the combined version, with pause, dark mode, and restart.
Once both features are merged and checked, we ask the agent to check the feature folders for anything left uncommitted. After that check, it removes the finished worktrees and their branch names. The agent runs "git worktree remove" for each feature folder, then "git branch" with dash d for the two feature branches. The commands remove the folders and branch names, while the merged commits remain in the history of "main". We merge only the features we decide to accept. A feature we do not want simply stays out of "main".
The history shows both features starting from the pause commit and joining in the merge commit now named by "main". We wrote and reviewed each feature in its own worktree before merging it. This is how several agents can work on separate features while we decide what enters the project. Give each agent its own branch and worktree, review its feature, then merge and check the result.
See you in the next video.