Files
ai-workflow-course/modules/11-collaboration-humans-and-agents/README.md
T
claude 2684095e2f Build out all 27 modules + capstone (#1)
Co-authored-by: claude <claude@jpaul.io>
Co-committed-by: claude <claude@jpaul.io>
2026-06-22 12:19:01 -04:00

433 lines
24 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Module 11 — Collaboration: Humans and Agents on One Repo
> **You now have every piece — issues, branches, PRs, review. This module wires them into one loop,
> and points out that half your "teammates" might not be human.** Once the loop runs the same way no
> matter who's pulling the work, an agent is just another contributor who needs a branch.
---
## Prerequisites
This is the synthesis module for Unit 2's collaboration arc. It assumes the whole chain up to here:
- **Module 2** — commits as checkpoints, and `git diff`/`git log` as the record everyone reads.
- **Module 6** — branches as isolated sandboxes; you make changes off `main`, not on it.
- **Module 7** — worktrees, so more than one branch (and more than one agent) can be live at once
without stepping on each other.
- **Module 8** — a remote on a git host (GitHub the default; a self-hosted forge if you took that
track), so there's a shared copy to collaborate around.
- **Module 9** — issues: the task layer that says *what* needs doing and *who* (human or agent) owns it.
- **Module 10** — pull/merge requests and the skill of reviewing a diff you didn't write.
Each of those taught one move. This module is the assembled motion. If you're missing one, the loop
still works, but a step will feel like a black box — go back and fill it in.
---
## Learning objectives
By the end of this module you can:
1. Run the full collaboration loop end to end — issue → branch → implementation → PR → review →
merge → issue auto-closed — and explain why each step exists.
2. Link a PR to an issue so the merge closes the issue automatically, and explain when that does and
doesn't fire.
3. Decide correctly between a **branch** and a **fork** based on whether you have push access.
4. Reason about **who's allowed to push**: roles, protected branches, and why "never commit to
`main`" stops being a personal habit and becomes an enforced rule.
5. Treat an agent as a contributor — give it a branch, route an issue to it, review its PR on the
same gate you'd use for a human — and know where a human has to stay in the loop.
---
## Key concepts
### Two loops, not one
Module 2 gave you the **inner loop**: edit, `git diff`, commit, repeat. That loop lives on your disk
and is yours alone. It's how *you* (or your agent) make progress in a working session.
This module is the **outer loop** — the one the *team* sees:
```
issue → branch → implementation → pull request → review → merge → issue closed
(M9) (M6) (inner loop, M2) (M10) (M10) (this module)
```
Everything you learned was a single station on this track. The reason to assemble them now — rather
than keep treating issues, branches, and PRs as separate skills — is that the *handoffs between
stations* are where collaboration actually happens, and where it breaks. The issue says what to do.
The branch isolates the attempt. The PR makes the attempt reviewable. The review is the judgment.
The merge is the commitment. Closing the issue is the receipt. Skip a handoff and you get the
failure modes every team knows: work nobody asked for, changes that land straight on `main` with no
review, "done" issues for work that was never actually done.
The loop is worth internalizing as a loop because **it's the same loop regardless of who's doing the
work** — and increasingly, some of the workers are agents. Hold that thought; it's the whole point of
the module, and we'll come back to it.
### The loop, step by step
**1 — The issue (Module 9) is the contract.** Before any code, there's a statement of intent: a
title, a description of the desired behavior, maybe acceptance criteria. It has a number (`#42`) that
the rest of the loop will reference. The issue exists so that "what we're doing and why" lives
somewhere durable and shared — not in one person's head or one chat session that'll evaporate
(Module 1, Seam 2). Assign it to whoever's taking it: a person, or an agent.
**2 — The branch (Module 6) is the workspace.** You never implement on `main`. You cut a branch
named for the work — convention is something traceable like `42-clear-done-command` (the issue
number plus a slug). The name matters more than it looks: months later, `git branch` and the host's
branch list become a map of "what's in flight," and the issue number ties each branch back to its
contract.
```bash
git switch -c 42-clear-done-command # branch off main and switch to it
```
**3 — Implementation is the inner loop (Module 2).** This is where the actual editing happens —
you, or an agent, making commits on the branch. Nothing here is new; it's the edit/diff/commit
rhythm you already have. The branch keeps it isolated, so however bold the change, `main` is
untouched until the loop says otherwise.
```bash
git push -u origin 42-clear-done-command # publish the branch so others (and the host) can see it
```
**4 — The pull request (Module 10) makes it reviewable.** Opening a PR says "this branch is ready
to be considered for `main`." It bundles the diff, a description, and a discussion thread into one
reviewable unit. Crucially, **this is where you link back to the issue** (next section) so the loop
can close itself.
**5 — Review (Module 10) is the judgment gate.** Someone who isn't the author reads the diff for
correctness *and plausibility* — the skill Module 10 is built around. They approve, request changes,
or comment. For AI-generated diffs this gate is doing more work than it used to: the code compiles,
reads cleanly, and is still wrong in a way only review catches.
**6 — Merge is the commitment.** Approved, the PR merges into `main`. Squash, merge-commit, or
rebase — your team picks one; the effect is the same: the branch's work is now part of the shared
trunk. Delete the branch after; its job is done and its name lives on in the merge.
**7 — The issue closes — ideally by itself.** If you linked the PR correctly, merging closes the
issue automatically. The receipt is written without anyone touching the issue. That's the satisfying
*click* of the whole loop landing, and it's the concrete thing the lab makes you feel.
### Linking the PR to the issue (the auto-close)
The mechanic that makes step 7 free: put a **closing keyword** in the PR description. Most hosts —
GitHub, GitLab, Gitea/Forgejo, Bitbucket — recognize a common set:
```
Closes #42
```
`Closes`, `Fixes`, and `Resolves` (and their variants — `close/closed`, `fix/fixed`,
`resolve/resolved`) all work on the major hosts. When the PR merges **into the default branch**, the
host closes the referenced issue and cross-links the two so each shows the other. One line in the PR
body buys you a self-closing loop and a permanent trail from "why we did this" (issue) to "what we
did" (PR/diff) to "when it landed" (merge).
A plain mention without a keyword — just `#42`*links* the two but does **not** close on merge.
That's useful too (for "related to" references), but know the difference: the keyword is load-bearing.
> **The trail is the point.** Six months later, someone — possibly an agent reading the repo as
> durable memory (Module 2) — asks "why does `clear-done` exist?" The answer is one click away:
> issue → PR → diff → merge. You built that trail for free by linking one line.
### Branch vs. fork: it comes down to push access
There are two ways a contributor gets their work in front of the team, and the deciding question is
simple: **can you push to the repo?**
- **You have push (write) access → branch in the repo.** This is the normal case for a team working
on a shared repo, and everything above assumes it. Your branch lives alongside everyone else's on
the same remote; PRs go branch → `main` within one repo.
- **You don't have push access → fork, then PR from the fork.** This is the open-source contribution
model and the "outside contributor" case. You clone the repo into your *own* copy (a fork), push
branches there, and open a PR *across repos* from `your-fork:branch` into `upstream:main`. The
maintainers review and merge; you never needed write access to their repo.
```bash
# Forked-contributor flow (no push access to upstream):
# 1. Fork upstream/repo -> you-now-own you/repo (one click on the host)
# 2. git clone https://host/you/repo
# 3. git switch -c my-fix ; ...commit...
# 4. git push -u origin my-fix # origin = your fork, which you CAN push to
# 5. Open a PR from you/repo:my-fix -> upstream/repo:main
```
For this audience, working mostly on repos you control, **branches are the default and forks are the
exception** — you reach for a fork when contributing to something you don't own. The relevance to AI
work: an agent you run on your own repo branches like any teammate. An agent contributing to a
project it doesn't own forks like any outside contributor. The rule doesn't change for machines.
### Who's allowed to push
"Never commit directly to `main`" started as a personal discipline. On a shared repo it becomes an
*enforced* rule, and that enforcement is the other half of collaboration nobody mentions until it
bites.
**Roles.** Hosts assign access in tiers — typically read (clone, comment), then write/develop (push
branches, open PRs), then maintain/admin (manage settings, force-merge, change protections). A
contributor only needs *write* to do the whole loop above; admin is for the people running the repo.
Give out the least that lets someone do their job — the same least-privilege instinct you already
have for production systems.
**Protected branches.** This is the enforcement mechanism. You mark `main` (and any other shared
branch) as protected, and the host then *refuses* direct pushes to it. The only way in is a PR. You
can layer rules on top:
- **Require a pull request** — no direct pushes, full stop. The loop is mandatory, not optional.
- **Require a review approval** — at least one non-author approval before merge is allowed.
- **Restrict who can merge** — only certain roles can click the button.
Turning these on converts "we agreed not to push to `main`" into "the server won't let you." For a
solo learner this can feel like bureaucracy, but it's exactly the guardrail that makes it safe to add
contributors you trust *less than fully* — including machine ones. (Required **status checks**
"CI must pass before merge" — are the same protected-branch feature, but they need CI to exist first;
that's Module 14. We'll come back and switch it on there.)
### The contributor who isn't human
Here's the synthesis the whole unit was building toward. Re-read the loop — issue, branch,
implementation, PR, review, merge — and notice that **nothing in it specifies that the contributor is
a person.** That's not an accident; it's the most useful property of the whole system right now.
- **An agent is a contributor with a branch.** You hand an agent an issue (Module 9 already framed
assignees as a mix of humans and agents). It cuts a branch, implements, and opens a PR — exactly
the loop above. A human reviews that PR on the same gate used for any teammate (Module 10). The
agent never touches `main`; the protected-branch rules and the review gate apply to it identically.
This is *why* the loop is worth assembling as a loop: it's the harness that lets you accept work
from a contributor whose judgment you don't fully trust yet.
- **Two agents in parallel are just two contributors needing branches.** The moment you run more than
one agent at once, you have the classic collaboration problem — two workers who must not edit the
same files in the same working directory. That's not a new problem, and it already has an answer:
**worktrees (Module 7).** Each agent gets its own working directory and its own branch; they work
simultaneously, each opens its own PR, and you review and merge them independently. Worktrees
earned their module precisely so this case would already be solved by the time you got here.
- **The merge stays human (for now).** The agent can do every step *up to* merge. The merge — the
commitment to shared `main` — is where a human stays in the loop, because review is judgment and
judgment is the thing you haven't delegated yet. Unit 5 is about carefully, conditionally moving
that line; this module is where you should be able to *picture* an agent doing the first five steps
while you do the sixth.
The reframe to carry forward: **collaboration tooling was never really about humans.** It's about
coordinating *contributors* — isolating their work, making it reviewable, controlling who can commit
it to the trunk. Those guarantees are exactly what you need to safely let an agent contribute, which
is why the team layer you just learned doubles as the agent-safety layer you'll lean on for the rest
of the course.
---
## The AI angle
A generic "intro to team git" lesson ends at "branch, PR, review, merge — congrats, you can work on a
team." This module's reason to exist is that **the team you're coordinating now includes agents, and
the loop is what makes that safe.**
- **The loop is the harness for untrusted contributors — and an agent is one.** Branch isolation,
the PR boundary, mandatory review, protected `main` — every one of these was designed to let work
flow from someone whose every change you don't personally vouch for. That's the exact profile of an
agent. You don't need new tooling to put an agent to work; you need the tooling you just learned,
pointed at a new kind of contributor.
- **Volume goes up; the gate has to hold.** A human contributor opens a PR a day. An agent can open
five before lunch. The review gate (Module 10) and the protected-branch rules are what keep that
volume from landing unreviewed on `main`. The faster your contributors, the more the gate earns its
keep — same lesson as Module 1, one layer up.
- **Parallel agents are a solved problem, on purpose.** Two agents at once is just two contributors
needing isolation — worktrees (Module 7) and separate branches. You already have the answer; this
module is where you see *why* you were given it.
- **The auto-closing trail is memory for the next session.** Issue → PR → diff → merge is exactly the
durable, on-disk-and-on-host record a fresh agent reads to reconstruct "why does this exist?"
(Module 2's durable-memory reframe, now spanning the whole loop). Linking the PR to the issue isn't
bookkeeping; it's writing the project's memory in a form the next contributor — human or machine —
can follow.
You're not learning collaboration *and then* learning to work with agents. They're the same skill.
---
## Hands-on lab
**Lab language:** shell (git commands) plus your host's web UI for the issue, PR, review, and merge
steps. You'll implement the feature with your AI the way Module 4 taught — agent editing the files
directly, you reviewing the diff.
The goal is to run the **entire outer loop once**, on the `tasks-app`, and watch the issue close
itself on merge. One small feature, all seven stations.
**The feature:** add a `clear-done` command to the CLI that removes every completed task. It's a
deliberately small, two-file change (logic in `tasks.py`, wiring in `cli.py`) — small enough that the
loop, not the code, is what you're practicing.
**You'll need:**
- Your `tasks-app` repo from earlier modules, with a remote on your git host (Module 8) that supports
issues and PRs.
- Push access to that repo (it's yours, so you have it).
- Your editor-integrated AI tool (Module 4).
- Optionally, your host's CLI (`gh` for GitHub, `glab` for GitLab, `tea` for Gitea/Forgejo) — the web
UI works for everything here, so the CLI is convenience, not a requirement.
Starter artifacts are in this module's `lab/`: `issue.md` (the issue to file) and `pr-body.md` (the
PR description, including the load-bearing closing keyword).
### Part A — Set the guardrail (one-time)
Before the loop, make `main` enforce what you've been doing by hand. In your host's web UI, open the
repo's branch-protection settings and protect `main` with **"require a pull request before merging."**
```bash
# Confirm the rule bites — this push should now be REFUSED by the host:
git switch main
echo "# direct edit" >> README.md
git commit -am "try to push straight to main"
git push # expect: remote rejects the push to a protected branch
git reset --hard HEAD~1 # undo the local commit; we'll do it the right way
```
If the push went through, protection isn't on — fix that before continuing. Feeling the server say
*no* is the point: "never commit to `main`" is now a rule, not a resolution.
### Part B — Issue → branch
1. **File the issue.** Create a new issue from `lab/issue.md` (title and body). Note its number — say
it's `#42`. This is the contract.
2. **Branch for it**, naming the branch after the issue:
```bash
git switch main && git pull # start from current main
git switch -c 42-clear-done-command # use YOUR issue number
```
### Part C — Implementation (with AI)
3. Point your editor-integrated AI at the repo and ask for the feature:
> "Add a `clear-done` command. In `tasks.py`, add a `TaskList` method that removes all completed
> tasks. In `cli.py`, wire up a `clear-done` command that calls it, saves, and prints how many
> were removed. Match the existing style."
4. **Review the diff before you trust it** — the Module 2 habit, the Module 10 skill:
```bash
git diff
```
Confirm it touched only `tasks.py` and `cli.py`, the logic lives in `tasks.py` (not crammed into
the CLI), and it does what you asked. Run it:
```bash
python cli.py add "keeper" ; python cli.py add "trash" ; python cli.py done 1
python cli.py clear-done # expect it to remove the completed one
python cli.py list # "keeper" remains, "trash" is gone
```
5. Commit and push the branch:
```bash
git add tasks.py cli.py
git commit -m "Add clear-done command (closes #42)"
git push -u origin 42-clear-done-command
```
### Part D — PR → review → merge → auto-close
6. **Open the PR** from your branch into `main`, using `lab/pr-body.md` as the description. Make sure
the body contains the closing line with **your** issue number:
```
Closes #42
```
7. **Review it.** Open the PR's "Files changed" tab and read the diff *as a reviewer*, not as the
author — the Module 10 move. For the full effect, pretend an agent wrote it (in a moment, one
will): is the logic where it belongs? Any edge case missed (empty list, nothing done yet)?
Approve it.
8. **Merge it.** Click merge (your protection rule required the PR and, if you added it, the
approval). Delete the branch when prompted.
9. **Watch the issue close itself.** Open issue `#42`. It should now be **closed**, with a link to
the PR that closed it. You didn't touch the issue — the merge did. That click is the whole loop
landing.
```bash
git switch main && git pull # bring the merged work down locally
git branch -d 42-clear-done-command # tidy up the local branch
```
### Part E — Now make the contributor an agent
Run the loop one more time, but this time **let an agent be the contributor for steps 26.** File a
second issue (e.g. "Add a `pending` command that lists only incomplete tasks" — the `TaskList.pending()`
method already exists, so this is wiring only). Then prompt your agent:
> "Take issue #43. Create a branch named `43-pending-command`, implement the feature, commit
> referencing the issue with a closing keyword, push the branch, and open a PR into `main` whose
> description closes #43."
Let the agent drive to the open-PR state. Then **you** are the human at the gate: review the diff,
and merge (or request changes) yourself. You've just watched the exact loop run with a non-human
contributor — and felt precisely where you, the human, stayed in it. If you want the parallel-agents
case, file two issues and run two agents in separate worktrees (Module 7), each on its own branch.
---
## Where it breaks
- **Auto-close only fires on merge to the *default* branch.** Closing keywords close the issue when
the PR lands on `main` (or whatever your default is). Merge into a non-default branch and the issue
stays open — by design. Keep the keyword in the *PR description* (or a commit message); a closing
keyword buried in a mid-thread comment behaves differently across hosts.
- **The exact keyword set is host-specific.** `Closes/Fixes/Resolves` are the safe, widely-supported
trio, but the full list and the cross-repo syntax (`owner/repo#42`, needed when a fork's PR closes
an upstream issue) vary by host. When in doubt, mention-link and close the issue by hand — the trail
still exists.
- **Auto-closed is not the same as actually done.** Merging closes the issue *mechanically*. It says
nothing about whether the work was correct — that judgment was the review (Module 10), and if review
was a rubber stamp, you just auto-closed an issue for broken work. The loop automates the
bookkeeping, never the thinking.
- **Protected branches protect against accidents, not admins.** Most hosts let admins bypass
protection (sometimes silently). And an account with push access — including a *bot* account you set
up for an agent — is an attack surface and a blast radius: its token can push branches and, if
over-permissioned, merge them. Scope machine accounts to the least they need; this is the front edge
of a problem Unit 4 takes head-on.
- **Forks add real friction beyond the extra clone.** Keeping a fork in sync with a fast-moving
upstream is ongoing work, and PRs *from* forks are deliberately limited by hosts (for example, they
often can't access the upstream repo's CI secrets — relevant once you reach Module 14). For repos
you own, prefer branches; reach for forks only when you genuinely lack push access.
- **The loop diagram is the happy path.** Real PRs get change requests, need a rebase onto a moved
`main`, or hit a merge conflict (Module 6) when two contributors touched the same lines — exactly
the parallel-agent scenario worktrees mitigate but don't eliminate. The stations are fixed; the
number of trips around them isn't.
- **Squash-merge collapses authorship.** If your team squashes, the agent's (or your) individual
commits become one commit on `main`, and the per-commit trail lives only on the now-deleted branch /
closed PR. That's usually a fine trade for a clean history — just know the granular history moved
from `main` to the PR record.
---
## Check for understanding
**You're done when:**
- You ran the full loop on `tasks-app` at least once and watched an issue close itself on merge —
with `main` protected so the PR was mandatory, not optional.
- You can draw the seven-station loop (issue → branch → implementation → PR → review → merge → closed)
from memory and say which earlier module owns each station.
- You can state the branch-vs-fork rule in one sentence (push access → branch; no push access → fork)
and why an agent follows the same rule.
- You ran at least one trip around the loop with an **agent as the contributor** for the
implement-and-open-PR steps, and can point to the exact step where you, the human, stayed in the
loop (the merge).
- You can explain why the same tooling that coordinates human teammates is what makes accepting an
agent's work safe.
When the loop feels like one motion rather than six separate tools — and when "give the agent a
branch and review its PR" feels obvious rather than novel — you're ready for Module 12, where we make
the *recovery* half of this safety net its own discipline: reverting a bad PR after it's already
merged.