Skip to content

Commit 8b56cd6

Browse files
authored
Merge pull request #8 from japer-technology/copilot/update-future-and-branch-upgrade-md
Document branch-per-agent multi-personality model for GMI
2 parents 7abc44f + 8230243 commit 8b56cd6

2 files changed

Lines changed: 447 additions & 6 deletions

File tree

BRANCH-UPGRADE.md

Lines changed: 378 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,378 @@
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

Comments
 (0)