|
| 1 | +# BRANCH-UPGRADE.md — Multi-Personality GMI via Git Branches |
| 2 | + |
| 3 | +A concrete engineering plan for turning **GitHub Minimum Intelligence |
| 4 | +(GMI)** from a single agent on `main` into an arbitrary number of |
| 5 | +agents, each one a long-lived branch in the same repo. |
| 6 | + |
| 7 | +This document is the operational companion to [`FUTURE.md`](./FUTURE.md) |
| 8 | +§4.4 and Phase 3, and slots underneath [`PIVOT.md`](./PIVOT.md). FUTURE |
| 9 | +explains *why* branches are the right primitive; PIVOT explains how |
| 10 | +gh-aw shapes the surrounding refactor; this file is the diff list. |
| 11 | + |
| 12 | +> **Core assumption.** Every local AI agent **thinks inside its own git |
| 13 | +> branch.** A personality is a branch (`gmi/<name>`). The repo's default |
| 14 | +> branch (`main`) is reserved for human-curated state and for the |
| 15 | +> canonical `gmi/main` personality the public meets on first install. |
| 16 | +> Crossing into multi-agent territory is then a `git checkout -b` — no |
| 17 | +> new repo, no new workflow file, no new install. |
| 18 | +
|
| 19 | +--- |
| 20 | + |
| 21 | +## 0. TL;DR |
| 22 | + |
| 23 | +- **One personality = one branch.** Naming convention `gmi/<name>`. |
| 24 | +- **The agent job checks out its own branch, not `main`.** All thinking, |
| 25 | + memory writes, and self-mutations happen on that branch. |
| 26 | +- **`state/`, `.pi/`, `AGENTS.md`, `memory.log` all become branch-local.** |
| 27 | + Concurrency between agents is *free* because they never touch the |
| 28 | + same paths on the same ref. |
| 29 | +- **Cross-agent reads are explicit:** `git show gmi/other:path` via the |
| 30 | + `gmi-mcp` server. No agent can silently mutate another's mind. |
| 31 | +- **Cross-agent writes are pull requests.** Two agents disagreeing |
| 32 | + produces a merge conflict — a human reviews and picks. |
| 33 | +- **Issue/PR routing** is by label, slash command, or `@gmi-<name>` |
| 34 | + mention. A tiny router job on `main` dispatches the event to the |
| 35 | + correct branch's workflow. |
| 36 | +- **The default install is unchanged.** A first-time user still installs |
| 37 | + one workflow file, opens one issue, and meets one personality |
| 38 | + (`gmi/main`). Everything else is opt-in. |
| 39 | + |
| 40 | +If you implement only one section, implement **§2 (workflow checkout |
| 41 | +target)** — it unlocks every other section. |
| 42 | + |
| 43 | +--- |
| 44 | + |
| 45 | +## 1. Current shape (what we are changing) |
| 46 | + |
| 47 | +Today, the workflow defined in |
| 48 | +`.github/workflows/github-minimum-intelligence-agent.yml` runs |
| 49 | +`lifecycle/agent.ts` on a runner that: |
| 50 | + |
| 51 | +- Checks out `main` (or whatever ref the triggering event points at). |
| 52 | +- Reads `.pi/`, writes JSONL to `state/sessions/`, updates |
| 53 | + `state/issues/<n>.json`, and `git push`es back to the same branch. |
| 54 | +- Holds `contents: write`, `issues: write`, `actions: write`. |
| 55 | + |
| 56 | +There is exactly one personality, one identity, one memory tree, and |
| 57 | +one writer. Two issues opened seconds apart race on `state/sessions/` |
| 58 | +and the loser eats a `non-fast-forward` push rejection. |
| 59 | + |
| 60 | +The branch upgrade rewrites every assumption in that paragraph. |
| 61 | + |
| 62 | +--- |
| 63 | + |
| 64 | +## 2. Workflow changes |
| 65 | + |
| 66 | +### 2.1 Resolve the target branch *first* |
| 67 | + |
| 68 | +Add a leading `resolve` job that decides which `gmi/<name>` branch |
| 69 | +should handle this event, before any LLM cost is paid: |
| 70 | + |
| 71 | +| Trigger | Branch resolution | |
| 72 | +| ------------------------------- | ---------------------------------------------------------- | |
| 73 | +| Issue opened | Label `gmi:<name>` → `gmi/<name>`; default `gmi/main`. | |
| 74 | +| Issue comment with `/gmi <verb>`| `gmi/<verb>` if it exists; else `gmi/main`. | |
| 75 | +| `@gmi-<name>` mention | `gmi/<name>`. | |
| 76 | +| Pull request opened | `gmi/pr-review`. | |
| 77 | +| `schedule:` cron | Specified per cron entry (`gmi/standup`, `gmi/digest`, …). | |
| 78 | +| `workflow_dispatch` | Input `personality`, default `main`. | |
| 79 | + |
| 80 | +The resolver emits `branch=gmi/<name>` as a job output. If the branch |
| 81 | +does not exist, the resolver falls back to `gmi/main` and posts a |
| 82 | +one-line comment ("No `gmi/<name>` personality is installed; routing to |
| 83 | +`gmi/main`.") instead of failing. |
| 84 | + |
| 85 | +### 2.2 `actions/checkout` uses the resolved branch |
| 86 | + |
| 87 | +```yaml |
| 88 | +- uses: actions/checkout@v4 |
| 89 | + with: |
| 90 | + ref: ${{ needs.resolve.outputs.branch }} |
| 91 | + fetch-depth: 0 # we need history for memory/MCP reads |
| 92 | +``` |
| 93 | +
|
| 94 | +This is the single most important change. Every existing read in |
| 95 | +`agent.ts` (`.pi/`, `state/sessions/`, `AGENTS.md`) now naturally |
| 96 | +returns *that personality's* view. |
| 97 | + |
| 98 | +### 2.3 Apply job pushes to the same branch |
| 99 | + |
| 100 | +After the read-only `think` job (see FUTURE.md §3.1) produces |
| 101 | +`session-delta.json`, the `apply` job: |
| 102 | + |
| 103 | +- Checks out the same `gmi/<name>` branch. |
| 104 | +- Validates the delta against the schema. |
| 105 | +- Commits and pushes back to `gmi/<name>` only. |
| 106 | +- **Never pushes to `main`.** If the delta proposes a repo change (e.g. |
| 107 | + a code fix the agent wants merged), it opens a PR from `gmi/<name>` |
| 108 | + to `main` via a `safe-outputs: create-pull-request` action. |
| 109 | + |
| 110 | +### 2.4 Concurrency keys are per branch |
| 111 | + |
| 112 | +```yaml |
| 113 | +concurrency: |
| 114 | + group: gmi-${{ needs.resolve.outputs.branch }}-${{ github.event.issue.number || github.run_id }} |
| 115 | + cancel-in-progress: false |
| 116 | +``` |
| 117 | + |
| 118 | +Two personalities never serialize against each other. Two events on the |
| 119 | +same personality still serialize (push contention on the same branch). |
| 120 | + |
| 121 | +--- |
| 122 | + |
| 123 | +## 3. Repository layout changes |
| 124 | + |
| 125 | +Nothing about the on-disk layout *has* to change — the win comes from |
| 126 | +the fact that the layout is now scoped to a branch. The few additions |
| 127 | +that *do* live on `main`: |
| 128 | + |
| 129 | +``` |
| 130 | +.github/workflows/github-minimum-intelligence-agent.yml # unchanged file, new resolve+checkout logic |
| 131 | +.github-minimum-intelligence/ |
| 132 | + agents/ |
| 133 | + main.md # frontmatter: branch=gmi/main, triggers, safe-outputs |
| 134 | + triage.md # frontmatter: branch=gmi/triage, … |
| 135 | + pr-review.md # … |
| 136 | + standup.md # … |
| 137 | + index.yml # canonical list of installed personalities (see §6) |
| 138 | +``` |
| 139 | + |
| 140 | +The per-branch layout is **identical to today's GMI** — `.pi/`, |
| 141 | +`state/sessions/`, `state/issues/`, `AGENTS.md`, `memory.log`. A |
| 142 | +personality branch is just "GMI as it always was, but on a non-default |
| 143 | +ref." That is the upgrade's whole appeal: it adds no new mental model, |
| 144 | +it just declines to share one across personalities. |
| 145 | + |
| 146 | +### 3.1 The `gmi/main` branch |
| 147 | + |
| 148 | +`gmi/main` is a regular `gmi/*` branch that happens to be the |
| 149 | +default-recipient of unrouted events. It tracks `main` for non-state |
| 150 | +files (workflows, READMEs, code) via a periodic merge job: |
| 151 | + |
| 152 | +``` |
| 153 | +main ────●────●────●────●────●───────── (humans + curated) |
| 154 | + \ \ |
| 155 | +gmi/main ─────●─────────●───────●───── (personality memory + .pi/) |
| 156 | + merge thinks |
| 157 | +``` |
| 158 | + |
| 159 | +Merges from `main → gmi/main` are automatic and routine. Merges from |
| 160 | +`gmi/main → main` are PRs, reviewed by humans. The same shape applies |
| 161 | +to every other `gmi/<name>` branch. |
| 162 | + |
| 163 | +--- |
| 164 | + |
| 165 | +## 4. Identity, memory, and the MCP boundary |
| 166 | + |
| 167 | +### 4.1 Per-branch identity overlay |
| 168 | + |
| 169 | +Each `gmi/<name>` branch carries its own `AGENTS.md` and optionally its |
| 170 | +own `.pi/skills/`. The identity-broker (FUTURE.md §4.2) loads, in order: |
| 171 | + |
| 172 | +1. `AGENTS.md` from `main` — the project-wide soul file. |
| 173 | +2. `AGENTS.md` from `gmi/<name>` — the personality overlay (tone, role, |
| 174 | + allowed verbs, refusal style). |
| 175 | +3. The recent `state/sessions/*.jsonl` from `gmi/<name>` — short-term |
| 176 | + memory. |
| 177 | + |
| 178 | +If `gmi/<name>` has no overlay, the personality is the canonical GMI |
| 179 | +voice with a different memory tree — useful for "same agent, isolated |
| 180 | +context" experiments. |
| 181 | + |
| 182 | +### 4.2 Memory is branch-local by default |
| 183 | + |
| 184 | +`state/sessions/`, `state/issues/`, and `memory.log` live on the |
| 185 | +personality branch. There is no shared mutable memory. This kills an |
| 186 | +entire class of race conditions today's GMI quietly papers over by |
| 187 | +serializing every event through a single concurrency group. |
| 188 | + |
| 189 | +### 4.3 Cross-branch reads via `gmi-mcp` |
| 190 | + |
| 191 | +The `gmi-mcp` server (FUTURE.md §3.3) gains two methods so any |
| 192 | +personality can *read* (never write) any sibling's mind: |
| 193 | + |
| 194 | +- `branch.list()` — enumerate `gmi/*` branches and their metadata. |
| 195 | +- `branch.read(branch, path, range?)` — `git show gmi/<branch>:<path>`, |
| 196 | + optionally a byte range; never mutates. |
| 197 | + |
| 198 | +This is how the standup agent on `gmi/standup` can summarize what |
| 199 | +`gmi/pr-review` saw yesterday without either branch sharing storage. |
| 200 | + |
| 201 | +### 4.4 Cross-branch writes are pull requests |
| 202 | + |
| 203 | +If `gmi/triage` decides `gmi/main` should remember a fact, it opens a |
| 204 | +PR from `gmi/triage → gmi/main` via the safe-outputs apply job. A human |
| 205 | +(or a guardian personality, see §7) reviews. There is no API for one |
| 206 | +agent to push to another's branch. Ever. |
| 207 | + |
| 208 | +--- |
| 209 | + |
| 210 | +## 5. Routing and surface area |
| 211 | + |
| 212 | +### 5.1 Issue & comment dispatch |
| 213 | + |
| 214 | +A label `gmi:<name>` (added by humans or by `gmi/triage`) pins the |
| 215 | +issue to that personality. Once pinned, every subsequent comment on |
| 216 | +that issue routes to the same branch — the conversation stays with the |
| 217 | +personality that started it. `state/issues/<n>.json` on `gmi/<name>` |
| 218 | +records the binding; the resolver consults it before falling back to |
| 219 | +the label. |
| 220 | + |
| 221 | +### 5.2 Slash commands |
| 222 | + |
| 223 | +`/gmi <verb>` in a comment maps to `gmi/<verb>` if such a branch |
| 224 | +exists. `/gmi help` lists installed personalities from `gmi/index.yml`. |
| 225 | +`/gmi handoff <name>` rebinds the issue and re-runs the new |
| 226 | +personality, recording the handoff in both branches' memory. |
| 227 | + |
| 228 | +### 5.3 PR review, scheduled, dispatch |
| 229 | + |
| 230 | +- **PR opened / synchronized** → `gmi/pr-review`. |
| 231 | +- **Cron** → whichever branch the cron entry names. |
| 232 | +- **`workflow_dispatch`** → branch chosen by the dispatcher input. |
| 233 | + |
| 234 | +### 5.4 Bootstrap a new personality |
| 235 | + |
| 236 | +``` |
| 237 | +git fetch origin gmi/main |
| 238 | +git checkout -b gmi/researcher gmi/main |
| 239 | +# edit AGENTS.md to describe the new voice |
| 240 | +git commit -am "hatch gmi/researcher" |
| 241 | +git push -u origin gmi/researcher |
| 242 | +# add an entry to gmi/index.yml on main via a PR |
| 243 | +``` |
| 244 | +
|
| 245 | +That is the entire ritual. The same `workflow_dispatch` install action |
| 246 | +can automate it (`personality=researcher`, `from=gmi/main`). |
| 247 | +
|
| 248 | +--- |
| 249 | +
|
| 250 | +## 6. Lifecycle: GC, archival, audit |
| 251 | +
|
| 252 | +### 6.1 `gmi/index.yml` |
| 253 | +
|
| 254 | +A single YAML file on `main` that lists active personalities. The |
| 255 | +resolver in §2.1 only routes to branches present in the index — a |
| 256 | +branch can exist without being installed (e.g. a draft). This makes |
| 257 | +the install set reviewable in a PR. |
| 258 | +
|
| 259 | +```yaml |
| 260 | +personalities: |
| 261 | + - name: main |
| 262 | + branch: gmi/main |
| 263 | + purpose: Default conversational agent. |
| 264 | + - name: triage |
| 265 | + branch: gmi/triage |
| 266 | + triggers: [issues.opened] |
| 267 | + - name: pr-review |
| 268 | + branch: gmi/pr-review |
| 269 | + triggers: [pull_request.opened, pull_request.synchronize] |
| 270 | + - name: standup |
| 271 | + branch: gmi/standup |
| 272 | + triggers: [schedule] |
| 273 | +``` |
| 274 | + |
| 275 | +### 6.2 Branch GC |
| 276 | + |
| 277 | +A scheduled workflow on `main` runs weekly and: |
| 278 | + |
| 279 | +- Lists `gmi/*` branches. |
| 280 | +- For each branch with no commits in N days **and** no entry in |
| 281 | + `gmi/index.yml`, opens a PR archiving the branch (tag |
| 282 | + `archive/gmi/<name>/<date>`, delete the branch). |
| 283 | +- Never deletes a branch that is in the index; instead flags it. |
| 284 | + |
| 285 | +### 6.3 Audit via `git log` |
| 286 | + |
| 287 | +Because every personality's every thought is a commit on a branch with |
| 288 | +a stable name, `git log gmi/<name> -- state/sessions/` is the entire |
| 289 | +audit trail. No database, no extra UI. A human can `git diff |
| 290 | +gmi/triage..gmi/main -- state/sessions/` to see where two personalities |
| 291 | +disagreed about the same issue. |
| 292 | + |
| 293 | +--- |
| 294 | + |
| 295 | +## 7. Safety and trust |
| 296 | + |
| 297 | +The branch upgrade does **not** weaken the existing trust model; if |
| 298 | +anything it strengthens it by reducing blast radius per agent. |
| 299 | + |
| 300 | +- The agent runs with write access to **one branch**, not to `main`. |
| 301 | + Branch protection on `main` is therefore meaningful for the first |
| 302 | + time (today's GMI is a trusted writer to `main` by design). |
| 303 | +- The apply job's safe-outputs allowlist is **per personality**, set in |
| 304 | + the agent's frontmatter. `gmi/triage` may add labels but not create |
| 305 | + PRs; `gmi/pr-review` may comment but not push to `main`. |
| 306 | +- A **guardian personality** (`gmi/guardian`) can be installed whose |
| 307 | + sole job is to review cross-branch PRs from other personalities. It |
| 308 | + has no write access of its own — only the ability to approve or |
| 309 | + request changes. |
| 310 | +- Prompt injection on `gmi/triage` cannot taint `gmi/main`'s memory, |
| 311 | + because the only path between them is a reviewable PR. |
| 312 | + |
| 313 | +--- |
| 314 | + |
| 315 | +## 8. Migration plan (what to ship, in order) |
| 316 | + |
| 317 | +Each step is independently shippable and preserves the current install. |
| 318 | + |
| 319 | +1. **Create `gmi/main`** as a branch tracking today's `main`. Add the |
| 320 | + resolver job described in §2.1; default everything to `gmi/main`. |
| 321 | + Outwardly nothing changes; internally the workflow is now |
| 322 | + branch-aware. |
| 323 | +2. **Move `state/` writes off `main`.** The apply job pushes to |
| 324 | + `gmi/main`. Add a one-way merge job (`main → gmi/main`) for |
| 325 | + non-state files. Branch protection on `main` becomes possible. |
| 326 | +3. **Add `gmi/index.yml`.** Reviewable list of installed personalities; |
| 327 | + the resolver consults it. |
| 328 | +4. **Ship `gmi/triage`.** First sibling personality; proves the model |
| 329 | + with a small, low-risk surface (labels-only safe-outputs). |
| 330 | +5. **Add `branch.list` / `branch.read` to `gmi-mcp`.** Cross-branch |
| 331 | + reads become a typed API rather than implicit `git` calls. |
| 332 | +6. **Ship `gmi/pr-review` and `gmi/standup`.** Two more personalities |
| 333 | + that exercise PR events and `schedule:` triggers respectively. |
| 334 | +7. **Add the GC workflow** (§6.2) and the guardian personality (§7). |
| 335 | +8. **Document the `git checkout -b gmi/<name>`** bootstrap as the |
| 336 | + official way to add a personality — no admin UI, no extra tool. |
| 337 | + |
| 338 | +--- |
| 339 | + |
| 340 | +## 9. Risks and counter-arguments |
| 341 | + |
| 342 | +- **Branch sprawl.** Cheap to create, easy to abuse. Mitigated by |
| 343 | + `gmi/index.yml` (only indexed branches receive events) plus GC. |
| 344 | +- **Merge conflict UX.** When two personalities edit the same file and |
| 345 | + a human merges, conflicts will arise. This is *the right surface* — |
| 346 | + but it must be visible and survivable. A `gmi/conflicts/` log on |
| 347 | + `main` records every cross-branch PR that conflicted, with the |
| 348 | + personalities involved. |
| 349 | +- **Stale identity overlays.** A `gmi/<name>` branch can drift far from |
| 350 | + `main`'s `AGENTS.md`. The identity-broker (§4.1) should warn when an |
| 351 | + overlay was forked from an `AGENTS.md` that is more than M commits |
| 352 | + behind. |
| 353 | +- **Cost of `fetch-depth: 0`.** Personalities need history for memory |
| 354 | + reads. On large repos this is expensive. Mitigation: shallow fetch |
| 355 | + for the working tree, plus a sparse fetch of `state/sessions/` only. |
| 356 | +- **GitHub UI doesn't visualize "agent branches".** True; this is a |
| 357 | + documentation problem, not a technical one. `gmi/index.yml` plus a |
| 358 | + README badge listing live personalities covers the gap until/unless |
| 359 | + a richer surface exists. |
| 360 | +- **Default-branch protection conflicts with self-upgrade.** GMI today |
| 361 | + rewrites its own workflow file on `main`. Post-upgrade, self-upgrade |
| 362 | + happens on `gmi/main` and proposes a PR to `main` like any other |
| 363 | + cross-branch write. This is *more* auditable, but slower; document |
| 364 | + the new flow in `BOOTSTRAP.md`. |
| 365 | + |
| 366 | +--- |
| 367 | + |
| 368 | +## 10. Bottom line |
| 369 | + |
| 370 | +The repo is already the mind. Branches are already the way git lets |
| 371 | +many minds share one repo without stepping on each other. The upgrade |
| 372 | +is just to **stop pretending an AI agent is special** and let it have |
| 373 | +what every human contributor has had for twenty years: a branch of its |
| 374 | +own. |
| 375 | + |
| 376 | +Once each agent thinks in its own branch, "multi-agent per repo" stops |
| 377 | +being an architecture problem and becomes a naming convention. That is |
| 378 | +the entire point. |
0 commit comments