| 1 | --- |
| 2 | title: Work and the merge queue |
| 3 | description: How the work service keeps work items, pull requests, checks and rulesets, coordinates sessions and claims, decides and lands a merge, and tests many changes at once in the merge queue. |
| 4 | --- |
| 5 | |
| 6 | import Aside from '../../../components/Aside.astro'; |
| 7 | import Diagram from '../../../components/Diagram.astro'; |
| 8 | |
| 9 | `work` (`services/work`, Rust) keeps everything about a change that is not |
| 10 | the code itself: work items, pull requests, comments, reviews, labels, |
| 11 | milestones, rulesets, checks and commit statuses, CODEOWNERS, and the |
| 12 | merge queue. Its database is `g1t-work`. The code lives in `repos`, and |
| 13 | `work` asks `repos` whenever it needs it. |
| 14 | |
| 15 | ## Work items and pull requests |
| 16 | |
| 17 | A work item belongs to a workspace, not to a repository |
| 18 | ([Work items](/work/work-items/)). `work_items` holds them, numbered per |
| 19 | workspace, with no repository of their own. `work_item_repos` links each |
| 20 | to any number of the workspace's repositories, one row per repository, in |
| 21 | a role: |
| 22 | |
| 23 | | Role | Means | |
| 24 | | --- | --- | |
| 25 | | `origin` | The repository it was filed in, if any. For a work item that was an issue, the row keeps the old number as `legacy_number`. | |
| 26 | | `target` | A repository the work is meant to land in. | |
| 27 | | `touched` | A repository where a pull request or commit for it landed or is in progress, added by `work` itself. | |
| 28 | |
| 29 | Each row also keeps the repository's `owner/name`, so a deleted |
| 30 | repository still reads, and when it landed. |
| 31 | |
| 32 | Work items and pull requests share one sequence of numbers per workspace |
| 33 | (`work_counters`), so `#12` names exactly one of them in it. Pull requests |
| 34 | still live in their repository and are unique there: a new one takes the |
| 35 | workspace's next number, never at or below a number its repository already |
| 36 | has. Labels and milestones belong to the workspace too, shared by its work |
| 37 | items and pull requests. |
| 38 | |
| 39 | A pull request records where its head is: a fork of its own, `pulls--<id>` |
| 40 | in the git store, or a branch in the repository |
| 41 | ([Git](/architecture/git/#pull-request-forks)). It records the work item it |
| 42 | is for, the ones it closes and the ones it is only part of, by item id. |
| 43 | |
| 44 | `resolve_ref` is the one place that says what `#N` names, for closing, |
| 45 | Markdown, chat and the CLI. In a repository, `#N` is that repository's pull |
| 46 | request N, else the work item filed there as issue N (its |
| 47 | `legacy_number`), else the workspace's work item N, else pull request N in |
| 48 | another of the workspace's repositories. `owner/name#N` reads as it would |
| 49 | in that repository, and `workspace#N` names another workspace's. |
| 50 | |
| 51 | Who can see and change a work item is `item_role` |
| 52 | ([Identity](/architecture/identity/)). |
| 53 | |
| 54 | `work` reads every push from its event queue. A push to a pull request's |
| 55 | head updates it: the new head, the files it changes, whether it is behind |
| 56 | the base, and which other open pull requests change the same files, the |
| 57 | overlap shown on each. |
| 58 | |
| 59 | When a pull request merges into the default branch, or a commit pushed |
| 60 | there says `Closes #N` (or `Fixes`, `Resolves`), `work` marks that |
| 61 | repository as landed on each work item it names, and records a resolution: |
| 62 | the repository, and the pull request or commit. The work item closes when |
| 63 | it has no target, or once every target has landed. `Part of #N` (or |
| 64 | `Refs #N`) links the pull request or commit, and its repository as |
| 65 | touched, without landing anything. References reach every repository of |
| 66 | the same workspace; a work item in another workspace is never closed. When a merged |
| 67 | pull request lands its own work item, that work item's other open pull |
| 68 | requests in the same repository close as superseded; those in its other |
| 69 | repositories stay open ([How g1t works](/concepts/overview/#several-pull-requests-for-one-work-item)). |
| 70 | |
| 71 | Work items were issues, one repository's each, until a migration moved |
| 72 | them into their workspaces: numbered above every number the workspace's |
| 73 | repositories had used, with their repository as origin, and labels and |
| 74 | milestones merged into the workspace's. A job on `work`'s cron |
| 75 | (`work_migration.rs`) assigned the numbers and announced the mapping once, |
| 76 | as `work.migrated`, for the other services to rewrite what they keep. Old |
| 77 | `/<owner>/<repo>/issues/<n>` links and `#N` references resolve through |
| 78 | `legacy_number` ([Issues became work |
| 79 | items](/work/from-issues/)). |
| 80 | |
| 81 | ## Coordination |
| 82 | |
| 83 | Many agents and people can work on one repository at once. `work` keeps |
| 84 | what lets them see each other, around the work item, which is the unit of |
| 85 | work (`services/work/src/coordination/`): |
| 86 | |
| 87 | | Table | Holds | Module | |
| 88 | | --- | --- | --- | |
| 89 | | `sessions` | One run of someone's own harness (`claude_code`, `codex`, `cursor`, `copilot` or `other`), recorded in the repository it is checked out in and attached to a work item of its workspace, a pull request and a branch | `sessions.rs` | |
| 90 | | `session_entries` | What a session recorded: prompts, reasoning, tool calls, notes, each with an idempotency `key` | `sessions.rs` | |
| 91 | | `claims` | An advisory, expiring "I'm working on #12" | `claims.rs` | |
| 92 | | Work items' `blocked_by` | The work items that have to land first, refused if it would make a cycle | `dependencies.rs` | |
| 93 | | `overlap_pairs` | Pairs of open pull requests changing the same files, announced once per pair | `overlaps.rs` | |
| 94 | |
| 95 | <Diagram name="coordination" /> |
| 96 | |
| 97 | Three choices shape it: |
| 98 | |
| 99 | - **Nothing is a lock.** A claim is advice: anyone may claim a work item |
| 100 | someone else holds, is told about them, and decides. Nothing is ever |
| 101 | refused because of a claim (step 9 in the diagram). |
| 102 | - **Nothing waits on a harness.** No cron sweeps for quiet sessions or |
| 103 | stale claims. The next read or change of a repository's coordination |
| 104 | finds them (`sweep`) and announces them then: a session quiet for 15 |
| 105 | minutes is idle, one quiet for 30 minutes has ended, and a claim not renewed |
| 106 | within its time (30 minutes unless asked, at most 240) has expired. |
| 107 | - **Everything can be sent twice.** Starting a session is idempotent on the |
| 108 | harness's own session id, and each entry on its `key`, so a hook can call |
| 109 | every time without checking first, and a retry after a lost answer does |
| 110 | no harm. |
| 111 | |
| 112 | `get_work` gathers a work item's whole context in one call (`context.rs`): |
| 113 | the work item, its repositories with their roles, what landed where and |
| 114 | which targets are still missing, suggested targets (each with why), its |
| 115 | definition of done, the required checks, claims, sessions, |
| 116 | pull requests with their checks, overlaps and place in the queue, what it |
| 117 | is blocked by and blocks, its decision records, and its latest |
| 118 | coordination events. A session's entries on a pull request are what |
| 119 | **Blame** reads to show the agent's account of a line ([Sessions and |
| 120 | why-blame](/guides/why-blame/)). [Claims and |
| 121 | dependencies](/work/claims-and-dependencies/) has the calls. |
| 122 | |
| 123 | ## Checks and statuses |
| 124 | |
| 125 | Anything can report on a commit: a workflow, an integration, g1t itself. |
| 126 | Each report is a status with a `context` (its name) and a state, kept per |
| 127 | commit in `commit_statuses`; richer reports are check runs and suites. |
| 128 | |
| 129 | Actions reports one status per run, named `<workflow> / <event>`, such as |
| 130 | `CI / pull_request` (`services/actions/src/plan.rs`). A required check |
| 131 | named `CI` is met by a status of that name for any event: |
| 132 | `check_name` in `crates/contracts/src/work.rs` reads the name before the |
| 133 | last ` / `. One that failed fails the check, even if another passed. |
| 134 | |
| 135 | ## Deciding a merge |
| 136 | |
| 137 | Rulesets are stored in `work`, and judged by `crates/rules`, which does no |
| 138 | I/O and so can be tested in full. The same rules run in two places: |
| 139 | |
| 140 | | When | Who asks | What is judged | |
| 141 | | --- | --- | --- | |
| 142 | | A push | `repos`, before anything is stored | Which refs may move, and how: deletion, force-push, signed commits, file paths | |
| 143 | | A merge | `work` | Required reviews, required checks, CODEOWNERS, up to date, the merge queue | |
| 144 | |
| 145 | `requirements` gathers every rule that applies to the target branch, and |
| 146 | `judge` returns, for each, whether it passes, fails or is still waiting, |
| 147 | and whether the person asking may bypass it. A merge happens only when |
| 148 | none fails or waits, for people and coding agents alike. |
| 149 | |
| 150 | When everything passes, `work` asks `repos` to land the pull request on |
| 151 | the base by the merge method asked for: a merge commit, squash, rebase or |
| 152 | fast-forward (`services/repos/src/merge_methods.rs`). Then it publishes |
| 153 | `pull.merged`. |
| 154 | |
| 155 | ## The merge queue |
| 156 | |
| 157 | Two changes can merge without a conflict and still break each other. A |
| 158 | repository whose rules require the merge queue tests each pull request |
| 159 | together with the ones ahead of it, and moves `main` only to a state whose |
| 160 | required checks passed (`services/work/src/queue.rs`). |
| 161 | |
| 162 | <Diagram name="merge-queue" /> |
| 163 | |
| 164 | 1. **Build.** Up to `max_entries_to_build` entries (4 by default) are |
| 165 | tested at once. Each entry is landed by its own merge method on the |
| 166 | state of the entry ahead of it (the default branch, for the first), and |
| 167 | the result is pushed to `g1t-queue/<entry>` in the repository. |
| 168 | 2. **Test.** Workflows that run on `merge_group` run on each of those |
| 169 | commits, and the required checks have to pass there. |
| 170 | 3. **Land.** Entries land in order. Once everything ahead of it has landed, |
| 171 | an entry moves the default branch to the very commit that was tested. |
| 172 | 4. **Eject.** An entry whose checks fail leaves the queue with a failed |
| 173 | check run on its pull request, and the entries behind it are tested |
| 174 | again without it. |
| 175 | |
| 176 | | State | Means | |
| 177 | | --- | --- | |
| 178 | | `waiting` | In the queue, not yet being built | |
| 179 | | `testing` | Built, its checks running | |
| 180 | | `passed` | Its checks passed; it lands when those ahead have | |
| 181 | | `failed` | Its checks failed; it leaves the queue | |
| 182 | | `landed` | On the default branch | |
| 183 | | `removed` | Taken out by hand | |
| 184 | |
| 185 | ### Who answers for a failure |
| 186 | |
| 187 | A combined state can fail because of the entry itself, or because of an |
| 188 | entry ahead of it. `integration.rs` works out which: |
| 189 | |
| 190 | - **Held, not blamed.** A state that fails while an entry ahead is still |
| 191 | being tested is held. If that entry fails too, the held one is tested |
| 192 | again without it and is not blamed. Only once everything ahead has |
| 193 | passed does the held entry answer for the failure, so a broken change is |
| 194 | blamed on itself, never on what was queued behind it. |
| 195 | - **Narrowed down.** An entry whose own checks pass but whose combination |
| 196 | fails breaks *with* some of the entries ahead. `work` builds the default |
| 197 | branch with half of them and the entry, and halves again, within three |
| 198 | extra builds (`PROBE_BUDGET`), recorded in `queue_probes`. The builds run |
| 199 | on `merge_group` like any queued state and never land. What is left is |
| 200 | reported as the `suspects`. |
| 201 | - **Told everywhere.** The answer goes on the pull request's and the |
| 202 | work item's timelines, into the pull request's session, as a decision record |
| 203 | (`integration_failure`, confidence `tested`, in `decisions`), and as the |
| 204 | `integration.failed` event. A passing combination is recorded as |
| 205 | `integration.passed`. |
| 206 | - **Rejoining.** A pull request the queue took out keeps its merge |
| 207 | request. Once a new commit's checks pass, it is merged again, into the |
| 208 | queue, as whoever queued it, by the rules as they are then. |
| 209 | |
| 210 | [The merge queue](/guides/merge-queue/#who-answers-for-it) has the table of |
| 211 | outcomes from a person's side. |
| 212 | |
| 213 | <Aside title="How states are built"> |
| 214 | `work` builds each candidate in the repository itself, through repos' |
| 215 | `build_queue_step`: the entry landed by its own merge method on the |
| 216 | state ahead of it, with the commits `land` would make there, pushed to |
| 217 | the attempt's `g1t-queue/` branch. Landing is then a fast-forward to |
| 218 | exactly that commit, and no sandbox is involved. |
| 219 | [The merge queue](/guides/merge-queue/) has the settings. |
| 220 | </Aside> |
| 221 | |
| 222 | ## Events it publishes and reads |
| 223 | |
| 224 | `work` publishes what happens to work items, pull requests, comments, checks |
| 225 | and the queue (`work_item.*`, `pull.*`, `comment.*`, `status.created`, |
| 226 | `queue.changed` and the like), and the coordination events: |
| 227 | `session.started`, `session.ended`, `work.claimed`, `work.released`, |
| 228 | `work.claim_expired`, `work.handoff_requested`, `dependency.changed`, |
| 229 | `overlap.detected`, `integration.failed`, `integration.passed` and |
| 230 | `decision.recorded`. It reads |
| 231 | `git.push` and a few repository events from `g1t-events-work` |
| 232 | ([Events](/architecture/events/#subscriber-queues)). |